架构设计

通过三张图理解脚手架的整体拓扑、登录认证流程与权限模型

整体架构拓扑图

多端单仓 · 共享基础设施 · Docker 编排,三端独立部署共享同一套后端

客户端Nginx 反代应用层基础设施web-network (Docker)浏览器管理后台微信小程序浏览器Web 端nginx-admin:18114nginx-api:18113nginx-web:18115ThinkPHP 8PHP-FPM 8.3MySQL 8.0common-mysql:13306Redis 7common-redis:16379fastcgiPDOCache客户端Nginx应用层基础设施Docker 网络

📋 部署要点

  • 三端独立部署:管理后台、API、Web 各一个 Nginx 容器,端口分离(18113/18114/18115),互不影响
  • 共享基础设施:MySQL 与 Redis 由 docker/docker-sites/common 独立部署,多项目共享
  • web-network:所有容器接入同一 Docker 网络,通过容器名互访(如 common-mysql
  • 源码 bind mount../backend:/var/www/app../web/dist:/usr/share/nginx/html 挂载,无需构建镜像

登录认证时序图

JWT 多密钥 + 设备指纹 + 401 刷新队列,完整的鉴权链路

客户端AuthMiddlewareJwtServiceCacheServiceUserInfoService1. GET /user/my-infoAuthorization + device-id 头2. verifyToken(token)按 key_ver 选密钥3. { user_id, key_ver, login_type }expired → 4002 / 401 触发刷新4. getDeviceKey(prefix, userId, tokenId)5. deviceId不匹配 → 4003 DEVICE_VERIFY_FAILED6. getUserInfoByUserId(userId)7. userInfo (含 active_permission_codes)8. { code: 1, data: userInfo }401 刷新分支① 并发 401 入队 requestQueue② 首个请求触发 refreshToken()③ JwtService 验证 refresh token④ CacheService 更新 access_token 缓存⑤ flush 队列,重发原请求(_retry=true)请求响应
401 刷新队列:单例锁 isRefreshing + requestQueue,并发 401 合并为一次刷新,避免重复请求。管理后台(axios)与小程序(uni.request)均实现此机制。

📄 关键文件

中间件backend/app/middleware/AuthMiddleware.php
JWT 服务backend/app/common/service/JwtService.php
缓存服务backend/app/common/service/CacheService.php
前端刷新队列wechat-miniprogram/api/request.js

权限模型 ER 图

RBAC 四要素 + 部门隔离 + 直接授权,最终权限 = 角色权限 ∪ 直接授权

system_userid (PK)real_namephonepassword / saltopen_idstatussystem_departmentid (PK)parent_idnameleader_iddeputy_director_idsortsystem_roleid (PK)codenameis_defaultstatussystem_permissionid (PK)parent_idcodenametype (1菜单/2按钮/3接口/4隐藏)can_direct_assignstatususer_department_roleuser_id (FK)department_id (FK)role_id (FK) + is_defaultrole_permissionrole_id (FK)permission_id (FK)user_permissionuser_id (FK)permission_id (FK)直接授权(绕过角色)NNNNNN直接授权 1:N1:N主实体关联表直接授权表角色权限路径直接授权路径

🔐 权限计算公式

  • 最终权限 = 角色权限 ∪ 直接授权权限
  • PermissionMiddlewarein_array($permissionCode, $activePermissionCodes) 校验
  • 权限码缓存键 user:perm_codes:{userId},1 小时过期,角色/直接权限变更时主动清理
  • 直接授权仅限 can_direct_assign = 1 的权限(如 feedback:view / feedback:delete)
  • 登录时按 is_default = 1 的部门-角色构建 active_* 字段,可通过切换部门变更
关键文件backend/app/common/traits/HasPermissions.php