两双筷子 - PHP
type: channel
link: https://www.dbkuaizi.com/category/php/
items: 多用户 PassKey 认证插件
items: 1Panel 部署 FastAdmin 管理端访问 302 解决方案
items: Typecho 多吉云上传插件
items: Typecho 垃圾评论过滤插件
items: Yii2 学习笔记
items: 利用短信模板提取短信变量
items: Typecho 多用户二次认证插件
items: PHP 程序执行函数
items: 基于 amis 的一些尝试
items: ThinkPHP6 使用 jwt-auth 组件

多用户 PassKey 认证插件
type: item
date: 2026-03-08T09:15:00+00:00
description: 出于安全考虑，去年开发了一个 OTP 多因素认证插件，后面就一直想着开发通行密钥插件，这样就不需要输入密码了，点两下就能登录进来，于是就花了两个小时使用 GPT-5.3-Codx 写了这个插件，不得不说 AI 还是太好用了。功能支持多用户的 PassKey 插件（通行密钥、WebAuthn 说的都是它）,支持 Typecho 1.2.0 + 的版本。支持自定义通行密钥名称一个用户支持创建多个通行密钥使用安装插件后，去个人设置页面绑定一个通行密钥通行密钥绑定后 就可以在登录时使用下载去 CNB 下载这个插件https://cnb.cool/dbkuaizi/typecho/passkey/-/releases或者直接下载代码仓库：https://cnb.cool/dbkuaizi/typecho/passkey注意：releases 发布版本 中的 zip 包已经包含 PassKey 插件目录，如果是直接下载的代码仓库，需要自行创建 usr/plugins/PassKey 目录

1Panel 部署 FastAdmin 管理端访问 302 解决方案
type: item
date: 2025-12-28T12:52:23+00:00
description: 最近使用 1Panel 面板部署 FastAdmin 项目用于测试，站点的伪静态选择 thinkphp，在部署完成后发现用户端可以正常访问，而管理端则会无限制重定向到 login/index，最终提示 “重定向次数过多...” 如下图所示：经过排查 发现问题出现在 1Panel 自带的 fastcgi-php.conf 上这个文件上，如图 21 行:解决方案是注释掉这一行，然后使用 fastadmin 官方推荐的 fastcgi 配置，代码如下：location ~ [^/]\.php(/|$) { fastcgi_pass 127.0.0.1:9000; # include fastcgi-php.conf; # 注释掉这一行或者直接复制我的示例代码 include fastcgi_params; fastcgi_index index.php; fastcgi_split_path_info ^((?U).+\.php)(/?.+)$; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; }保存修改的配置后，fastadmin 管理端入口即可正常访问：使用版本为 1Panel 2.0.16, OpenResty 版本为 1.27.1.2-2-3-focal

Typecho 多吉云上传插件
type: item
date: 2025-10-16T13:19:00+00:00
description: 去年了解到多吉云这个CDN 厂商，感觉性价比很高，并且网友使用后的评价也很不错 @二丫讲梵。然后看了一眼现在使用的又拍云 CDN，还有一些余额没有用完。想着还是等用完了这些余额，再迁移到多吉云上去。直到今年十月前后，终于快用完了。但是迁移时发现 Typecho 没有支持多吉云的插件，有一个支持 S3 存储协议的插件，但是阅读完多吉云文档后发现，多吉云的 S3 文档与标准版 S3 规范有一些差异，不能直接使用，所以就有了这个插件。DogeUpload 插件Typecho 多吉云附件上传插件, 版本要求 Typecho >= 1.2.0 版本。直接上传附件到多吉云，且支持更新和删除附件。自定义加速域名。多吉云图片处理后缀支持。时间戳防盗链，支持自定义 Token 有效期。历史资源处理 （将 博客域名自动替换成 CDN 域名）。超轻量级设计，贯彻 Typecho 极简设计理念，插件体积仅 32 KB。代码结构简单、注释完善、使用命名空间，适合作为插件开发参考。2026-01-26 更新 插件 1.0.1 适配 Typecho 1.3.0 版本，解决因 Upload 基类传参调整导致上传报 “Server Error” 的问题，感谢 @曾墨 的反馈下载地址：https://cnb.cool/dbkuaizi/blog/-/tree/main/typecho_plugin下载 "DogeUpload.zip"安装方式：将压缩包中的文件夹 DogeUpload 上传至 Typecho 插件目录 typecho/usr/plugins 下 并在插件面板启用。配置页预览更高性价比的选择博客建立后一直使用又拍云的CDN 服务，去年发现了多吉云这个CDN厂商，了解后得知 多吉云其实是一个 "二道贩子"，他们底层使用 阿里云、腾讯云等国内大厂的 CDN 节点作为基础支撑，得益于量大的原因，他们可以给出比直接购买阿里云、腾讯云CDN 更加实惠的价格。因为我前期一直在使用 又拍云，所以对又拍云的计费规则比较了解，这里就对比一下 又拍云 和 多吉云的成本差异：注：这里以 2025年10月15日 各厂商官网价格为基础作比较，若需核算成本，请以官网价格为准。又拍云云存储：0.0043 元 / GB / 天 (注：可以以当天流量使用情况进行抵扣)CDN 国内流量：0.29 元 / GBHTTPS 请求：0.05 元 / 万次HTTPS 证书免费更新多吉云云存储：0.003 元 / GB / 日CDN 国内流量：0.11 元 / GB （每月前 20 GB 免费）HTTPS 请求：0.04 元 / 万次（每月前 200W 次免费，日流量或云存储超过2TB 可联系客服免除 HTTPS 费用）HTTPS 证书续订目前免费，但官方说后期会收费，默认证书厂商 1元/次，指定厂商2元/次（后面有免费方案）我这里以 月流量 100 GB、云存储 10 GB、HTTPS 月请求数量 60W 来计算两家使用成本的差异（注：如果考虑购买资源包，成本会更低）。费用项目又拍云多吉云备注1. 云存储费用 - 计算公式10GB × 0.0043元/天 × 30天10GB × 0.003元/天 × 30天 - 小计1.29 元0.9 元 2. CDN流量费用 - 计算公式100GB × 0.29元/GB(100GB - 20GB) × 0.11元/GB多吉云每月前20GB免费- 小计29 元8.8 元 3. HTTPS请求费用 - 计算公式60万次 × 0.05元/万次0 元多吉云每月前200万次免费，60万次在免费额度内- 小计3 元0 元 4. 费用抵扣 - 规则存储费可抵扣流量费无额外抵扣规则又拍云特色- 抵扣金额1.29 元 月度最终成本 - 计算公式(CDN费 - 抵扣额) + HTTPS费存储费 + CDN费 + HTTPS费 - 总计(29 - 1.29) + 3 = 30.71 元0.9 + 8.8 + 0 = 9.7 元 大家可以参考这个费用情况核算一下，更换为多吉云后可节省多少钱，基本上都能节省 2/3 左右。HTTPS 证书更新多吉云的 CDN HTTPS 证书与又拍云有些差异，又拍云提供免费与收费的证书，且免费的证书可以免费自动续签。多吉云本身提供免费的 HTTPS 证书，但自动申请证书的服务是收费的（更新成功 默认厂商 1元/次，指定厂商 2元/次），但目前处于公测阶段，限时免费 ，具体可见官方文档： https://docs.dogecloud.com/cdn/manual-config-auto-cert。 现阶段证书是免费更新的，但官方文档中说后续肯定是会收费的，那么每年的成本是多少？以多吉云证书自动更新规则来计算，证书有效期 90 天，到期前 10 天自动…

Typecho 垃圾评论过滤插件
type: item
date: 2025-01-27T15:18:00+00:00
description: 前言目前使用的垃圾评论过滤插件在添加词库时十分繁琐，需要进入插件的设置页面才能添加。但从操作的角度上来说，直接在评论管理页面进行操作是最符合直觉的，所以有了这款插件。功能兼容性要求：依赖 Typecho 1.2.0+ 版本，利用命名空间功能。无需修改数据库结构：通过插件配置表实现拦截功能，安全高效。便捷操作：支持直接在评论管理页面一键添加屏蔽词，操作直观。智能处理：当新增屏蔽关键词时，自动处理符合条件的待审评论，节省人工审核时间。属性联动拦截：插件可自动检测评论中被拉黑的属性。例如，若拉黑某个 IP 地址，可自动屏蔽该 IP 提交的所有评论内容，并将其加入屏蔽词库。详细统计：在插件管理页面显示以下统计信息：已定义的过滤规则总数累计拦截次数自动收集的屏蔽词数量适配移动端：注入拉黑操作按钮时，适配移动端小屏幕自定义提示：多种拦截提示可选，也可自定义提示内容界面评论管理页面拉黑操作插件配置页面下载去码云下载插件

Yii2 学习笔记
type: item
date: 2024-09-29T11:05:44+00:00
description: Yii 2 开发文档，主要参考 Yii 2.0 权威指南 两双筷子: https://www.dbkuaizi.comYii 2 安装Composercomposer create-project --prefer-dist yiisoft/yii2-app-basic basicdownload下载地址：https://www.yiiframework.com/download修改 config/web.php 文件，给 cookieValidationKey 配置项 添加一个密钥。（使用 Composre 安装自动完成该步骤）'cookieValidationKey' => '在此处输入你的密钥',内置服务器使用 serve 命令启动 php 内置服务器，启动后可通过：http://localhost:8080/ 访问php yii serve::: alert-info默认情况下会使用 8080 端口，如果需要手动指定端口可以 加上 --port 来指定:::php yii serve --port=8888基础目录结构有部分精简，主要列出核心的目录结构 ├── assets // 目录用于存放前端资源包PHP类 ├── codeception.yml ├── commands // 控制器 ├── config // 应用配置及其他配置 │ ├── console.php // 控制台应用配置信息 │ ├── db.php // 数据库配置文件 │ ├── params.php // 参数配置文件 │ ├── test.php │ ├── test_db.php │ └── web.php // Web 应用配置信息 ├── controllers // 用于存放控制器的文件夹 ├── mail // 与邮件相关的布局文件 ├── models // 模型文件夹 ├── runtime // 记录日志和缓存，要求权限 777 ├── tests // 用于存放一些测试类 ├── vendor // composer 包及 Yii 框架核心 ├── views // 视图目录 ├── web // web服务根目录（同public） │ ├── index.php // 入口文件 ├── widgets // 存放一些常用的小挂件的类文件 ├── yii // yii 控制台入口 （同 think 与 artisan ） └── yii.bat // 与 yii 文件 功能一致，运行时可省略开头的 php 框架请求流程用户向入口脚本 web/index.php 发起请求。入口脚本加载应用配置并创建一个应用 实例去处理请求。应用通过请求组件解析请求的 路由。应用创建一个控制器实例去处理请求。控制器创建一个动作实例并针对操作执行过滤器。如果任何一个过滤器返回失败，则动作取消。如果所有过滤器都通过，动作将被执行。动作会加载一个数据模型，或许是来自数据库。动作会渲染一个视图，把数据模型提供给它。渲染结果返回给响应组件。响应组件发送渲染结果给用户浏览器。模块模块是一个独立的软件单元，由 模型、视图、控制器和其他组件组成，终端用户可以访问应用主体中已安装的模块的控制器，模块应该当作小应用主体来看待，和应用主体不同的是，模块不能单独部署，必须属于某个应用主体。可以看作是 ThinkPHP 的多应用模式模块的目录结构modules // 模块目录 └── admin // admin 模块 ├── controllers // 控制器目录 │ └── DefaultController.php // 模块默认控制器 ├── models // 模型目录 ├── Module.php // 模块 └── views // 视图目录 └── default └── index.php模块构建有两种方法，一种是 使用 Gii 工具创建，另一种是手动创建。Gii 创建模块 参考：https://www.cnblogs.com/liuwanqiu/p/6815817.html以 Admin 模块为例创建模块类<?php modules/admin/Module.php namespace app\modules\admin; class Module extends \yii\base\Module { public $controllerNamespace = 'app\modules\admin\controllers'; public function init() { parent::init(); // 在这里可以引入模块自己的配置项 \Yii::configure($this, require __DIR__ . '/config.p…

利用短信模板提取短信变量
type: item
date: 2024-03-10T12:49:00+00:00
description: 前段时间工作中遇到了一个字符串变量提取的场景，通过正则实现了，感觉实现思路蛮有意思的，就分享一下。前言对接过国内短信发送平台的开发者，应该知道大部分短信平台都需要使用 "短信模板"来发送短信。既：在短信服务提供商那边申请一个模板，在发送短信时只需要传递模板中的变量即可。例如短信内容是："您的验证码是：123456，5分钟内有效，请勿泄露"。在短信平台中模板格式是："您的验证码是：${code}，${time}分钟内有效，请勿泄露"我们只需要向短信平台传递 {"code":123456,"time":5} 这样一段请求参数，再由短信平台将参数替换为短信正文，最后发送到用户手机上。需求我们的业务系统本身需要对接多个短信平台，有的短信平台要求直接使用短信模板，有的短信平台要求直接传递短信原文。为了统一短信组件的调用，我们在业务中统一向短信发送组件传递短信原文，再由短信发送组件根据不同短信平台要求，拆出短信变量。利用模板变量尝试通过正则实现这个功能，步骤如下：通过匹配变量标签，提取出模板中所以的 key，拿到一个由 key 组成的一维数组接着将模板中变量标签部分的内容，用正则替换成另一个正则表达式，用这个正则提取短信原文中的值最后将 第一步的数组 key 和第二步的数组 value 组合成一个新的数组，实现短信变量的提取。使用一个相对复杂的例子，实现代码如下：<?php $template = '尊敬的${name}你好，您的订单${on}将于${y}年${m}月${d}过期，请及时通过${pay}续费，支付金额：${money} ${unit}。'; $content = '尊敬的张三你好，您的订单144514将于2023年8月21过期，请及时通过支付宝续费，支付金额：2.34 RMB。'; // {"name":"张三","on":"144514","y":"2023","m":"8","d":"21","pay":"支付宝","money":"2.34","unit":"RMB"} echo json_encode(GetTempParam($template,$content),JSON_UNESCAPED_UNICODE); /** * @param $template 第三方短信平台模板 * @param $content 需要发送的短信原文 * @param $identifier 第三方平台变量标签 * @return array 返回变量数组 */ function GetTempParam($template, $content, $identifier = ['${', '}']) { /** * 模板编写注意：变量不能连续、不可嵌套、若包含正则语法 需要转义(自动) 例如："尊敬的\[$name\]您好" * 模板: 尊敬的${name}您好，您的订单${on}将于${y}年${m}月${d}过期，请及时通过${pay}续费，支付金额：${money} ${unit}。 * 内容：尊敬的张三您好，您的订单114514将于2023年8月21过期，请及时通过支付宝续费，支付金额：2.33 RMB。 * 标签：['${', '}'] */ // 标签转义为 ['\${', '}'] $identifier = array_map('quotemeta', $identifier); // 第一步：使用 /(?<=\${)(.*?)(?=})/ 提取变量 keys ['name','on','y' ...] $pattern = sprintf('/(?<=%s)(.*?)(?=%s)/', $identifier[0], $identifier[1]); preg_match_all($pattern, $template, $temp_keys); // 如果没找到 key 就没有变量 if (empty($temp_keys[1])) return []; /** * 第二步：将模板标签替换为 (.*),结果当作正则匹配内容 * $content_pattern = 尊敬的(.*)你好，您的订单(.*)将于(.*)年(.*)月(.*)过期，请及时通过(.*)续费，支付金额：(.*) (.*)。 * $content_vals[0] = ['张三','114514','2023' ...] ,注意 若 模板不匹配 则为 null */ $content_pattern = preg_replace(sprintf('/%s(.*?)%s/', $identifier[0], $identifier[1]), '(.*)', $template); preg_match_all('/' . $content_patt…

Typecho 多用户二次认证插件
type: item
date: 2023-11-29T13:16:00+00:00
description: 前言出于网站安全性的考虑，想在 Typecho 管理后台登录时加一个基于 OTP 的多因素认证功能，网上找了半天没有合适的扩展，唯一找到的一个扩展还不支持多用户，于是就自己写了一个。依赖要求：Typecho 1.2.0+特点：界面简洁、支持多用户更新记录2026-03-22 (1.2.2)修复 因 Typecho 1.3.0 调整导致校验挂载点失效的问题，该问题升级 login2fa 修复。注：1.2.2 校验挂载点调整，需要更新后重启插件才可生效2025-09-12 （1.2.1）修复因命名空间冲突，导致OTP 配置读取失败，导致验证不通过的问题2024-02-01 （1.2.0）解决所有表单都添加 otp 输入框的bug2024-01-24 （1.0.1）修复 PHP 8.2 + 语法不兼容的错误提示 Deprecated: Using ${var} in strings is deprecated, use {$var} 调整 OTP 动态码 input 框的挂载方式，尽量兼容更多后台模板使用说明插件开启后，在登录页面会多一个动态码输入框，需要输入账户密码时同时输入OTP 动态码(令牌)进行校验，校验失败时会提示相应错误信息。绑定 OTP 密钥在个人设置页面，使用身份验证器扫码或手动绑定，扫描后输入六位动态码进行验证。绑定成功后密钥固定不再变化，未绑定时每次加载个人页面随机生成密钥（若怀疑密钥已泄露不安全，则解绑后重新绑定即可）。otpauth 信息中包含站点名称、当前用户名称，扫码添加后效果如下：解绑 OTP 认证绑定成功后再进入个人设置页面，可以校验六位验证码，可以进行解绑操作。登录认证插件开启后，后台登陆页面会增加一个动态码输入框，已绑定的后台用户在登陆时需要校验 OTP 动态码，未绑定的用户则不会校验，为空正常登录即可。插件安装https://cnb.cool/dbkuaizi/typecho/login2fa插件配置第一个是 OTP 认证码的时间窗口期，主要用于解决客户端和服务器时间戳不一致的问题。第二个为一个防呆设计，因为 Typecho 插件卸载后插件的所有数据都将删除，这就会导致所有人的 OTP 密钥全部丢失，即使重新开启插件也无法恢复，为了防止不小心点到禁用按钮，需要在设置页面改为确认卸载后才允许卸载插件。

PHP 程序执行函数
type: item
date: 2022-09-10T10:03:00+00:00
description: 作为一个服务端语言，PHP不可避免会有在程序中执行外部程序的需求，那么这篇文章就总结一下 PHP 执行外部程序的函数以及需要注意的坑。执行运算符 与 shell_exec()通过 shell 环境执行命令，并且将完整的输出以字符串的方式返回。PHP 执行运算符，使用反引号表示。PHP 会尝试将反引号的内容当做 Shell 命令来执行，并将其输出的信息返回（既，外部程序的执行输出会返回给而不是输出到终端）。执行运算符与函数 shell_exec() 相同（可以理解为是别名的关系）。 需要注意的是，在 php.ini 中如果禁用了 shell_exec() 函数，则执行运算符也会失效。 参数说明shell_exec(string $cmd): string || nullcmd 要执行的命令，与双引号一样的是，执行运算符支持解析变量。返回值外部程序的标准输出，如果执行过程中发生错误或进程不产生输出，则返回 null。 因为不产生输出和发生错误都会返回 null，所以，使用此函数的返回值无法检测外部程序是否执行成功，如果想获取程序执行的退出码，可以使用 exec() 函数的 return_var 参数。 例子<?php // 执行运算符与 shell_exec 效果一样 var_dump(`ls /home`); var_dump(shell_exec('ls /home')); // 执行错误的命令 输出 Null var_dump(shell_exec('ls /homes')); ?>执行后终端输出如下内容：dbkuaizi-alpine:/home# php test/demo.php string(13) "project test " string(13) "project test " ls: /homes: No such file or directory NULLsystem执行外部程序，并显示外部程序的输出结果。如果运行在 PHP 服务器模块中，system() 函数还会尝试在每行输出完毕后，自动刷新 Web 服务器输出的缓存。参数说明system(string $command[, int &$return_var = ?]): stringcommand 要执行的命令return_var 如果提供此参数，则外部命令执行后的状态将会被设置在此变量中。返回值成功则返回命令输出的最后一行，失败则返回 false。 若要判断命令是否成功执行，需要要判断状态码是否为 0，不要通过返回值是否等于 false 来判断执行是否成功。因为实际测试中，执行失败的返回值是空字符串。 例子<?php $a = system('ls /home',$b); var_dump($a,$b); echo '------'.PHP_EOL; $c = system('ll /home',$d); var_dump($c,$d); ?>输出：dbkuaizi-alpine:/home# php test/demo.php project test string(4) "test" int(0) ------ sh: ll: not found string(0) "" int(127)exec执行一个外部程序。参数说明exec(string $command[, array &$output = ?, int &$return_var = ?]): stringcommand 要执行的命令output 如果提供了此参数，那么会用执行命令的输出填充此数组，每行输出填充数组的一个元素。需要注意的是 exec() 函数会在数组末尾追加函数，所以在多次调用的情况下，请在 exec() 函数调用，对数组使用 unset() 进行重置。return_var 命令执行后的返回状态将会被写入到此变量中。返回值例子<?php $a = exec('ls /home',$b,$c); var_dump($a,$b,$c); echo '------'.PHP_EOL; $a = exec('ls /usr',$b,$c); var_dump($a,$b,$c); ?>输出dbkuaizi-alpine:/home# php test/demo.php string(4) "test" array(2) { [0]=> string(7) "project" [1]=> string(4) "test" } int(0) ------ string(5) "share" array(8) { [0]=> string(7) "project" [1]=> string(4) "test" [2]=> string(3) "bin" [3]=> string(3)…

基于 amis 的一些尝试
type: item
date: 2022-08-21T16:52:00+00:00
description: 前言我第一次知道 amis 这个项目应该在 19 年的时候，那会 amis 才开源时间不长，除了百度内部用之外，几乎没有听过那家公司有使用案例，所以也没有做过多关注（即使到了现在各种程序员社交平台上的讨论也绝不算多）。后来在深圳一家公司的工作经历接触到了 LowCode 这个概念，相应的对 amis 也有了一定的关注，但还是因为没有听过太多公司的应用案例，所以也没想过 用在日常的项目开发中。 然后就到了去年（2021年）在深圳找工作面试过程中，通过与面试官交流技术栈，发现有一些公司有使用 amis 去构建自己的一些中后台项目。之后就有了用 Amis 构建一个后台项目的想法。Tpamis在项目的开发初期发现目前没有 基于 PHP 和 Amis 这两个技术栈的开源项目，甚至基于 Amis 的衍生开源项目也很少。于是就打算将写的这个项目开源，即使直接使用我这个开源项目的人不多，也希望可以提供一些借鉴的方案。就这样经过半年的迭代项目的功能也逐渐完善，取名 Tpamis 开源到了 github 和 gitee 上。关于项目的具体信息，可以关注：https://gitee.com/dbkuaizi/tpamis 和 https://github.com/dbkuaizi/tpamis 这里不做赘述。随着 Tpamis 开发的过程，对 amis 的了解也逐渐的加深，用一句话来总结就是: Amis 像是一座功能完善的围城，在城内提供了绝对数量的组件可以满足 80% 甚至 90% 的功能。但 Amis 没有的功能组件，对于后端来说就是无法实现的。(毕竟有专业前端支持的项目，又为什么会选择 Amis 呢)。举几个例子：Tpamis 没有多标签页的功能，并不是我不想做进去，而是通过 jssdk 的开发方式，不支持这个功能。在移动端模式下，个人主页和退出登录按钮是显示在页面菜单上面的， 原因也是因为 amis jssdk 没有提供相应的功能。当你遇到上面的问题时，有两种解决方法，一种是像我一样换一个方式实现这个功能，另一种就是找前端去写 react 的代码，扩充 api 或者扩充组件的功能。amis 的优缺点经过 tpamis 的开发，我把我认为的优点和缺点总结一下，希望可以对你是否选择 amis 提供一些参考。优点足够多的组件支持，按照官方的说法 目前提供了 120+ 组件，如果只是做内部的中后台管理系统，组件绝对是够用的状态。JSON 化构建页面，整个过程中无需前端人员参与，后端开发者通过 JSON 配合 amis 官方文档，即可实现页面。Echarts 支持，由于是百度的中后台项目，所以对 Echarts 支持度很好。有大厂背书，更新频率也不算低，不用担心忽然不维护。缺点组件即围城，正如我上面所说，如果你需要一个组件目前没有支持的功能，那么要么选择 react 自行开发，要么选择通过其他组件来实现这个功能，不过好在组件很多，总有可以替代的方式。JSON 构建页面，JSON 是个双刃剑，既降低页面的开发难度，也限制了页面的可能性，如果你需要在实现一个比较复杂的功能，那么你需要写出很复杂的JSON 来实现它，例如 Tpamis 的登录页面，就是混合了 HTML、JS、CSS、JSON 才实现的效果。社区活跃度极差，在开发的过程中遇到解决不了的问题（组件Bug、文档不完善等），加过官方的交流群、也提过 Issues，但基本上都没有任何回应。也就是说 一旦你开发中遇到了问题，95% 的可能性只能自己解决。细小的 Bug，组件数量达到这个量级，出现 bug 也是在所难免的。但问题在于 amis 开源版的使用者大部分都是纯后端开发者，没有能力去修复这些bug 只能等待官方处理。（这里大家可以去看一下 amis 的 issues，很多 bug 的提问并没有得到官方的答复：https://github.com/baidu/amis/issues ）总结Amis 好用吗？如果你们项目对后台的需求只有增删改查，那么无疑 Amis 的很不错的一个选择，减少了后端开发写页面的负担。我会在工作中选择 Amis 吗？目前为止，我不太建议在工作中使用 Amis 来开发正式项目。毕竟一个没有社区积极的支持，出了问题只能靠自己。我不想在自己负责的项目中引入这些隐患。对于 Amis 还有其他选择吗？ Amis 其实是百度爱速搭项目的一个衍生开源项目，爱速搭毕竟是商业项目，必然有着更好的商业支持，也是一个不错的选择。上面的这些内容只是出于在这个阶段对于 amis 的感受，或许在后期的迭代中，Amis会有更完善的生态体系，让它变成一个不错的选择。

ThinkPHP6 使用 jwt-auth 组件
type: item
date: 2022-07-16T19:20:14+00:00
description: github地址：https://github.com/QThans/jwt-auth安装 jwt 组件composer require thans/tp-jwt-auth生成密钥php think jwt:create自定义中间件<?php declare (strict_types = 1); /** * 登录状态中间件，用来处理jwt的登录状态 */ namespace app\middleware; use thans\jwt\exception\JWTException; use thans\jwt\exception\TokenBlacklistException; use thans\jwt\exception\TokenBlacklistGracePeriodException; use thans\jwt\exception\TokenExpiredException; use thans\jwt\middleware\JWTAuth; use think\exception\HttpException; class Auth extends JWTAuth { /** * 处理请求 * * @param \think\Request $request * @param \Closure $next * @return Response */ public function handle($request, \Closure $next) { try { $payload = $this->auth->auth(); } catch (TokenExpiredException $e) { // 捕获token过期 // 尝试刷新token，会将旧token加入黑名单 try { $this->auth->setRefresh(); $token = $this->auth->refresh(); $payload = $this->auth->auth(false); } catch (TokenBlacklistGracePeriodException $e) { $payload = $this->auth->auth(false); } catch (JWTException $exception) { // 如果捕获到此异常，即代表 refresh 也过期了，用户无法刷新令牌，需要重新登录。 // throw new HttpException(401, $exception->getMessage()); throw new HttpException(401, 'refresh 也过期了'); } } catch (TokenBlacklistGracePeriodException $e) { // 捕获黑名单宽限期 $payload = $this->auth->auth(false); } catch (TokenBlacklistException $e) { // 捕获黑名单，退出登录或者已经自动刷新，当前token就会被拉黑 throw new HttpException(401, '当前用户未登录'); } catch (JWTException $e){ throw new HttpException(401, 'Token不能为空'); } // 可以获取 payload里自定义的字段，比如uid $request->user_id = $payload['uid']->getValue(); // 如果有新的token，则在响应头返回（前端判断一下响应中是否有 token，如果有就直接使用此 token 替换掉本地的 token，以此达到无痛刷新token效果） if (isset($token)) { $response = $next($request); $response = $response->header(['Access-Control-Expose-Headers' => 'Authorization,ExportFileName']); return $this->setAuthentication($response, $token); } return $next($request); } }