lovife
|
1f3c485ceb
|
feat(role,scope)!: 角色与授权可配 + 需求收敛(4 角色 / 10 菜单 / 删两模块)
角色与授权(交接摘要 §一·三十五):
- 权限判定合并两个来源:内置角色按代码(BuiltinRole)、自定义角色按库
(sys_role_permission)。实现放 module 层的 DbRoleGrantSource,security 只依赖
接口 RoleGrantSource —— 否则角色服务要调 permissionChecker,两个包迟早成环
- 权限判定必须加缓存:此前是零 IO,而压测基线是"SQL 条数恒定"。缓存放 Redis,
空结果也缓存(否则已删角色的 token 会把库打穿)、Redis 挂了退化成直读库、TTL 只兜底
- V18 建 sys_role.user_type(账号的 user_type 一直从角色反推,自定义角色不在代码映射里)
与 sys_role_menu;8 个接口 + 错误码 40045;契约 119 -> 129
- 内置角色只读(权限码是前后端契约,做成可改 = 一次误操作就能把管理员锁在系统外);
平台级能力两道防线(界面禁用 + 服务端 40301);防自锁;改完立即生效(写授权清缓存)
- RoleProvisioner 从"已存在就 continue"改成幂等回填 + 每日 03:20 定时任务 ——
修的是"代码改了权限、已开通租户永远停在发放那一刻",这种漂移不报错也没测试会红
- scripts/e2e-m10-role.py 77/77
需求收敛(交接摘要 §一·三十六,本节的数字为准):
- 删副班主任 / 审核员两个内置角色(6 -> 4)、历史整理器(module/cleanup)、
加入申请(module/join + 学生端 pages/join)、collect_task_org.deputy_user_ids 整列;
审核范围三级回退 -> 两级,连带 6 个 mapper 的 LIKE '%,id,%' 分支
- V19 迁移处理存量:先解 sys_user_role 绑定再软删角色;账号只在"除这两个角色外没有
别的角色"时才软删。必须迁而不能只删代码 —— 角色码删掉后 mergePermissions 返回空集,
存量账号会"能登录、处处 40301",不抛异常不打日志
- 契约 129 -> 119、错误码 59 -> 54、权限码 30 -> 28、PC 菜单 12 -> 10、
小程序页 10 -> 9、原型 32 -> 29 屏、E2E 39 -> 37 个(断言 1695 条)
- UserService.list 对不认识的角色码由"静默丢掉过滤条件"改成失败关闭
(之前传个不存在的 roleCode 会返回全校账号,且看起来完全正常)
- 品牌元信息同步改名「深学在线」:pom 名称、/doc.html 标题、配置与启动类注释、
存储自检探针内容;学生端 app 名与全局导航栏标题
|
2026-10-10 16:48:54 +08:00 |
|
lovife
|
b9373d6ebd
|
fix(config): 本地私有配置改用 profile 机制(spring.config.import 优先级不够)
起因:用户把七牛 AK/SK 填进了 application-dev.yml(**会提交的文件**)。
已把密钥挪到 jar 外的 backend/application-local.yml(gitignore),
并把 dev 文件还原成空占位符 —— 确认过待提交差异里没有密钥。
⚠️ 排查中发现一个**沉默的坑**:一开始用
spring.config.import: optional:file:./application-local.yml
加载本地密钥,结果**启动正常、日志不报错、但配的 provider 完全没生效**
(还是本地磁盘)。三步定位:
① 确认 YAML 合法(结构/编码/缩进都对)→ 不是文件问题
② 换 --spring.config.additional-location=file:./... → **生效了**
→ 文件与内容没问题,问题在 import 这条路
③ 把 import 改成**非可选 + 指向不存在的文件** → 启动失败并报
"Config data resource ... does not exist"
→ 证明 import **确实被处理了**,是**优先级**问题
根因:spring.config.import 导入的文件排在 **profile 文件之后(优先级更低)**,
于是 application-dev.yml 里的 storage.provider 把导入文件里的 provider: qiniu
盖掉了 —— 而这一切没有任何报错。
修法:改用标准机制 —— 文件恢复成 profile 名 application-local.yml,
application.yml 里加 spring.profiles.include: local。
profile 文件后加载、优先级更高。启动日志现在打印
The following 2 profiles are active: "local", "dev"
那个坑已写进 application.yml 的注释里,免得后人再踩。
教训:**"配置了但没生效、且不报错"是最难查的一类问题。**
判断某个配置机制到底有没有被执行,有个通用办法:
**把它改成一个必然会失败的值** —— 如果照样跑起来,说明它根本没被处理。
⛔ 仍阻塞:用户填的 AK/SK **验证无效**(七牛 401 bad token)。
两个互相独立的签名实现都失败,所以不是代码问题:
① 七牛官方 SDK 的 Auth.uploadToken(自检走的就是它)
② 手写的管理 API 签名(GET https://rs.qiniu.com/buckets)
线索:AK 40 字符正常、**SK 41 字符**(七牛的 AK/SK 都是 40 字符的 URL 安全 base64);
但逐字符试"去掉首/尾字符"三种组合都被拒 → 不是收尾多一个字符。
需要用户到七牛控制台 → 密钥管理重新完整复制一对。
为不阻塞本地开发,application-local.yml 里 provider 先留 local(密钥保留着,
换成有效的之后去掉一行注释即可)。回归:e2e-m3-file 29/29、m3-submit 31/31。
|
2026-09-29 14:38:37 +08:00 |
|
lovife
|
65ebf8de90
|
feat(storage): 对象存储接七牛云 —— 解除"不能上多副本"
文件以前直接写本地磁盘:两个实例各存各的,学生这次传到 A、下次请求落到 B,
文件就"不见了"。这不是性能问题,是正确性问题 —— 它一直卡着不上多副本。
新增:
· module/file/storage/{StorageService,LocalStorageService,QiniuStorageService}
· config/StorageConfig(装配 + 可选启动自检)
· FillProperties 新增 fill.storage
· MaterialFileController 新增 GET /{id}/url(签名链接)与 GET /{id}/content
· scripts/e2e-m1-storage.py(存储自检,没配七牛时跳过而非失败)
关键设计:
① 业务只认 path(std/1/17/张同学-…-身份证.jpg),各实现自己映射成真实位置:
本地 ./data/uploads/std/...、七牛 xiaotutongxue/std/...
**换存储不用改数据库、不用改业务代码**
② 空间私有 → 下载必须签名。GET /{id}/url 每次现签(默认 30 分钟),
所以这个 URL 绝不能长期保存:写进库、塞进导出的 Excel、发给学生明天再打开,都是 403
③ 对象键里有中文,拼 URL **必须逐段编码** —— 整串 encode 会把 / 编成 %2F,
那在七牛那边是另一个键,表现是"传上去了却打不开",且报错什么都没有
④ 区域默认 auto:华东-浙江在新旧控制台对应 z0 与 cn-east-2,
写错了的症状是**上传超时**——一个从报错完全看不出原因的失败
⑤ AK/SK 不进仓库也不进 jar:dev.yml 只放占位符;
真密钥在 backend/application-local.yml(已 gitignore),
用 spring.config.import: optional:**file**:./application-local.yml
—— 刻意用 file: 而不是 classpath:,后者会被打进 jar,密钥就跟着构建产物走了
⑥ 配了 qiniu 却没密钥 → 启动就炸(fail-fast):起得来的话第一个受害的是
正在填材料的学生,而排查要从"文件保存失败"一路追到环境变量
已验证:存储抽象改造没破坏任何现有链路 —— 本地磁盘路径下
e2e-m3-file 29/29、m3-submit 31/31、m1-materials 68/68、m3-draft 27/27 全绿;
启动日志打出「本地磁盘(多副本部署下会丢文件)」;git check-ignore 确认
application-local.yml 被忽略而 .example 不被忽略。
⏳ 未验证:**七牛的真实链路没跑过** —— 用户给了域名/空间/区域/私有/目录前缀,
但没给 AccessKey/SecretKey,没有它生成不了上传凭证。
补上后跑 python scripts/e2e-m1-storage.py 即可(它会验:配置齐全 →
上传处理落进七牛 → 取签名链接 → 真的下载回来比字节 → 不带签名被拒
→ 对象键带前缀 → 原始件一字不差)。没配时脚本跳过并 exit 0,不误报失败。
|
2026-09-29 14:09:13 +08:00 |
|
lovife
|
9d7a8ca845
|
feat(m0): 后端骨架 + 账号体系(M0 验收 36/36 通过)
技术栈(已在 4.1.1 上实测跑通,非纸面选型)
Java 21 + Spring Boot 4.1.1 + MyBatis-Plus 3.5.17(boot4) + PostgreSQL 16.15
+ Redis 7 + Flyway 12.4 + springdoc 3.1.1;前端定 Vue 3 + Element Plus + uni-app
交付内容
M0-01 基础设施 deploy/docker-compose.yml(PG + Redis + MinIO,容器名与端口避让本机既有项目)
M0-02 后端骨架 xt-common + xt-server;统一响应 / 全局异常 / 58 个错误码 /
多租户拦截 / 软删除 / 字段自动填充 / AES-GCM / HMAC 索引 / RSA / JWT
M0-05 租户 开通租户(一次完成建租户 + 发 6 个内置角色 + 建首个管理员)、列表、详情
M0-06 组织 CRUD + 组织树 + ancestor_path 自动维护 + 占用校验
M0-07 PC 登录 RSA 解密 + 图形验证码 + 失败锁定 + 等时比较防账号枚举 + 审计日志
M0-08 登出/当前用户 Token 黑名单(存 SHA-256,TTL 到自然过期)
M0-01 数据库 Flyway 2 个迁移 → 26 张表(20 业务 + 5 标准 + 1 迁移记录)
安全姿态
· 租户拦截器【默认拒绝】:匿名请求触及租户表直接抛 40101,不做「不加条件放行」
· 权限码定义在代码里(PermissionDef / BuiltinRole),不建 sys_permission 表,杜绝前后端漂移
· 匿名接口只靠 @PublicApi 放行,不做路径白名单,避免两套机制各说各话
· 账号不存在时也跑一次 BCrypt(假哈希),拉平响应时间防枚举
· 登录密码 RSA 传输,防止网关/APM 把明文密码记进日志
脚本
scripts/sync-ddl.py 文档 DDL → Flyway 迁移,带 --check 防漂移
scripts/e2e-m0.py M0 验收 36 项,可进 CI
scripts/verify-docs.py 文档一致性(接口数【逐节】核对、错误码、跨文档数字、表格列数)
scripts/dev-login.py RSA 加密登录助手
scripts/dev-clean-e2e.py 清理 E2E 测试数据
文档修正(实测暴露,非人工审查)
· DDL 缺陷:sys_tenant.contact_phone / sys_user.phone / collect_roster.phone
原 varchar(20) 按明文长度建,装不下 AES-GCM 密文(约 52 字符),改 varchar(128)(PRD V2.4)
· 错误码 40007b/40007c 带字母后缀与「code 为数字」的约定冲突,规范化为 40071/40072
· 07-开发实施计划 新增 §9 版本兼容踩坑记录(10 条实录)与 §10 M0 完成情况
|
2026-09-28 17:28:21 +08:00 |
|