域 1: 认证授权
角色:开发者 / 平台管理员 / API 运营
前置条件:用户已在系统中完成注册,账号处于正常(未锁定/未冻结)状态;系统认证服务正常运行。
后置条件:认证成功则发放 JWT Token 并建立会话;认证失败则返回具体错误原因且不创建任何会话。
操作步骤:
- 用户提供用户名和密码,客户端向
/oauth/token 发送 POST 请求(grant_type=password)。
- 网关 AuthFilter 截获请求,验证基本参数完整性。
- 请求转发至
FopAuthService.authenticate() 进行认证逻辑处理。
- 系统根据 username 查询 DEVELOPER 表获取用户信息。
- 使用 BCrypt(当前为 MD5,待升级)比对密码哈希值。
- 密码匹配成功 → 生成 JWT Token(含userId/roles/exp),存入 Redis TokenStore,返回给客户端。
- 密码不匹配 → 返回错误码 AUTH_001(密码错误),记录一次失败尝试。
- 连续失败达 5 次 → 触发账号临时锁定(15分钟)。
业务规则:
- BR-1.1.1:密码必须经过 BCrypt 哈希比对 — 证据:A ShiroAuthorizingRealm.doGetAuthenticationInfo()
- BR-1.1.2:连续认证失败 5 次触发账户锁定 — 证据:A FopAuthServiceImpl.authenticate()
- BR-1.1.3:JWT Token 有效期 3600 秒(1小时),RefreshToken 有效期 7 天 — 证据:A TokenService.generateToken()
- BR-1.1.4:同一账号仅允许一个有效 Token(互踢机制)— 证据:B Redis TokenStore.set()
输入:username(用户名, 字符串, 必填), password(密码, 字符串, 必填), grant_type(固定值 "password"), client_id(客户端标识, 必填)
输出:access_token(JWT字符串), token_type("Bearer"), expires_in(3600), refresh_token(刷新令牌字符串), scope(授权范围)
异常处理:
- 用户不存在 → 返回 401 + 错误码 AUTH_002(用户未注册)
- 密码错误 → 返回 401 + 错误码 AUTH_001(凭证无效)+ 剩余重试次数
- 账户锁定 → 返回 403 + 错误码 AUTH_003(账户已锁定)+ 解锁时间
- 认证服务异常 → 返回 500 + 错误码 SYS_001(系统繁忙,请稍后再试)
角色:所有已认证用户(自动触发)
前置条件:用户持有有效的 RefreshToken(尚未过期);RefreshToken 未被主动撤销。
后置条件:颁发新的 AccessToken 和 RefreshToken 对;旧的 AccessToken 立即失效。
操作步骤:
- 客户端携带 RefreshToken 向
/oauth/token 发送 POST(grant_type=refresh_token)。
- 服务端从 Redis 中读取 RefreshToken 记录,验证有效性。
- 验证通过 → 作废旧 AccessToken,签发新 Token 对。
- 刷新计数累加(用于异常行为检测)。
业务规则:
- BR-1.2.1:RefreshToken 只能使用一次(单次有效)— 证据:A FopAuthService.refreshToken()
- BR-1.2.2:24小时内超过 20 次刷新 → 触发安全告警 — 证据:B SecurityMonitor.checkAbnormal()
输入:grant_type("refresh_token"), refresh_token(当前有效的刷新令牌), client_id
输出:新 access_token, 新 refresh_token, expires_in
异常处理:RefreshToken 过期/无效 → 401 + 提示重新登录
角色:所有已登录用户
前置条件:用户持有有效的 AccessToken。
后置条件:AccessToken 和 RefreshToken 均被标记为已撤销;Shiro Session(如果存在)被清除。
操作步骤:
- 客户端调用 POST
/oauth/revoke 携带当前 Token。
- 服务端将 Token 加入 Redis 黑名单(TTL = Token 剩余有效期)。
- 清理关联的 Shiro Session 数据(如有)。
- 记录登出审计日志。
业务规则:
- BR-1.3.1:登出后在黑名单 TTL 内该 Token 无法恢复使用 — 证据:B FopAuthService.logout()
输入:token(要撤销的令牌)
输出:result_code, message
角色:第三方应用(自动化调用)
前置条件:第三方应用已获得 AppKey/AppSecret 且应用状态为"已上线";已订阅目标 API。
后置条件:签名验证通过则放行请求至目标服务;验证失败则拒绝并记录安全告警。
操作步骤:
- 第三方应用在 HTTP Header 中携带 X-App-Key、X-Timestamp、X-Signature 三个参数。
- 网关 SignUtil 从 Header 提取上述参数。
- 根据 AppKey 从缓存/DB 查询对应 AppSecret。
- 对请求体 + Timestamp + AppSecret 做 HMAC-SHA256 签名计算。
- 对比计算的签名与 X-Signature 是否一致。
- 检查 Timestamp 与服务器时间差不超过 ±5 分钟(防重放攻击)。
- 全部通过 → 放行;任一失败 → 返回 401/403。
业务规则:
- BR-1.4.1:签名算法采用 HMAC-SHA256 — 证据:A SignUtil.verifySignature()
- BR-1.4.2:时间窗口容差 ±300 秒(5分钟)— 证据:B GatewayAuthFilter.checkTimestamp()
- BR-1.4.3:连续签名失败 10 次/分钟 → 触发 IP 级限流 — 证据:B RateLimitFilter.handleAbnormal()
输入:X-App-Key(Header), X-Timestamp(Header, Unix秒级), X-Signature(Header), Request Body
输出:签名通过 → 正常转发请求;签名失败 → 401 Unauthorized + error_code SIGN_001/SIGN_002/SIGN_003
角色:所有已认证用户发起的资源访问
前置条件:用户已完成身份认证(持有有效 JWT);目标资源定义了权限要求。
后置条件:有权限则允许访问资源;无权限则返回 403 Forbidden。
操作步骤:
- 网关/框架拦截器提取 JWT 中的 roles/permissions claims。
- 对照目标资源的权限注解(@RequiresPermissions / @PreAuthorize)。
- 匹配 → 放行;不匹配 → 返回 403。
业务规则:
- BR-1.5.1:权限模型为 RBAC(用户→角色→权限三级)— 证据:A ShiroAuthorizingRealm.doGetAuthorizationInfo()
- BR-1.5.2:超级管理员拥有全部权限(硬编码 role=admin)— 证据:B PermissionService.hasAllPermission()
角色:忘记密码的开发者
前置条件:用户账号存在且绑定了邮箱或手机号。
后置条件:用户可通过验证链接/短信验证码设置新密码。
操作步骤:
- 用户在登录页面点击"忘记密码",输入注册邮箱或手机号。
- 系统验证账号存在性,生成密码重置 Token(有效期 30 分钟)。
- 发送含重置链接的邮件或含验证码的短信。
- 用户点击链接/输入验证码,设置新密码。
- 密码符合强度规则(8-20位、含大小写字母和数字)→ 更新密码并作废重置 Token。
业务规则:
- BR-1.6.1:重置链接/验证码 30 分钟内有效 — 证据:C PasswordResetService.sendResetToken()
- BR-1.6.2:新密码不能与前 5 次使用的密码重复 — 证据:C PasswordHistoryDAO.checkReuse()
输入:email 或 phone(二选一)
输出:result_code(000000=邮件/短信已发送), message
域 2: 应用管理
角色:开发者
前置条件:开发者已登录且实名认证通过;开发者账号状态正常。
后置条件:APP_INFO 表新增一条记录(status=0 草稿态);系统分配唯一 AppCode 和 AppKey/AppSecret 对。
操作步骤:
- 开发者在门户填写应用基本信息(名称/类型/描述/回调地址)。
- 前端调用 POST
/api/apps 提交到网关。
- 网关转发 Dubbo RPC 至
FopPlatformService.createApp()。
- Service 层校验:应用名长度 2~50 字符、callbackUrl 格式合法、同名应用数 < 20。
- 校验通过 → 插入 APP_INFO 表(status=0 草稿)。
- 生成 AppCode(唯一编码)、AppKey、AppSecret(BCrypt 加密存储)。
- 返回应用 ID、AppKey、AppSecret 给调用方。
注意:AppSecret 仅在创建时返回一次
业务规则:
- BR-2.1.1:每个开发者最多创建 20 个应用 — 证据:A FopPlatformServiceImpl.createApp()
- BR-2.1.2:应用名称在全局范围内唯一 — 证据:B AppInfoDao.selectByName()
- BR-2.1.3:AppSecret 使用 BCrypt 单向 Hash 存储,不可逆 — 证据:A EncryptUtil.hashSecret()
- BR-2.1.4:新建应用默认状态为"草稿"(status=0),不能调用任何 API — 证据:B AppStatusEnum.DRAFT
输入:appName, appType(1自建/2第三方), callbackUrl, description, iconUrl(可选)
输出:appId, appCode, appKey, appSecret(仅此一次可见)
异常处理:
- 名称重复 → 409 CONFLICT + APP_001(应用名称已存在)
- 超限 → 403 FORBIDDEN + APP_002(已达最大应用数限制)
- 回调URL非法 → 400 BAD_REQUEST + APP_003(回调地址格式不合法)
角色:开发者
前置条件:应用处于"草稿"状态;必填信息(名称/类型/回调地址)已完整填写。
后置条件:应用状态变更为"待审核"(status=1);APPLY_RECORD 表新增一条审核申请记录;RabbitMQ 发送审核事件消息。
操作步骤:
- 开发者点击"提交审核"按钮。
- 前端调用 POST
/api/apps/{id}/submit。
- Service 校验当前状态必须是"草稿",否则拒绝。
- 更新 APP_INFO.status = 1(待审核)。
- 插入 APPLY_RECORD(type=应用创建审核, status=0 待审)。
- 发送 MQ 消息通知 OA/BPM 系统。
业务规则:
- BR-2.2.1:只有"草稿"状态的应用才能提交审核 — 证据:A FopPlatformServiceImpl.submitForAudit()
- BR-2.2.2:审核事件必须可靠投递 MQ(至少一次语义)— 证据:B RabbitTemplate.convertAndSend()
输入:id(应用ID, 路径参数)
输出:result_code, applyId(审核申请单ID)
角色:平台管理员
前置条件:管理员具有"应用审核"权限;存在"待审核"状态的申请单。
后置条件:审核通过 → 应用状态变为"已上线"(status=2);审核驳回 → 应用回退到"草稿"(status=0),附带驳回理由。
操作步骤:
- 管理员在 OA/后台查看待审核列表,选择一条申请。
- 查看应用详情(名称/类型/回调地址/开发者资质)。
- 做出"通过"或"驳回"决定,驳回时填写意见。
- 调用
FopPlatformService.auditApp(applyId, opinion, result)。
- 更新 APPLY_RECORD.status 和 APP_INFO.status。
- 发送 MQ 消息通知开发者审核结果。
业务规则:
- BR-2.3.1:驳回时必须填写驳回理由(至少 10 个字符)— 证据:B AuditValidator.validateRejectReason()
- BR-2.3.2:审核操作记录不可删除/修改 — 证据:B APPLY_RECORD 审计表只追加不更新
输入:applyId, auditOpinion, auditResult(pass/reject)
输出:newStatus, message
角色:开发者(应用所有者)/ 平台管理员
前置条件:操作者是应用的所有者或有"应用管理"权限的管理员;应用处于非"已注销"状态。
后置条件:新的 AppKey/AppSecret 对立即生效;旧密钥对立即失效(无法再用于签名验证)。
操作步骤:
- 操作者在应用详情页点击"重置密钥"。
- 二次确认弹窗(防止误操作)。
- 调用 POST
/api/apps/{id}/reset-key。
- 系统生成新密钥对,替换数据库中的加密存储。
- 清除 Redis 中的旧密钥缓存。
- 仅这一次返回新 Secret 明文(前端需提醒用户立即保存)。
业务规则:
- BR-2.4.1:新密钥立即生效,旧密钥立即失效 — 证据:A AppKeyGenerator.resetKey()
- BR-2.4.2:AppSecret 明文只在重置响应中出现一次,DB中永远只存Hash — 证据:A EncryptUtil.hashSecret()
- BR-2.4.3:30天内最多重置 3 次 — 证据:C RateLimiter.checkResetFreq()
输出:newAppKey, newAppSecret(仅此一次)
角色:平台管理员
前置条件:管理员有"应用管理"权限;应用当前状态不是"已注销"。
后置条件:冻结 → 应用状态变为"冻结"(status=3),所有 API 调用被拦截返回 403;解冻 → 恢复到之前的状态(通常为"已上线")。
业务规则:
- BR-2.5.1:冻结操作必须填写原因 — 证据:B FreezeValidator.requireReason()
- BR-2.5.2:冻结期间该应用的所有 API 调用均返回 403 + APP_FROZEN — 证据:B GatewayAuthFilter.checkAppStatus()
角色:开发者(应用所有者)/ 平台管理员
前置条件:应用无正在运行的订阅关系(或强制解除)。
后置条件:应用状态变为"已注销"(status=4);AppKey/AppSecret 失效;保留历史数据和日志(软删除)。
业务规则:
- BR-2.6.1:注销为软删除,数据保留 180 天后才物理清理 — 证据:C SoftDeletePolicy.RETENTION_DAYS
角色:开发者(应用所有者)
前置条件:应用处于"草稿"或"已上线"状态。
后置条件:指定字段更新为新值;更新时间戳刷新。
业务规则:
- BR-2.7.1:"已上线"状态下修改名称/回调地址不需要重新审核(策略可配)— 证据:C AppConfig.reAuditOnChange
角色:开发者(看自己的)/ 管理员(看全部)
操作:支持按状态筛选、关键词搜索、分页查询。
域 3: API 管理
角色:API 运营人员 / 平台管理员
前置条件:操作者有"API管理"权限;API 路径在全局范围内唯一(同 HTTP Method)。
后置条件:API_INFO 表新增记录;初始版本 v1.0.0 创建;API 处于"draft"状态,尚不可被订阅。
操作步骤:
- 运营人员在后台填写 API 元数据(名称/路径/方法/分类/描述/请求参数/响应格式)。
- 调用 POST
/api/apis。
- Service 校验路径唯一性(api_path + http_method 组合唯一)。
- 插入 API_INFO 表。
- 后续调用 Publish 接口使其进入"published"状态。
业务规则:
- BR-3.1.1:api_path + http_method 组合必须全局唯一 — 证据:A ApiInfoDao.selectByPathAndMethod()
- BR-3.1.2:新建 API 默认为 draft 状态,需要显式发布才可被订阅 — 证据:B ApiStatusEnum.DRAFT
角色:API 运营
前置条件:API 处于 draft 或 deprecated 状态;版本号遵循 SemVer 规范。
后置条件:API 状态变为"published";开发者可在 API市场中看到并订阅此 API。
业务规则:
- BR-3.2.1:版本号必须递增(v1.0 → v1.1 或 v2.0)— 证据:B VersionValidator.checkIncrement()
- BR-3.2.2:发布操作记录审计日志 — 证据:B AuditLogger.logPublish()
角色:开发者(代表其应用)
前置条件:开发者有已上线的应用;目标 API 已发布且可被订阅。
后置条件:API_SUBSCRIPTION 表新增订阅关系(status=0 待生效或直接生效);网关路由规则中添加该应用的访问白名单。
业务规则:
- BR-3.3.1:每个应用对每个 API 最多只能有一个有效订阅 — 证据:B SubscriptionDao.checkUnique()
- BR-3.3.2:订阅时可申请 QPS 和日调用量上限,需运营审批 — 证据:B FopPlatformService.subscribeApi()
角色:API 运营
前置条件:API 当前状态为 published。
后置条件:API 状态变为 deprecated;已有订阅者收到通知;推荐替代 API(如果有)。
域 4: 开发者管理
角色:新用户
前置条件:提供有效的邮箱和手机号。
后置条件:DEVELOPER 表新增记录(cert_status=0 未认证);发送邮件验证链接。
操作步骤:
- 用户在注册页面填写用户名、密码、邮箱、手机号、企业名称。
- 前端调用 POST
/dev/register。
- Service 校验用户名唯一、邮箱格式、密码强度。
- 密码 BCrypt 加密后写入 DB。
- 发送验证邮件(链接 24 小时内有效)。
- 返回 developerId 和"请查收验证邮件"提示。
业务规则:
- BR-4.1.1:用户名全局唯一,4-30字符,字母数字 — 证据:B RegisterValidator.validateUsername()
- BR-4.1.2:密码强度:8-20位,必须含大小写字母和数字 — 证据:B PasswordStrengthChecker.validate()
- BR-4.1.3:邮箱验证通过后才能使用其他功能 — 证据:C EmailVerificationService.requiredVerified()
输入:username, password, email, phone, company(可选)
输出:developerId, result_code, message
角色:已注册但未认证的开发者
前置条件:开发者已登录;邮箱验证已通过。
后置条件:提交认证申请等待人工/自动审核。
操作步骤:
- 用户进入"实名认证"页面,填写真实姓名和身份证号。
- 上传身份证正面/背面照片(存储至 FastDFS)。
- 调用 POST
/dev/certify。
- 系统校验身份证号格式(18位校验码校验)。
- 提交审核申请(可接入第三方 OCR/人脸核身服务)。
业务规则:
- BR-4.2.1:身份证号必须通过校验码算法验证 — 证据:B IdCardValidator.validateChecksum()
- BR-4.2.2:证件照存储至 FastDFS,DB 只存 URL — 证据:C FileStorageService.upload()
域 5: 监控统计
角色:开发者 / 平台管理员 / API 运营
前置条件:系统中已有调用日志数据(CALL_LOG 表或 ES 索引)。
后置条件:返回指定时间范围内的聚合统计数据。
操作步骤:
- 用户选择时间范围(默认近 7 天),可选筛选特定应用。
- 前端调用 GET
/stats/calls/overview。
- 后端从 ES 聚合查询 CALL_LOG 索引。
- 计算 totalCalls / successRate / avgResponseTime / errorCount / p99。
业务规则:
- BR-5.1.1:统计数据允许 5 分钟延迟(ES 近实时索引)— 证据:C ElasticsearchConfig.refreshInterval
输入:dateRange(格式: 2026-01-01~2026-06-13), appId(可选)
输出:totalCalls, successRate(%), avgResponseTime(ms), errorCount, p99Latency(ms)
域 6: 日志审计
角色:平台管理员 / 审计员
前置条件:操作者有"审计日志查看"权限。
后置条件:返回符合条件的审计日志分页列表。
业务规则:
- BR-6.1.1:审计日志不可被普通用户删除或修改 — 证据:B AuditLogDAO.deleteRestricted()
- BR-6.1.2:日志保留期限至少 180 天 — 证据:C RetentionPolicy.AUDIT_LOG_DAYS
域 7: 系统管理
非功能性需求 (NFR)
| ID | 需求项 | 具体描述 | 优先级 | 验证方式 |
| NFR-1 | 性能 - API 响应时间 | P95 延迟 < 200ms(不含外部调用);P99 延迟 < 500ms | P0 | JMeter 压测 + Prometheus 监控 |
| NFR-2 | 性能 - 并发能力 | 网关支持 1000 QPS 认证请求;平台服务支持 500 TPS 写操作 | P0 | JMeter 压测 |
| NFR-3 | 可用性 | 核心链路可用性 >= 99.9%(月度);计划内维护窗口提前 48 小时通知 | P0 | Grafana 告警 + SLA 报表 |
| NFR-4 | 安全性 | 传输层 TLS 1.2+;密码 BCrypt 存储;敏感字段 AES 加密;SQL注入防护;XSS 防护 | P0 | OWASP ZAP 扫描 + SonarQube |
| NFR-5 | 可观测性 | 全链路 TraceId 传递;关键操作审计日志;Prometheus + Grafana 监控大盘 | P1 | 日志检索验证 + 监控面板验收 |
| NFR-6 | 可维护性 | 核心模块单元测试覆盖率 >= 60%;代码规范通过 SonarQube Quality Gate | P1 | Jacoco 覆盖率报告 + SonarQube 门禁 |
| NFR-7 | 数据表结构不变 | 重构过程中所有现有数据库表结构保持不变;新增字段使用 ALTER TABLE ADD COLUMN;禁止 DROP/RENAME 已有列 | P0 | SchemaDiff 工具对比前后 DDL |
NFR-7 约束声明:本次重构的核心原则之一是"数据表结构不变"。所有重构工作在现有数据模型之上进行:
- 不允许修改已有列的类型、约束或删除列
- 如需扩展,只能新增列(设置合理的默认值和 NULL 策略)
- 视图(View)、存储 Procedure、Trigger 不作为依赖项
- MyBatis XML 映射可以调整以适配新的 Java 对象结构,但底层 SQL 不能改变表的物理结构
状态机定义
应用生命周期状态机
stateDiagram-v2
[*] --> Draft : createApp()
Draft --> PendingReview : submit()
PendingReview --> Online : approve()
PendingReview --> Draft : reject(reason)
Online --> Frozen : adminFreeze(reason)
Frozen --> Online : adminUnfreeze()
Draft --> Withdrawn : withdraw()
Online --> Withdrawn : withdraw()
Withdrawn --> [*]
note right of Draft : 可编辑\n不可调用API\n不可被订阅
note right of PendingReview : 只读\n等待审批
note right of Online : 运行态\n可调用已订阅API
note right of Frozen : API调用拦截\n需管理员解冻
note right of Withdrawn : 密钥失效\n数据软删保留180天
API 生命周期状态机
stateDiagram-v2
[*] --> Draft : createApi()
Draft --> Published : publish(version)
Published --> Deprecated : deprecate(reason)
Deprecated --> Archived : archive()
Archived --> [*]
Draft --> [*] : delete()
note right of Draft : 编辑中\n不对开发者可见
note right of Published : 可被订阅\n可被调用
note right of Deprecated : 不接受新订阅\n已有订阅仍可用
数据模型与一致性声明
核心实体关系 (ER)
erDiagram
DEVELOPER {
bigint developer_id PK
varchar username
varchar real_name
varchar email
int cert_status
}
APP_INFO {
bigint app_id PK
bigint developer_id FK
varchar app_name
varchar app_code
varchar app_key
varchar app_secret
int status
}
API_INFO {
bigint api_id PK
varchar api_name
varchar api_path
varchar http_method
int status
}
API_SUBSCRIPTION {
bigint sub_id PK
bigint app_id FK
bigint api_id FK
int qps_limit
int status
}
DEVELOPER ||--o{ APP_INFO : "1:N owns"
APP_INFO ||--o{ API_SUBSCRIPTION : "1:N subscribes"
API_INFO ||--o{ API_SUBSCRIPTION : "1:N subscribed_by"
数据模型一致性声明 (NFR-7):
本需求文档中描述的所有数据实体及其字段与当前生产环境数据库完全一致。重构过程中不会对以下表做结构性变更:
- DEVELOPER(开发者表)
- APP_INFO(应用信息表)
- API_INFO(API 信息表)
- APPLY_RECORD(申请记录表)
- API_SUBSCRIPTION(订阅关系表)
- CALL_LOG(调用日志表/ES Index)