2020年6月

开启WSL

以管理员身份启动 Windows PowerShell,输入以下命令

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

命令执行可能需要几分钟,系统设置完成后会提示是否重启,我们输入“Y”重启系统以完成设置

下载子系统

WIndows获取发行版子系统下载链接。
然后再命令行执行下载,文件大概四五百兆

Invoke-WebRequest -Uri https://aka.ms/wslubuntu2004 -OutFile Ubuntu.appx -UseBasicParsing

安装子系统

Add-AppxPackage .\Ubuntu.appx

安装好之后,打开开始菜单会看到一个新的应用程序,打开它即可打开子系统。
第一次运行需要配置用户和密码,按照提示设置用户和密码即可。

资料参考:https://blog.irain.in/archives/Windows_Server_2019_Subsystem.html

准备工作

创建相关目录和文件

mkdir -p ./CA/{private,newcerts} && cd ./CA
echo 01 > serial
touch index.txt index.txt.attr

OpenSSL配置文件

编辑openssl.cnf

vi openssl.cnf

将以下内容复制到openssl.cnf文件

[ ca ]
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
dir             = ../CA                 # Where everything is kept
certs           = $dir/certs            # Where the issued certs are kept
crl_dir         = $dir/crl              # Where the issued crl are kept
new_certs_dir   = $dir/newcerts         # default place for new certs.
database        = $dir/index.txt        # database index file.
serial          = $dir/serial           # The current serial number
RANDFILE        = $dir/private/.rand    # private random number file
#unique_subject  = no                   # Set to 'no' to allow creation of
                                        # several ctificates with same subject.

# The root key and root certificate.
private_key     = $dir/private/cakey.pem# The private key
certificate     = $dir/cacert.pem       # The CA certificate

# For certificate revocation lists.
crlnumber         = $dir/crlnumber      # the current crl number
crl               = $dir/crl.pem        # The current CRL
crl_extensions    = crl_ext

# SHA-1 is deprecated, so use SHA-2 instead.
preserve          = no                  # keep passed DN ordering
default_md        = sha256              # use SHA-256 by default
default_days      = 365                 # how long to certify for
default_crl_days  = 30                  # how long before next CRL

# A few difference way of specifying how similar the request should look
# For type CA, the listed attributes must be the same, and the optional
# and supplied fields are just that :-)
policy            = policy_match

# For the CA policy
[ policy_match ]
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional
vi root.conf
[ req ]
default_bits        = 2048
default_keyfile     = r.pem
default_md          = sha256
string_mask         = nombstr
distinguished_name  = req_distinguished_name
req_extensions      = req_ext
x509_extensions     = x509_ext

[ req_distinguished_name ]
countryName                 = Country Name (2 letter code)
countryName_default         = CN
stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = Guangdong
localityName                = Locality Name (eg, city)
localityName_default        = Shaoguan
organizationName            = Organization Name (eg, company)
organizationName_default    = jwj
commonName                  = Common Name (e.g. server FQDN or YOUR name)
commonName_max              = 64
commonName_default          = jwj

[ x509_ext ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints       = CA:TRUE,pathlen:3
keyUsage               = digitalSignature, keyEncipherment, keyCertSign, cRLSign

[ req_ext ]
subjectKeyIdentifier = hash
basicConstraints     = CA:TRUE
keyUsage             = digitalSignature, keyEncipherment, keyCertSign, cRLSign

参数含义:

字段 值
countryName 国家名缩写
stateOrProvinceName 州或省
localityName 地点,如城市
organizationName 组织名
commonName 商标(证书上显示的 CA 名称)

  • xxx_default 设置该字段默认值,这样等一下生成证书时就不用手动填写信息,直接回车使用默认值就行了。

生成 CA 根密钥:

openssl genrsa -out ./private/cakey.pem 2048
Generating RSA private key, 2048 bit long modulus
......................................+++
........................+++
e is 65537 (0x10001)

自签发 CA 根证书:

openssl req -new -x509 -key ./private/cakey.pem -out ./cacert.pem -days 7300 -config ./root.conf
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [CN]:
State or Province Name (full name) [GuangDong]:
Locality Name (eg, city) [ShaoGuan]:
Organization Name (eg, company) [jwj]:
Common Name (e.g. server FQDN or YOUR name) [My CA]:

将 PEM 格式证书转为常用的 DER 格式:

openssl x509 -inform PEM -in ./cacert.pem -outform DER -out ./CA.cer

用 CA 证书签发 SSL 证书
创建文件夹方便管理:

mkdir ../i0w.cn && cd ../i0w.cn

创建用户证书配置文件:

vi server.conf
[ req ]
default_bits        = 2048
default_keyfile     = r.pem
default_md          = sha256
string_mask         = nombstr
distinguished_name  = req_distinguished_name
req_extensions      = req_ext
x509_extensions     = x509_ext

[ req_distinguished_name ]
countryName                 = Country Name (2 letter code)
countryName_default         = CN
stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = Guangdong
localityName                = Locality Name (eg, city)
localityName_default        = Shaoguan
organizationName            = Organization Name (eg, company)
organizationName_default    = jwj
commonName                  = Common Name (e.g. server FQDN or YOUR name)
commonName_max              = 64
commonName_default          = localhost

[ x509_ext ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints       = CA:FALSE
keyUsage               = digitalSignature, keyEncipherment
subjectAltName         = @alt_names

[ req_ext ]
subjectKeyIdentifier = hash
basicConstraints     = CA:FALSE
keyUsage             = digitalSignature, keyEncipherment
subjectAltName       = @alt_names

[ alt_names ]
DNS.1   = localhost
DNS.2   = i0w.cn
DNS.3   = *.i0w.cn
IP.1    = 127.0.0.1
IP.2    = 192.168.0.111
IP.3    = 192.168.1.111

注意:

  1. 在 [ alt_names ] 下填写要签发证书的域名或 IP,支持通配符;
  2. Firefox 下出现 MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY,原因是 basicConstraints 被设置成了 CA:TRUE,改为 CA:FALSE 即可。

生成用户 RSA 密钥:

openssl genrsa -out ./server.key 2048
Generating RSA private key, 2048 bit long modulus
.......................................................................................+++
................+++
e is 65537 (0x10001)

生成用户证书请求:

openssl req -new -key ./server.key -out ./server.csr -config ./server.conf
Generating RSA private key, 2048 bit long modulus
.......................................................................................+++
................+++
e is 65537 (0x10001)
[root@huawei i0w.cn]# openssl req -new -key ./server.key -out ./server.csr -config ./server.conf
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [CN]:
State or Province Name (full name) [GuangDong]:
Locality Name (eg, city) [ShaoGuang]:
Organization Name (eg, company) [jwj]:
Common Name (e.g. server FQDN or YOUR name) [*.i0w.cn]:

签发用户证书:

openssl ca -config ../CA/openssl.cnf -in ./server.csr -out ./server.crt -days 3650 -extensions x509_ext -extfile ./server.conf
Using configuration from ../CA/openssl.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'CN'
stateOrProvinceName   :PRINTABLE:'GuangDong'
localityName          :PRINTABLE:'ShaoGuang'
organizationName      :PRINTABLE:'jwj'
commonName            :T61STRING:'*.i0w.cn'
Certificate is to be certified until Jun 10 01:39:26 2030 GMT (3650 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

附上证书签发目录结构:

$ tree
.
├── demoCA
│   ├── CA.cer              # CA 证书(DER 格式)
│   ├── cacert.pem          # CA 证书(PEM 格式)
│   ├── index.txt           # 签发记录数据库
│   ├── index.txt.attr
│   ├── index.txt.old
│   ├── newcerts
│   │   └── 01.pem
│   ├── private
│   │   └── cakey.pem       # CA 私钥
│   ├── serial
│   └── serial.old
├── 2heng.xin
│   ├── 2heng.xin.crt       # 用户证书
│   ├── 2heng.xin.csr
│   └── 2heng.xin.key       # 用户证书私钥
├── root.conf               # CA 配置文件
└── server.conf             # 用户配置文件

参考来源:
https://2heng.xin/2018/12/16/your-own-ca-with-openssl/
https://blog.csdn.net/cuitone/article/details/87966042

在实际的项目开发过程中,总会遇到多套程序使用一个公众号的情况。而共用一个公众号,首先会遇到的应该是access_token问题了,两个程序互相的去获取access_token,导致被“挤下线”。
这次我遇到的情况比较简单,两套程序都是自己负责开发的,所以比较好处理,只需要修改代码,让它们共用access_token即可。

什么是access_token?

access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。

为什么会被“挤下线”呢?

access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效

怎么解决被“挤下线”呢?

建议公众号开发者使用中控服务器统一获取和刷新access_token,其他业务逻辑服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致access_token覆盖而影响业务

详细想法

微信那边建议的是,使用中控服务器统一获取和刷新access_token,但要去搞一套中控程序,那就太麻烦了,还是用现有的实在点。
这里想到的是用redis来储存access_token,然后A程序从redis获取access_token
如果access_token不存在或已过期,则A程序就从微信服务器获取access_token,然后更新到redis里去。
这样,B程序去resis获取到的access_token就是最新的了,不用再去微信哪里获取,导致A程序获取到的access_token失效。
当然A跟B的顺序不是固定的,谁先发现access_token过期,就谁去更新。然后,如果刚好并发,也有极有可能会出问题(出问题再说)。

ThinkPHP配置

因为要用TP的缓存,所以要在缓存配置里增加redis配置,这样才能用redis来储存缓存内容。
首先,按照手册配置缓存配置,增加Redis的配置。

<?php
use think\facade\Env;

// +----------------------------------------------------------------------
// | 缓存设置
// +----------------------------------------------------------------------

return [
    // 默认缓存驱动
    'default' => Env::get('cache.driver', 'file'),

    // 缓存连接方式配置
    'stores'  => [
        'file' => [
            // 驱动方式
            'type'       => 'File',
            // 缓存保存目录
            'path'       => '',
            // 缓存前缀
            'prefix'     => '',
            // 缓存有效期 0表示永久缓存
            'expire'     => 0,
            // 缓存标签前缀
            'tag_prefix' => 'tag:',
            // 序列化机制 例如 ['serialize', 'unserialize']
            'serialize'  => [],
        ],
        /*** 下面这些是新加的 ***/
        // Redis缓存驱动
        'redis'   =>  [
            // 驱动方式
            'type'   => 'redis',
            // 服务器地址
            'host'       => '127.0.0.1',
        ],
        /*** 上面这些是新加的 ***/
    ],
];

EasyWeChat使用示例

虽然详细想法里写了很多,但其中很多工作都EasyWeChatThinkPHP完成了,所以代码很简单。
下面是简单的使用示例:

// 初始化EasyWeChat,app_id和secret经过脱敏,不要zhao'chao
$wechat = \EasyWeChat\Factory::officialAccount([
    'app_id'    => 'wx4202f388888',
    'secret'    => 'b06e645090bb1bd0aefc6588888',
]);

// 就这么简单的一句,就可以使用Redis储存AccessToken了
$wechat->access_token->setCache(\think\facade\Cache::store('redis'));

// 发模板消息
$wechat->template_message->send([
    'touser'      => 'dsadasdasdas',
    'template_id' => 'vN_nAl6UiLbCnCT_-lwPoGgaMZUXvY0G72Rr3C-5k6o',
    'url'         => 'https://blog.ll00.cn',
    'data'        => [
        // 头部
        'first'    => '您有新的待审批通行证,请尽快处理!',
        // 申请内容
        'keyword1' => '钞票',
        // 预约时间
        'keyword2' => '2020年6月6日',
        // 申请人
        'keyword3' => '西虹市首富',
        // 申请时间
        'keyword4' => '2020年5月20日 13时14分',
        // 底部
        'remark'   => '王多鱼爱夏竹冠名代码',
    ],
]);