Write the Code. Change the World.

1月 09

laravel 好用, ant design pro 不仅好用还好看。那么,laravel + ant design pro 做出来的项目也会很好用,很好看。

创建初始化 laravel 项目以及 ant design pro 项目

先创建 laravel 项目

# 创建 最新版本的 laravel 项目,并且指定项目文件名为 lreact.com
composer create-project laravel/laravel=6.* --prefer-dist lreact.com

# 删除原本的 readme
rm -rf README.md

# 使用新的 readme,简洁
echo '### Laravel 6.* initialize' >> Readme.md

# 初始化 git 仓库
git init
git add .
git commit -m 'Laravel 6.* initialize'

再创建 ant design pro 项目

在 `laravel` 的 `resources` 目录下,新建文件夹 `antdesign`
cd resources

mkdir antdesign

npm create umi
# 依次选择 ant design pro -> javascript

# 当前版本 v4

git add .

git commit -m 'ant design pro v4 initialize'

npm install

安装完成后,需要稍微修改,并打包

部署请参考 https://pro.ant.design/docs/deploy-cn

# 修改输出目录(antdesign 目录下)
vim config/config.js

# 添加指定目录 base 以及 publicPath

export default {
  base: '/dashboard/',
  publicPath: '/dashboard/',
  plugins,
  hash: false,

# 编译
npm run build

# 将打包好的文件复制到 laravel 的 public 目录
mv dist ../../public/dashboard

# 访问站点
https://www.lrect.com/dashboard

能正常访问,但是授权登录错误。先到这来。

如果你不想每次都移来移去,可以在 config/config.js 里追加配置 outputPath
参考

于是,配置为

export default {
  base: '/dashboard/',
  publicPath: '/dashboard/',
  outputPath: '../../public/dashboard',
  plugins,
  hash: false,

再打包。文件就会打包到 laravel 项目下的 public 下的 dashboard 目录下。

修改 request

ant design pro 的请求都放在 services 下。在这里你可以定义和修改请求。但,这里的请求用的是 umi-request, 而这里对 umi-request 的引用放在 utils/request.js 中。这里你可以添加 header,加入拦截器等等。请看 umi-request 文档。

https://github.com/umijs/umi-request/blob/master/README_zh-CN.md

这里添加空的拦截器(request,response),还有添加请求前缀。完整的 request.js 如下。后边,会对拦截器进一步完善。

/**
 * request 网络请求工具
 * 更详细的 api 文档: https://github.com/umijs/umi-request
 */
import { extend } from 'umi-request';
import { notification } from 'antd';
const codeMessage = {
  200: '服务器成功返回请求的数据。',
  201: '新建或修改数据成功。',
  202: '一个请求已经进入后台排队(异步任务)。',
  204: '删除数据成功。',
  400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
  401: '用户没有权限(令牌、用户名、密码错误)。',
  403: '用户得到授权,但是访问是被禁止的。',
  404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
  406: '请求的格式不可得。',
  410: '请求的资源被永久删除,且不会再得到的。',
  422: '当创建一个对象时,发生一个验证错误。',
  500: '服务器发生错误,请检查服务器。',
  502: '网关错误。',
  503: '服务不可用,服务器暂时过载或维护。',
  504: '网关超时。',
};
/**
 * 异常处理程序
 */

const errorHandler = error => {
  const { response } = error;

  if (response && response.status) {
    const errorText = codeMessage[response.status] || response.statusText;
    const { status, url } = response;
    notification.error({
      message: `请求错误 ${status}: ${url}`,
      description: errorText,
    });
  } else if (!response) {
    notification.error({
      description: '您的网络发生异常,无法连接服务器',
      message: '网络异常',
    });
  }

  return response;
};

/**
 * 配置request请求时的默认参数
 */
const request = extend({
  prefix: '/dashboard',
  errorHandler,
  // 默认错误处理
  credentials: 'include', // 默认请求是否带上cookie
});

// request拦截器, 改变url 或 options.
request.interceptors.request.use(async (url, options) => {
  const headers = {
    'Content-Type': 'application/json',
    'Accept': 'application/json'
  };

  return (
    {
      url: url,
      options: { ...options, headers: headers },
    }
  );
})

// response拦截器, 处理response
request.interceptors.response.use((response, options) => {
  return response;
});

export default request;

到目前为止,输出目录,接口前缀,都是手动写的。分别写在了三个地方。如果想修改,必须得每个地方改一次。如果定义一个常量,直接使用常量不就好了么。下边进行常量定义。不过,定义了常量,那就必须保证你的需求里这三个地方的值是一样的。

定义常量,方便输出以及接口调用

在 config/config.js 中,定义常量 BASE , 设置好值。

const BASE = '/dashboard';

然后在 define 中进行注册该值。只有注册了,其他 js 才能访问的到,并且不需要加 window,请看这里文档。
https://pro.ant.design/docs/environment-variables-cn#%E5%A4%84%E7%90%86%E5%9C%A8-lint-%E4%B8%AD%E7%9A%84%E6%8A%A5%E9%94%99

于是 config/config.js 是这样

const BASE = '/dashboard';
…
export default {
  base: BASE,
  publicPath: `${BASE}/`,
  outputPath: `../../public/${BASE}/`,
  plugins,
  hash: false,
  targets: {
    ie: 11,
  },

…

utils/request.js 是这样

const request = extend({
  prefix: BASE,
  errorHandler,
  // 默认错误处理
  credentials: 'include', // 默认请求是否带上cookie
});

好了,提交 git 暂时先到这一步。

git add .

git commit -m '定义BASE作为项的输出路径和Api前缀'

最后又多一句,还是可以使用 npm start 进行奔跑的。

可参考

https://pro.ant.design/docs/getting-started-cn

https://www.yuque.com/yuxuanbeishui/zog1rm

https://learnku.com/articles/32218

https://umijs.org/zh/config/#uglifyjsoptions

https://www.codercto.com/a/26106.html

阅读全文 >>

1月 03

socket.io 默认支持断开重连。可经常断开长连也很不好。短时间内(比如一分钟)断开重连这种现象,很容易观察到,并且可以观察到时间都很准时,每经过某个时间就会断开然后重连。

遇到这个现象,该怎么处理呢。如果熟悉 socket.io 的机制,可能想到心跳问题。这里先跳过。

遇到这个现象 ,可能你会找服务端的原因。是 nginx 出问题了,还是实现 socket.io 的服务出现问题了。(socket.io 是 websocket 的包装版)

  1. 观察 nginx,因为 nginx 的代码最少。发现 nginx timeout 都注释掉了。
# proxy_connect_timeout 60s;
# proxy_send_timeout 60s;
# proxy_read_timeout: Nginx will close the connection if the proxied server does not send data to Nginx in 60 seconds; At the same time, this close behavior is also affected by heartbeat setting of Swoole.
# proxy_read_timeout 60s;
  1. 分析框架源码,这个是最漫长,和可能性大的。毕竟用到别人的框架。这里使用 swoole + laravels + websocket 的 socket.io 的封装。找了好久也没找到问题。

  2. 找不到具体原因,然后去看 socket.io 的文档。才发现,心跳没发送是一个原因。

https://socket.io/docs/server-api/#new-Server-httpServer-options

重述具体现象

前端页面连接好 socket.io 后,如果前端不发送任何消息,60 秒内,服务端就会断开服务。然后重连。如果在 60 秒之内,你有发送消息,这个断开时间就会延长。直到你不发消息后的 60 秒。那如果这样,利用 socket.io 的心跳不就解决了。可是,从始至终,都没看见过发送过心跳。原来是服务端设置的心跳时间太长了,远远超过了 60 秒。也就是心跳还没来得及发,服务端就已经断开了,前端就已经又重连了。既然这样,那控制心跳时间小于 60 秒不就好了。

既然是一种方法,那么前端 socket.io 怎么发送这个心跳呢。

前端连接好服务端后,服务端会推送一个消息下来,这个消息包裹以下信息:

0{"sid":"NWUwZWY4NzBiYjk1OQ==","upgrades":[],"pingInterval":44000,"pingTimeout":36000}

就是以 pingInterval 这个频率来发送心跳。 pingInterval 要大于 pingTimeout。 这样就可以很直接的看到心跳的间隔。修改服务端代码,onOpen 的时候,推送这个消息过来就好,并 pingInterval 是正确的值。

心跳就是 ping pong。一来一回,建立起来就好。

末了

socket.io 是 websocket 的封装版,就是在 websocket 上加入了约定好的算法以及计算,来实现 socket.io 的这种连接方式。

上边虽然解决了不再一分钟断开又重连。可是这个心跳频率还是有点高。因为上边提到的一分钟就断开,导致没办法把心跳设置大一点。这个问题需要处理。还是得去了解源码,进一步分析吧。

阅读全文 >>

12月 25

了解 model.exports export 等等

https://blog.vini123.com/187

了解各种工具包

lodash 。Lodash 通过降低 array、number、objects、string 等等的使用难度从而让 JavaScript 变得更简单。

https://www.lodashjs.com/

moment 。 时间格式处理工具。

http://momentjs.cn/

jsonwebtoken 。用于生产及交验 token

https://github.com/auth0/node-jsonwebtoken

https://blog.csdn.net/qq_37398213/article/details/81606824

https://cloud.tencent.com/developer/article/1431793

url 。 url 模块提供了一些实用函数,用于URL处理与解析。

https://www.cnblogs.com/fengch/p/8610196.html

http://nodejs.cn/api/url.html

yeast 。根据时间戳生成唯一字符串 (做聊天的时候,生成房间 id 可以使用)

https://github.com/unshiftio/yeast

[https://blog.csdn.net/themagickeyjianan/article/details/78588257](https://blog.csdn.net/themagickeyjianan/article/details/78588257)

https://www.javascriptcn.com/read-53551.html

ioredis 。 ioredis 是一个用于 Node.js/io.js 的 Redis 客户端,强健、功能强大且全面。

https://www.oschina.net/p/ioredis
https://blog.csdn.net/qq_33589252/article/details/85535890

ws 。 ws websocket 库, node.js 的 websocket 的实现。

https://www.npmjs.com/package/ws

https://blog.csdn.net/LiMubai_CN/article/details/81844156

os 。 os 模块提供了与操作系统相关的实用方法和属性。

http://nodejs.cn/api/os.html#os_os

http://nodejs.cn/api/os/os_networkinterfaces.html

mysql 。 mysql 连接池

https://www.jianshu.com/p/2239a4df6ed5

bluebird 。bluebird 实现更强大的 Promise

https://www.ibm.com/developerworks/cn/web/wa-lo-use-bluebird-implements-power-promise/index.html

http://bluebirdjs.com/docs/getting-started.html

https://blog.csdn.net/qq_24884955/article/details/84564778

阅读全文 >>

12月 20

虽然 homestead 用的很习惯,还是想尝试新东西 docker 。也不是新东西了,出来有些年了。既然用 laravel ,那么就用 laradock

熟悉 virtualBox + vagrant + homestead 的朋友,对于 laradock 上手也会很快。当初,装 homestead 需要安装 virtualBox(也可以是vm等) 和 vagrant,然后是使用 homestead 来实现环境的配置安装。对于 docker 可以这样理解,docker 就是 virtualBox + vagrant,或是 vagrant。 我们只需要安装一个 docker 就可以了。然后下载 laradock 即可以开始安装了。

步骤

安装使用,一步到位,初观全局

laradock: https://github.com/laradock/laradock

这里以 win 为例子:

# 先选定一个磁盘位置,用来存放 laradock 并且相邻文件夹做为 www 目录(当然也可以其他位置)
cd /e/
mkdir service
cd service

git clone https://github.com/laradock/laradock.git

cd laradock

# 准备配置文件
cp env-example .env

# 修改配置文件
vim .env
# 修改 APP_CODE_PATH_HOST = '../www'

# 创建并启动
docker-compose up -d nginx php-fpm mysql redis workspace

# 重启
docker-compose restart

# 进入服务器
winpty docker-compose exec workspace bash

# 退出 
exec 

上边的过程,可能有些长久。先创建 laravel 项目。

# 默认安装最新版本。当前版本 6.8
composer create-project laravel/laravel --prefer-dist docker.cn

再配置 nginx,创建默认 laravel 项目

cd service/laradock/nginx/sites

# 复制一份配置出来。命名一定要以 .conf 作为后缀
cp app.conf.example docker.cn.conf 

# 修改 docker.cn.conf
vim docker.cn.conf

# 通常会修改 server 下的 server_name 和 root。将 server_name 修改为你想要指定的域名。比如 docker.cn。 root修改为映射的文件目录。比如 /var/www/study/docker.cn/public 。保存退出。

# 每个对象都是一个容器
# 比如 修改了 nginx 只需要单独启动 nginx 即可
docker-compose up -d nginx

然后再执行 winpty docker-compose exec workspace bash 。如果发现还是访问不了。可以 docker-compose restart 再执行 winpty docker-compose exec workspace bash 

配置 host

# 修改 host 文件,再末尾加入 
vim hosts
GG
127.0.0.1 docker.cn

###
https://xueyuanjun.com/post/9608

https://www.cnblogs.com/itfenqing/p/10972603.html

https://blog.csdn.net/lfm940624/article/details/86678698

阅读全文 >>

12月 16

做聊天项目的时候,通常会用到关键词过滤。对于一些敏感信息,可能还会进行内容加密。这里从两个不同的功能点,总结下过程。也是在别人的基础上,跑了一遍而已。照着做,可以一步到位。

环境
1. php
2. js
3. laravel

敏感词过滤

https://github.com/FireLustre/php-dfa-sensitive

composer require lustre/php-dfa-sensitive

# 项目中
use DfaFilter\SensitiveHelper;

# 添加词库,使用文件
$path = storage_path('sensitive/words.txt');
$handle = SensitiveHelper::init()->setTreeByFile($wordFilePath);

# 添加词库,单个添加
$data = [
    '哎呦我的天',
    '太阳',
    '太阳块融化我的脸'
];

$handle = SensitiveHelper::init()->setTree($data);

# 替换,检测
$islegal = $handle->islegal($content);

$content = '啥啥啥,哎呦我的天呀,太阳快融化我的脸';

$content = $handle->replace($content, '*', true);

上边这些,github 上都有,照着做就可以了。

https://www.zkii.net/tech/php/1115.html

数据加密解密

这里数据加密使用 aes。数据加密解密的对象是字符串。无论哪 js 还是 php,都可以互相转换。不过这里可能存在一种文体,js 加密的 js 可以解密,但 php 不能解密。反之亦然。出现这种情况,往往是加密解密模式没有配对上。

** js 加密解密 **

参考: https://blog.csdn.net/yingbaoyu/article/details/95761177

如果不是 node 环境,可以下载 https://pan.baidu.com/s/1Mvg8vhlD56wvS4b6yjUoaA 提取码:57gd

例子:


const aseKey = 'YjKp7COQ9QZN2EgMJdiI8tzBsJarvQAr'; const aseIv = 'zmtuC5UyMfK3r1QYXKa1lYmNNy8F5jiP'; //将秘钥转换成Utf8字节数组 const key = CryptoJS.enc.Utf8.parse(aseKey); const iv = CryptoJS.enc.Utf8.parse(aseIv); let data = { name: 'vini', gender: 1 } data = JSON.stringify(data); let encoded = CryptoJS.AES.encrypt(data, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }).toString(); console.log(encoded); let uncoded = CryptoJS.AES.decrypt(encoded, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }).toString(CryptoJS.enc.Utf8); console.log(uncoded); # 这个时候, data 应该等于 uncoded

key 的长度决定了 CBC 模式的不同。

# php 加密解密

<?php

namespace App\Helper;

class Aes
{
    static public function encrypt($data)
    {
        $method = 'AES-256-CBC';
        $key = 'YjKp7COQ9QZN2EgMJdiI8tzBsJarvQAr';
        $iv = 'zmtuC5UyMfK3r1QY';
        return openssl_encrypt($data, $method, $key, 0, $iv);
    }

    static public function decrypt($data)
    {
        $method = 'AES-256-CBC';
        $key = 'YjKp7COQ9QZN2EgMJdiI8tzBsJarvQAr';
        $iv = 'zmtuC5UyMfK3r1QY';
        return openssl_decrypt($data, $method, $key, 0, $iv);
    }
}

这里为了方便,把 method key iv 都写死了。项目里边不会这样做。比如,可以定义一个类:

<?php
namespace App\Utils;

class Aes
{
    protected $aesSecret = 'YjKp7COQ9QZN2EgMJdiI8tzBsJarvQAr', $aesIv = '00000000000000000000000000000000';

    /**
     * 进制转换
     * @param string $hex
     * @return string
     */
    protected function aesHexIv($hex = '')
    {
        $string = '';
        $hex = $hex != '' ? $hex : $this->aesIv;
        for ($i = 0; $i < strlen($hex) - 1; $i += 2) {
            $string .= chr(hexdec($hex[$i] . $hex[$i + 1]));
        }
        return $string;
    }

    /**
     * AES加密
     * @param string $content
     * @param string $key
     * @param string $hex
     * @return string
     */
    public function aesEncrypt($content = '', $key = '', $hex = '')
    {
        $hash = hash('sha256', $key ?: $this->aesSecret, true);
        $enCrypt = openssl_encrypt($content, 'AES-256-CBC', $hash, PKCS7_TEXT, $this->aesHexIv($hex));
        return base64_encode($enCrypt);
    }


    /**
     * AES解密
     * @param string $content
     * @param string $key
     * @param string $hex
     * @return string
     */
    public function aesDecrypt($content = '', $key = '', $hex = '')
    {
        $content = base64_decode(str_replace(' ', '+', $content));
        $hash = hash('sha256', $key ?: $this->aesSecret, true);
        return openssl_decrypt($content, 'AES-256-CBC', $hash, PKCS7_TEXT, $this->aesHexIv($hex));
    }
}

https://www.php.net/manual/zh/openssl.pkcs7.flags.php

更多信息

https://baike.baidu.com/item/%E9%AB%98%E7%BA%A7%E5%8A%A0%E5%AF%86%E6%A0%87%E5%87%86/468774?fromtitle=aes&fromid=5903&fr=aladdin

https://www.keylala.cn/aes

https://blog.csdn.net/GlatChen/article/details/79978875

https://suijimimashengcheng.51240.com/

进一步了解 aes 加密解密

在多端配合的情况下,不是 keyiv 都对,就可以解密出加密的数据的。 秘钥位数,加密模式,填充方式也得一一对应上。

aes 参数

  1. key length(密钥位数,密码长度)
  2. key (密钥,密码)
  3. IV (向量)
  4. mode (加密模式)
  5. padding (填充方式)

阅读全文 >>

12月 04

npm config set registry https://registry.npm.taobao.org 

npm config get registry 
– 或npm info express

安装 ant design pro

npm create umi

# 选择 ant design pro
# 选择 typescript或javascript

阅读全文 >>

12月 03

校准时间

有些时候,特别是虚拟机,跑着跑着,可能就不准了。校准时间就有必要了。

使用 ntpdate

这里使用 ntpdate 来纠正数据

# centos
yum install ntpdate

# ubuntu
apt-get install ntpdate

# 开始纠正
ntpdate -u ntp.api.bz

如果发现时间和上海时间相差 8 个小时,时区问题请解决,再执行上边的命令。

vim /etc/profile

G

# 调到末尾,加入下边的命令
export TZ='CST-8'

# 使得环境变量生效,再执行上边的命令纠正时区
source /etc/profile

如果经常纠正可以加入定时任务

参考

https://time.is/

https://www.cnblogs.com/luchuangao/p/7795293.html

https://blog.csdn.net/wblinux/article/details/81981328

https://blog.csdn.net/qq_27664167/article/details/80921327

https://www.cnblogs.com/st-jun/p/7737188.html

阅读全文 >>