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 完成情况
This commit is contained in:
2026-09-28 17:28:21 +08:00
parent 63aec55329
commit 9d7a8ca845
88 changed files with 7564 additions and 32 deletions
@@ -0,0 +1,112 @@
# ═══════════════════════════════════════════════════════════
# 数据信息填报系统 · 主配置
# 环境差异全部放 application-{dev,test,prod}.yml
# ⚠️ 生产密钥一律走环境变量,不写在这里
# ═══════════════════════════════════════════════════════════
server:
port: 8080
# 上传接口是长连接,超时要放宽(学生可能从聊天记录选一个 40MB 的文件)
tomcat:
connection-timeout: 60s
max-swallow-size: 100MB
compression:
enabled: true
mime-types: application/json,text/html,text/css,application/javascript
min-response-size: 2048
spring:
application:
name: xt-server
profiles:
active: dev
lifecycle:
# 优雅停机:等在途请求处理完,避免上传/导出被打断
timeout-per-shutdown-phase: 30s
jackson:
time-zone: Asia/Shanghai
default-property-inclusion: always
deserialization:
fail-on-unknown-properties: false
servlet:
multipart:
max-file-size: 50MB
max-request-size: 60MB
datasource:
driver-class-name: org.postgresql.Driver
hikari:
pool-name: xt-hikari
minimum-idle: 5
maximum-pool-size: 20
connection-timeout: 10000
idle-timeout: 600000
max-lifetime: 1700000
# 慢 SQL 从应用侧也记一份,方便和 PG 的 log_min_duration_statement 对照
leak-detection-threshold: 20000
data:
redis:
timeout: 3s
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 2
max-wait: 3s
flyway:
enabled: true
locations: classpath:db/migration
# 迁移文件一旦发布就不许改,改了校验会失败——这是保护,不是障碍
validate-on-migrate: true
baseline-on-migrate: false
table: flyway_schema_history
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
# 生产环境关掉 SQL 日志,dev 单独开
log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
global-config:
banner: false
db-config:
# 软删除字段名,客户明确要求就叫 deleted
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
id-type: auto
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: when-authorized
health:
redis:
enabled: true
springdoc:
api-docs:
path: /v3/api-docs
swagger-ui:
path: /doc.html
tags-sorter: alpha
operations-sorter: alpha
# ── 本项目业务配置 ────────────────────────────
fill:
# JWT 见《03-接口设计》§1.2:PC 8 小时 / 小程序 7 天
jwt:
issuer: tianbaotong-fill
pc-expire-seconds: 28800
mp-expire-seconds: 604800
# 密钥与 RSA/AES 密钥在 application-{env}.yml 或环境变量中提供
security:
# 登录失败锁定:5 次锁 15 分钟(§1.6)
login-max-fail: 5
login-lock-minutes: 15
# 连续失败几次后要求图形验证码
captcha-after-fails: 2
upload:
# 单文件上限 50MB(PRD §10.1)
max-file-size-mb: 50