盘古用户平台 - 应用管理模块技术方案
| 文档信息 |
内容 |
| 文档版本 |
V1.0 |
| 项目名称 |
盘古用户平台(Pangu User Platform) |
| 模块名称 |
应用管理模块 |
| 编写团队 |
湖北新华业务中台研发团队 |
| 创建日期 |
2026-01-31 |
| 审核状态 |
待评审 |
修订记录
| 版本 |
日期 |
修订人 |
修订内容 |
| V1.0 |
2026-01-31 |
湖北新华业务中台研发团队 |
初稿 |
目录
- 模块概述
- 需求分析
- 系统设计
- 前端技术方案
- 后端技术方案
- 数据库设计
- 接口设计
- 开发计划
- 测试方案
- 风险评估
1. 模块概述
1.1 模块定位
应用管理模块是盘古用户平台的核心模块之一,负责管理接入平台的第三方应用,控制API接口访问权限。通过该模块,超级管理员可以创建应用、分配接口权限、管理应用密钥,实现对外开放API的安全访问控制。
1.2 核心价值
| 价值点 |
描述 |
| 统一接入管理 |
对所有第三方应用进行统一注册和管理 |
| 细粒度权限控制 |
精确控制每个应用可访问的API接口 |
| 安全认证机制 |
通过AppId+AppSecret签名机制保障接口安全 |
| 密钥生命周期管理 |
支持密钥重置,确保安全风险可控 |
1.3 模块边界
┌─────────────────────────────────────────────────────────────┐
│ 应用管理模块 │
├─────────────────────────────────────────────────────────────┤
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ 应用CRUD │ │ 接口授权 │ │ 密钥管理 │ │
│ │ - 新增应用 │ │ - 接口勾选 │ │ - 自动生成 │ │
│ │ - 编辑应用 │ │ - 权限存储 │ │ - 重置密钥 │ │
│ │ - 删除应用 │ │ - 权限校验 │ │ - 安全提示 │ │
│ │ - 查询列表 │ │ │ │ │ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ 开放API网关 │ │
│ │ - 签名验证 │ │
│ │ - 权限校验 │ │
│ │ - 请求转发 │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────┘
2. 需求分析
2.1 功能需求
2.1.1 功能清单
| 功能编号 |
功能名称 |
功能描述 |
优先级 |
状态 |
| APP-001 |
应用列表查询 |
按应用名称、编码、状态筛选查询 |
P0 |
待开发 |
| APP-002 |
新增应用 |
创建新应用,自动生成应用编码和密钥 |
P0 |
待开发 |
| APP-003 |
编辑应用 |
修改应用信息和接口授权 |
P0 |
待开发 |
| APP-004 |
删除应用 |
删除应用及其授权信息 |
P1 |
待开发 |
| APP-005 |
重置密钥 |
重新生成应用密钥 |
P0 |
待开发 |
| APP-006 |
接口授权 |
配置应用可访问的API接口 |
P0 |
待开发 |
| APP-007 |
禁用/启用应用 |
控制应用访问权限 |
P0 |
待开发 |
2.1.2 业务规则
| 规则编号 |
规则描述 |
验证方式 |
| APP-R01 |
应用编码由系统自动生成,格式:YY + 6位数字序号 |
后端生成 |
| APP-R02 |
应用密钥由系统自动生成,32位随机字符串 |
后端生成 |
| APP-R03 |
重置密钥后,旧密钥立即失效 |
后端处理 |
| APP-R04 |
重置密钥后需弹窗显示新密钥并提供复制功能 |
前端展示 |
| APP-R05 |
删除应用需popconfirm二次确认 |
前端交互 |
| APP-R06 |
禁用应用后,该应用无法调用任何API接口 |
后端拦截 |
| APP-R07 |
接口授权采用勾选方式,可多选 |
前端交互 |
| APP-R08 |
应用名称不能重复 |
后端校验 |
2.2 非功能需求
| 需求类型 |
需求描述 |
指标 |
| 性能 |
应用列表查询响应时间 |
≤ 500ms |
| 性能 |
接口授权校验响应时间 |
≤ 50ms |
| 安全 |
密钥加密存储 |
AES加密 |
| 安全 |
接口签名验证 |
MD5签名 |
| 安全 |
防重放攻击 |
时间戳校验(5分钟内有效) |
| 可用性 |
权限变更实时生效 |
无需重启服务 |
2.3 用户角色
| 角色 |
权限范围 |
| 超级管理员 |
可查看所有应用,可新增/编辑/删除应用,可进行接口授权,可重置密钥 |
| 分公司用户 |
无权限 |
| 学校用户 |
无权限 |
3. 系统设计
3.1 整体架构
┌─────────────────────────────────────────────────────────────────────┐
│ 前端层 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 应用管理页面 (Vue 3 + Element Plus) │ │
│ │ - 列表页 (index.vue) │ │
│ │ - 新增/编辑弹窗 (AppDialog.vue) │ │
│ │ - 密钥展示弹窗 (SecretDialog.vue) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└────────────────────────────────┬────────────────────────────────────┘
│ HTTP/HTTPS
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 后端服务层 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 应用管理服务 (Spring Boot) │ │
│ │ - ApplicationController │ │
│ │ - ApplicationService │ │
│ │ - ApplicationMapper │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 开放API网关 │ │
│ │ - ApiAuthInterceptor (签名验证) │ │
│ │ - ApiPermissionFilter (权限校验) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└────────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 数据层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ pg_application│ │ pg_app_api │ │ pg_api_dict │ │
│ │ 应用表 │ │ 应用接口授权 │ │ 接口字典 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ │
│ │ Redis │ ← 缓存应用授权信息,提升验证性能 │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
3.2 数据流设计
3.2.1 应用创建流程
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ 前端 │ │ Controller │ │ Service │ │ MySQL │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │ │
│ POST /api/application │ │
│──────────────────► │ │
│ │ │ │
│ │ insertApplication │
│ │──────────────────► │
│ │ │ │
│ │ │ 1.生成应用编码 │
│ │ │ 2.生成密钥 │
│ │ │ 3.加密存储 │
│ │ │──────────────────►
│ │ │ │
│ │ │ 4.保存接口授权 │
│ │ │──────────────────►
│ │ │ │
│ 返回新密钥(仅新增时) │ │
│◄────────────────── │ │
│ │ │ │
3.2.2 开放API调用流程
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ 第三方应用 │ │ API网关 │ │ Redis │ │ 业务 │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │ │
│ GET /open/xxx │ │ │
│ Header: X-App-Id, X-Timestamp, X-Sign │
│──────────────────► │ │
│ │ │ │
│ │ 1.验证时间戳 │ │
│ │ 2.查询应用信息 │ │
│ │──────────────────► │
│ │◄────────────────── │
│ │ │ │
│ │ 3.验证签名 │ │
│ │ 4.检查应用状态 │ │
│ │ 5.检查接口权限 │ │
│ │ │ │
│ │ │ 转发请求 │
│ │─────────────────────────────────────►
│ │ │ │
│ 返回业务数据 │ │ │
│◄──────────────────────────────────────────────────────│
│ │ │ │
4. 前端技术方案
4.1 技术栈
| 技术 |
版本 |
用途 |
| Vue |
3.x |
前端框架 |
| Element Plus |
2.x |
UI组件库 |
| Axios |
1.x |
HTTP客户端 |
| Pinia |
2.x |
状态管理 |
4.2 目录结构
pangu-ui/src/
├── api/
│ └── application.js # 应用管理API封装
├── views/
│ └── application/
│ ├── index.vue # 应用列表页
│ └── components/
│ ├── AppDialog.vue # 新增/编辑弹窗
│ └── SecretDialog.vue # 密钥展示弹窗
└── mock/
└── application.js # Mock数据(开发阶段)
4.3 页面设计
4.3.1 应用列表页 (index.vue)
页面布局
┌─────────────────────────────────────────────────────────────────┐
│ 搜索区域 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ ┌────┐ ┌────┐ │
│ │ 应用名称 │ │ 应用编码 │ │ 状态 ▼ │ │搜索│ │重置│ │
│ └──────────────┘ └──────────────┘ └──────────┘ └────┘ └────┘ │
├─────────────────────────────────────────────────────────────────┤
│ [+ 新增] │
├─────────────────────────────────────────────────────────────────┤
│ 表格区域 │
│ ┌──────┬──────────┬────────────────────┬────────┬──────┬─────┐│
│ │应用名│ 应用编码 │ 授权接口 │ 状态 │ 创建 │操作 ││
│ ├──────┼──────────┼────────────────────┼────────┼──────┼─────┤│
│ │智慧 │ YY000001 │ [学校] [年级] [+3] │ ● 正常 │01-01 │编辑 ││
│ │校园 │ │ │ │admin │重置 ││
│ │ │ │ │ │ │删除 ││
│ └──────┴──────────┴────────────────────┴────────┴──────┴─────┘│
├─────────────────────────────────────────────────────────────────┤
│ 共 20 条 [<] 1 2 3 ... [>] │
└─────────────────────────────────────────────────────────────────┘
核心代码结构
<template>
<div class="app-container">
<!-- 搜索区域 -->
<el-card shadow="never" class="search-wrapper">
<el-form :model="queryParams" :inline="true">
<el-form-item label="应用名称">
<el-input v-model="queryParams.appName" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="应用编码">
<el-input v-model="queryParams.appCode" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryParams.status" placeholder="全部" clearable>
<el-option label="正常" value="0" />
<el-option label="停用" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">搜索</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 表格区域 -->
<el-card shadow="never">
<el-button type="primary" @click="handleAdd">新增</el-button>
<el-table :data="tableData" border stripe>
<el-table-column prop="appName" label="应用名称" />
<el-table-column prop="appCode" label="应用编码" />
<el-table-column prop="apis" label="授权接口">
<template #default="{ row }">
<el-tag v-for="api in row.apis?.slice(0, 3)" :key="api" size="small">
{{ api }}
</el-tag>
<el-tag v-if="row.apis?.length > 3" type="info">+{{ row.apis.length - 3 }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态">
<template #default="{ row }">
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
{{ row.status === '0' ? '正常' : '停用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" />
<el-table-column label="操作" fixed="right">
<template #default="{ row }">
<el-button link @click="handleEdit(row)">编辑</el-button>
<el-button link @click="handleResetSecret(row)">重置密钥</el-button>
<el-popconfirm title="确定删除?" @confirm="handleDelete(row)">
<template #reference>
<el-button link type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<el-pagination v-model:current-page="queryParams.pageNum" :total="total" />
</el-card>
<!-- 弹窗组件 -->
<AppDialog ref="appDialogRef" @success="handleQuery" />
<SecretDialog ref="secretDialogRef" />
</div>
</template>
4.3.2 新增/编辑弹窗 (AppDialog.vue)
表单布局
┌─────────────────────────────────────────────────────────────────┐
│ 新增应用 / 编辑应用 [X] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 应用名称* [________________________] │
│ │
│ 应用编码 [保存后自动生成___________] (只读) │
│ │
│ 应用描述 [________________________] │
│ [________________________] │
│ [________________________] │
│ │
│ 联系人 [________________________] │
│ │
│ 联系电话 [________________________] │
│ │
│ 状态 [●] 正常 [○] 停用 │
│ │
│ 接口授权 [✓] 查询学生信息 │
│ [✓] 查询学校信息 │
│ [✓] 查询年级信息 │
│ [ ] 查询班级信息 │
│ [ ] 查询会员信息 │
│ [ ] 查询区域树 │
│ │
├─────────────────────────────────────────────────────────────────┤
│ [取消] [确定] │
└─────────────────────────────────────────────────────────────────┘
表单字段说明
| 字段 |
类型 |
必填 |
校验规则 |
说明 |
| appName |
文本 |
✓ |
最大100字符 |
应用名称 |
| appCode |
文本 |
- |
只读 |
系统自动生成 |
| description |
文本域 |
- |
最大500字符 |
应用描述 |
| contactPerson |
文本 |
- |
最大50字符 |
联系人 |
| contactPhone |
文本 |
- |
手机号格式 |
联系电话 |
| status |
开关 |
✓ |
- |
0正常/1停用 |
| apiCodes |
复选框组 |
- |
- |
授权的接口编码列表 |
4.3.3 密钥展示弹窗 (SecretDialog.vue)
弹窗布局
┌─────────────────────────────────────────────────────────────────┐
│ 应用密钥 [X] │
├─────────────────────────────────────────────────────────────────┤
│ ⚠️ 请妥善保管密钥,密钥重置后旧密钥将立即失效 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 应用名称 AI智慧平台 │
│ │
│ 应用编码 YY000001 │
│ │
│ 应用密钥 [a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6] [复制] │
│ │
├─────────────────────────────────────────────────────────────────┤
│ [关闭] │
└─────────────────────────────────────────────────────────────────┘
4.4 API接口封装
/**
* 应用管理API接口
* @author 湖北新华业务中台研发团队
*/
import request from '@/utils/request'
// 获取应用列表
export function listApplication(params) {
return request.get('/api/application/list', { params })
}
// 获取应用详情
export function getApplication(appId) {
return request.get(`/api/application/${appId}`)
}
// 新增应用
export function addApplication(data) {
return request.post('/api/application', data)
}
// 修改应用
export function updateApplication(data) {
return request.put('/api/application', data)
}
// 删除应用
export function deleteApplication(appId) {
return request.delete(`/api/application/${appId}`)
}
// 重置密钥
export function resetAppSecret(appId) {
return request.put(`/api/application/resetSecret/${appId}`)
}
// 获取API接口列表(用于授权选择)
export function getApiList() {
return request.get('/api/application/apiList')
}
4.5 前端开发规范
| 规范项 |
说明 |
| 组件命名 |
大驼峰,如 AppDialog.vue |
| 方法命名 |
小驼峰,如 handleQuery、handleEdit |
| 事件处理 |
以 handle 开头,如 handleSubmit |
| API请求 |
统一使用 @/utils/request 封装 |
| 表单验证 |
使用 Element Plus 的 Form 组件校验 |
| 错误处理 |
统一使用 ElMessage 提示 |
5. 后端技术方案
5.1 技术栈
| 技术 |
版本 |
用途 |
| Spring Boot |
3.3.x |
应用框架 |
| Spring Security |
6.x |
安全框架 |
| MyBatis Plus |
3.5.x |
ORM框架 |
| Redis |
7.x |
缓存 |
| Hutool |
5.x |
工具库 |
| JDK |
17+ |
运行环境 |
5.2 模块结构
pangu-admin/
├── controller/
│ └── ApplicationController.java # 应用管理控制器
├── service/
│ ├── IApplicationService.java # 应用服务接口
│ └── impl/
│ └── ApplicationServiceImpl.java
├── mapper/
│ ├── ApplicationMapper.java # 应用Mapper
│ └── AppApiMapper.java # 应用接口授权Mapper
├── domain/
│ ├── entity/
│ │ ├── Application.java # 应用实体
│ │ ├── AppApi.java # 应用接口授权实体
│ │ └── ApiDict.java # 接口字典实体
│ ├── vo/
│ │ └── ApplicationVO.java # 应用视图对象
│ └── dto/
│ └── ApplicationDTO.java # 应用传输对象
└── util/
└── SecretGenerator.java # 密钥生成工具
pangu-open/
├── controller/
│ └── OpenApiController.java # 开放API控制器
├── interceptor/
│ └── ApiAuthInterceptor.java # API认证拦截器
└── service/
└── ApiAuthService.java # API认证服务
5.3 核心类设计
5.3.1 实体类 (Application.java)
/**
* 应用实体
* @author 湖北新华业务中台研发团队
*/
@Data
@TableName("pg_application")
public class Application extends BaseEntity {
/** 应用ID */
@TableId(type = IdType.AUTO)
private Long appId;
/** 应用编码 */
private String appCode;
/** 应用名称 */
private String appName;
/** 应用密钥(加密存储) */
private String appSecret;
/** 应用描述 */
private String appDesc;
/** 联系人 */
private String contactPerson;
/** 联系电话 */
private String contactPhone;
/** 状态(0正常 1停用) */
private String status;
/** 删除标志 */
@TableLogic
private String delFlag;
}
5.3.2 服务接口 (IApplicationService.java)
/**
* 应用管理服务接口
* @author 湖北新华业务中台研发团队
*/
public interface IApplicationService {
/**
* 查询应用列表
*/
List<ApplicationVO> selectApplicationList(ApplicationDTO dto);
/**
* 查询应用详情
*/
ApplicationVO selectApplicationById(Long appId);
/**
* 新增应用
* @return 返回应用编码和密钥
*/
Map<String, String> insertApplication(ApplicationDTO dto);
/**
* 修改应用
*/
int updateApplication(ApplicationDTO dto);
/**
* 删除应用
*/
int deleteApplicationById(Long appId);
/**
* 重置密钥
* @return 返回新密钥
*/
String resetAppSecret(Long appId);
/**
* 根据应用编码查询应用信息(用于API认证)
*/
Application selectByAppCode(String appCode);
/**
* 检查应用是否有接口权限
*/
boolean checkApiPermission(String appCode, String apiPath);
}
5.3.3 控制器 (ApplicationController.java)
/**
* 应用管理控制器
* @author 湖北新华业务中台研发团队
*/
@RestController
@RequestMapping("/api/application")
@PreAuthorize("hasRole('admin')") // 仅超级管理员可访问
public class ApplicationController extends BaseController {
@Autowired
private IApplicationService applicationService;
/**
* 查询应用列表
*/
@GetMapping("/list")
public TableDataInfo list(ApplicationDTO dto) {
startPage();
List<ApplicationVO> list = applicationService.selectApplicationList(dto);
return getDataTable(list);
}
/**
* 获取应用详情
*/
@GetMapping("/{appId}")
public AjaxResult getInfo(@PathVariable Long appId) {
return AjaxResult.success(applicationService.selectApplicationById(appId));
}
/**
* 新增应用
*/
@PostMapping
@Log(title = "应用管理", businessType = BusinessType.INSERT)
public AjaxResult add(@Validated @RequestBody ApplicationDTO dto) {
Map<String, String> result = applicationService.insertApplication(dto);
return AjaxResult.success(result);
}
/**
* 修改应用
*/
@PutMapping
@Log(title = "应用管理", businessType = BusinessType.UPDATE)
public AjaxResult edit(@Validated @RequestBody ApplicationDTO dto) {
return toAjax(applicationService.updateApplication(dto));
}
/**
* 删除应用
*/
@DeleteMapping("/{appId}")
@Log(title = "应用管理", businessType = BusinessType.DELETE)
public AjaxResult remove(@PathVariable Long appId) {
return toAjax(applicationService.deleteApplicationById(appId));
}
/**
* 重置密钥
*/
@PutMapping("/resetSecret/{appId}")
@Log(title = "应用管理", businessType = BusinessType.UPDATE)
public AjaxResult resetSecret(@PathVariable Long appId) {
String newSecret = applicationService.resetAppSecret(appId);
return AjaxResult.success("重置成功", Map.of("appSecret", newSecret));
}
/**
* 获取API接口列表
*/
@GetMapping("/apiList")
public AjaxResult apiList() {
return AjaxResult.success(applicationService.selectApiDictList());
}
}
5.4 核心算法
5.4.1 应用编码生成
/**
* 生成应用编码
* 格式:YY + 6位序号(从000001开始)
*/
public String generateAppCode() {
// 获取当前最大序号
String maxCode = applicationMapper.selectMaxAppCode();
int nextSeq = 1;
if (StringUtils.isNotBlank(maxCode)) {
nextSeq = Integer.parseInt(maxCode.substring(2)) + 1;
}
return String.format("YY%06d", nextSeq);
}
5.4.2 密钥生成
/**
* 生成32位随机密钥
*/
public String generateAppSecret() {
return RandomUtil.randomString(32);
}
5.4.3 签名验证
/**
* 验证API请求签名
*/
public boolean verifySign(HttpServletRequest request) {
String appId = request.getHeader("X-App-Id");
String timestamp = request.getHeader("X-Timestamp");
String sign = request.getHeader("X-Sign");
// 1. 验证时间戳(5分钟内有效)
long reqTime = Long.parseLong(timestamp);
if (Math.abs(System.currentTimeMillis() - reqTime) > 5 * 60 * 1000) {
throw new ApiException("请求已过期");
}
// 2. 获取应用密钥
Application app = applicationService.selectByAppCode(appId);
if (app == null || "1".equals(app.getStatus())) {
throw new ApiException("应用不存在或已禁用");
}
// 3. 构建签名字符串
String signStr = buildSignString(request, app.getAppSecret());
// 4. 验证签名
String expectedSign = DigestUtils.md5Hex(signStr).toUpperCase();
return expectedSign.equals(sign);
}
/**
* 构建签名字符串
* 将请求参数按ASCII排序后拼接,末尾追加appSecret
*/
private String buildSignString(HttpServletRequest request, String appSecret) {
Map<String, String> params = new TreeMap<>();
request.getParameterMap().forEach((key, values) -> {
if (values.length > 0) {
params.put(key, values[0]);
}
});
StringBuilder sb = new StringBuilder();
params.forEach((key, value) -> {
if (sb.length() > 0) sb.append("&");
sb.append(key).append("=").append(value);
});
sb.append("&appSecret=").append(appSecret);
return sb.toString();
}
5.5 缓存设计
| 缓存KEY |
过期时间 |
说明 |
app:info:{appCode} |
30分钟 |
应用基本信息 |
app:apis:{appCode} |
30分钟 |
应用授权的接口列表 |
/**
* 清除应用缓存
* 在修改、删除、重置密钥时调用
*/
public void clearAppCache(String appCode) {
redisCache.deleteObject("app:info:" + appCode);
redisCache.deleteObject("app:apis:" + appCode);
}
6. 数据库设计
6.1 表结构
6.1.1 应用表 (pg_application)
CREATE TABLE `pg_application` (
`app_id` bigint NOT NULL AUTO_INCREMENT COMMENT '应用ID',
`app_code` varchar(32) NOT NULL COMMENT '应用编码',
`app_name` varchar(100) NOT NULL COMMENT '应用名称',
`app_secret` varchar(64) NOT NULL COMMENT '应用密钥',
`app_desc` varchar(500) DEFAULT NULL COMMENT '应用描述',
`contact_person` varchar(50) DEFAULT NULL COMMENT '联系人',
`contact_phone` varchar(20) DEFAULT NULL COMMENT '联系电话',
`status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`app_id`),
UNIQUE KEY `uk_app_code` (`app_code`)
) ENGINE=InnoDB COMMENT='应用表';
6.1.2 应用接口授权表 (pg_app_api)
CREATE TABLE `pg_app_api` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`app_id` bigint NOT NULL COMMENT '应用ID',
`api_code` varchar(100) NOT NULL COMMENT '接口编码',
`api_name` varchar(100) DEFAULT NULL COMMENT '接口名称',
`api_path` varchar(200) NOT NULL COMMENT '接口路径',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_app_api` (`app_id`, `api_code`),
KEY `idx_app_id` (`app_id`)
) ENGINE=InnoDB COMMENT='应用接口授权表';
6.1.3 API接口字典表 (pg_api_dict)
CREATE TABLE `pg_api_dict` (
`api_id` bigint NOT NULL AUTO_INCREMENT COMMENT '接口ID',
`api_code` varchar(100) NOT NULL COMMENT '接口编码',
`api_name` varchar(100) NOT NULL COMMENT '接口名称',
`api_path` varchar(200) NOT NULL COMMENT '接口路径',
`api_method` varchar(10) DEFAULT 'GET' COMMENT '请求方法',
`api_desc` varchar(500) DEFAULT NULL COMMENT '接口描述',
`order_num` int DEFAULT 0 COMMENT '显示顺序',
`status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`api_id`),
UNIQUE KEY `uk_api_code` (`api_code`)
) ENGINE=InnoDB COMMENT='API接口字典表';
6.2 初始化数据
-- API接口字典数据
INSERT INTO pg_api_dict (api_id, api_code, api_name, api_path, api_method, api_desc, order_num) VALUES
(1, 'STUDENT_LIST', '查询学生信息', '/open/student/list', 'GET', '获取学生列表', 1),
(2, 'SCHOOL_LIST', '查询学校信息', '/open/school/list', 'GET', '获取学校列表', 2),
(3, 'GRADE_LIST', '查询年级信息', '/open/grade/list', 'GET', '获取年级列表', 3),
(4, 'CLASS_LIST', '查询班级信息', '/open/class/list', 'GET', '获取班级列表', 4),
(5, 'MEMBER_LIST', '查询会员信息', '/open/member/list', 'GET', '获取会员列表', 5),
(6, 'REGION_TREE', '查询区域树', '/open/region/tree', 'GET', '获取区域树形结构', 6);
-- 示例应用数据
INSERT INTO pg_application (app_id, app_code, app_name, app_secret, app_desc, status, create_time) VALUES
(1, 'YY000001', 'AI智慧平台', 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6', 'AI智慧教育平台接入', '0', NOW());
-- 示例应用接口授权
INSERT INTO pg_app_api (id, app_id, api_code, api_name, api_path, create_time) VALUES
(1, 1, 'SCHOOL_LIST', '查询学校信息', '/open/school/list', NOW()),
(2, 1, 'GRADE_LIST', '查询年级信息', '/open/grade/list', NOW()),
(3, 1, 'CLASS_LIST', '查询班级信息', '/open/class/list', NOW());
6.3 索引设计
| 表名 |
索引名 |
索引字段 |
说明 |
| pg_application |
uk_app_code |
app_code |
应用编码唯一 |
| pg_app_api |
uk_app_api |
app_id, api_code |
应用接口唯一 |
| pg_app_api |
idx_app_id |
app_id |
按应用查询 |
| pg_api_dict |
uk_api_code |
api_code |
接口编码唯一 |
7. 接口设计
7.1 管理端接口
7.1.1 查询应用列表
| 项目 |
内容 |
| 接口地址 |
GET /api/application/list |
| 请求参数 |
appName, appCode, status, pageNum, pageSize |
| 响应格式 |
TableDataInfo(分页格式) |
7.1.2 获取应用详情
| 项目 |
内容 |
| 接口地址 |
GET /api/application/{appId} |
| 请求参数 |
appId(路径参数) |
| 响应格式 |
AjaxResult |
7.1.3 新增应用
| 项目 |
内容 |
| 接口地址 |
POST /api/application |
| 请求体 |
ApplicationDTO |
| 响应格式 |
AjaxResult(包含appCode和appSecret) |
请求示例
{
"appName": "AI智慧平台",
"appDesc": "AI教育智慧平台",
"contactPerson": "张经理",
"contactPhone": "13812345678",
"status": "0",
"apiCodes": ["STUDENT_LIST", "SCHOOL_LIST", "GRADE_LIST"]
}
响应示例
{
"code": 200,
"msg": "新增成功",
"data": {
"appCode": "YY000002",
"appSecret": "x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5m6"
}
}
7.1.4 修改应用
| 项目 |
内容 |
| 接口地址 |
PUT /api/application |
| 请求体 |
ApplicationDTO(包含appId) |
| 响应格式 |
AjaxResult |
7.1.5 删除应用
| 项目 |
内容 |
| 接口地址 |
DELETE /api/application/{appId} |
| 请求参数 |
appId(路径参数) |
| 响应格式 |
AjaxResult |
7.1.6 重置密钥
| 项目 |
内容 |
| 接口地址 |
PUT /api/application/resetSecret/{appId} |
| 请求参数 |
appId(路径参数) |
| 响应格式 |
AjaxResult(包含新的appSecret) |
响应示例
{
"code": 200,
"msg": "重置成功",
"data": {
"appSecret": "n1e2w3s4e5c6r7e8t9k0e1y2v3a4l5u6"
}
}
7.1.7 获取API接口列表
| 项目 |
内容 |
| 接口地址 |
GET /api/application/apiList |
| 请求参数 |
无 |
| 响应格式 |
AjaxResult |
响应示例
{
"code": 200,
"msg": "查询成功",
"data": [
{ "apiCode": "STUDENT_LIST", "apiName": "查询学生信息", "apiPath": "/open/student/list" },
{ "apiCode": "SCHOOL_LIST", "apiName": "查询学校信息", "apiPath": "/open/school/list" },
{ "apiCode": "GRADE_LIST", "apiName": "查询年级信息", "apiPath": "/open/grade/list" },
{ "apiCode": "CLASS_LIST", "apiName": "查询班级信息", "apiPath": "/open/class/list" },
{ "apiCode": "MEMBER_LIST", "apiName": "查询会员信息", "apiPath": "/open/member/list" },
{ "apiCode": "REGION_TREE", "apiName": "查询区域树", "apiPath": "/open/region/tree" }
]
}
7.2 开放API接口
7.2.1 签名规则
请求头
| Header |
必填 |
说明 |
| X-App-Id |
是 |
应用编码 |
| X-Timestamp |
是 |
时间戳(毫秒) |
| X-Sign |
是 |
签名 |
签名算法
1. 将所有请求参数按参数名ASCII升序排序
2. 拼接成 key1=value1&key2=value2 格式
3. 末尾追加 &appSecret=xxx
4. 对整个字符串进行MD5加密(32位大写)
Java签名示例
public String generateSign(Map<String, String> params, String appSecret) {
// 1. 参数排序
Map<String, String> sortedParams = new TreeMap<>(params);
// 2. 拼接字符串
StringBuilder sb = new StringBuilder();
sortedParams.forEach((key, value) -> {
if (sb.length() > 0) sb.append("&");
sb.append(key).append("=").append(value);
});
sb.append("&appSecret=").append(appSecret);
// 3. MD5加密
return DigestUtils.md5Hex(sb.toString()).toUpperCase();
}
8. 开发计划
8.1 阶段划分
| 阶段 |
内容 |
交付物 |
| 阶段一:设计评审 |
技术方案评审、接口设计评审 |
评审通过的技术方案 |
| 阶段二:后端开发 |
数据库表创建、后端接口开发 |
可调试的后端API |
| 阶段三:前端开发 |
前端页面开发、接口联调 |
可运行的前端页面 |
| 阶段四:联调测试 |
前后端联调、功能测试 |
测试报告 |
| 阶段五:验收上线 |
用户验收、部署上线 |
上线报告 |
8.2 开发任务清单
8.2.1 后端任务
| 序号 |
任务 |
优先级 |
依赖 |
| 1 |
创建数据库表 |
P0 |
- |
| 2 |
实体类和Mapper开发 |
P0 |
1 |
| 3 |
Service层开发 |
P0 |
2 |
| 4 |
Controller层开发 |
P0 |
3 |
| 5 |
应用编码生成器 |
P0 |
3 |
| 6 |
密钥生成与加密 |
P0 |
3 |
| 7 |
API签名验证拦截器 |
P0 |
3 |
| 8 |
缓存处理 |
P1 |
7 |
| 9 |
单元测试 |
P1 |
4 |
8.2.2 前端任务
| 序号 |
任务 |
优先级 |
依赖 |
| 1 |
API接口封装 |
P0 |
- |
| 2 |
列表页面开发 |
P0 |
1 |
| 3 |
新增/编辑弹窗开发 |
P0 |
1 |
| 4 |
密钥展示弹窗开发 |
P0 |
1 |
| 5 |
Mock数据开发 |
P1 |
1 |
| 6 |
接口联调 |
P0 |
后端4 |
| 7 |
样式优化 |
P2 |
6 |
8.3 里程碑
| 里程碑 |
完成标准 |
| M1 设计完成 |
技术方案通过评审 |
| M2 后端完成 |
后端接口开发完成,Postman可调试 |
| M3 前端完成 |
前端页面开发完成,Mock数据可运行 |
| M4 联调完成 |
前后端联调通过,功能可正常使用 |
| M5 验收完成 |
用户验收通过,可部署上线 |
9. 测试方案
9.1 测试范围
| 测试类型 |
测试内容 |
| 功能测试 |
应用CRUD、接口授权、密钥重置 |
| 接口测试 |
API签名验证、权限校验 |
| 性能测试 |
列表查询性能、签名验证性能 |
| 安全测试 |
密钥加密、防重放攻击 |
9.2 测试用例
9.2.1 功能测试用例
| 用例编号 |
用例名称 |
前置条件 |
测试步骤 |
预期结果 |
| TC-APP-001 |
新增应用 |
已登录超管账号 |
1.点击新增 2.填写表单 3.选择接口 4.提交 |
应用创建成功,显示密钥 |
| TC-APP-002 |
应用编码自动生成 |
已有应用YY000001 |
新增应用 |
新应用编码为YY000002 |
| TC-APP-003 |
重置密钥 |
已有应用 |
点击重置密钥 |
弹窗显示新密钥,可复制 |
| TC-APP-004 |
删除应用 |
已有应用 |
点击删除,确认 |
应用删除成功 |
| TC-APP-005 |
禁用应用 |
已有应用 |
编辑状态为停用 |
应用无法调用API |
| TC-APP-006 |
接口授权 |
已有应用 |
勾选部分接口 |
仅授权接口可访问 |
9.2.2 接口测试用例
| 用例编号 |
用例名称 |
测试步骤 |
预期结果 |
| TC-API-001 |
签名正确 |
使用正确密钥生成签名 |
请求成功 |
| TC-API-002 |
签名错误 |
使用错误密钥生成签名 |
返回"签名验证失败" |
| TC-API-003 |
时间戳过期 |
使用10分钟前的时间戳 |
返回"请求已过期" |
| TC-API-004 |
应用禁用 |
使用禁用应用的密钥 |
返回"应用已禁用" |
| TC-API-005 |
无接口权限 |
访问未授权接口 |
返回"无权访问该接口" |
9.3 验收标准
| 验收项 |
验收标准 |
| 功能完整性 |
所有P0功能用例通过 |
| 接口安全性 |
签名验证、权限校验正常 |
| 性能指标 |
列表查询 ≤500ms,签名验证 ≤50ms |
| 代码质量 |
无严重BUG,代码规范检查通过 |
10. 风险评估
10.1 风险清单
| 风险编号 |
风险描述 |
影响程度 |
发生概率 |
应对措施 |
| R01 |
密钥泄露 |
高 |
低 |
加密存储,传输加密,操作审计 |
| R02 |
签名算法被破解 |
高 |
低 |
预留算法升级能力 |
| R03 |
API被恶意调用 |
中 |
中 |
频率限制,IP白名单(预留) |
| R04 |
权限变更不实时 |
低 |
中 |
使用缓存时设置合理过期时间 |
10.2 应对方案
R01 密钥泄露
- 预防:密钥加密存储,只在新增和重置时显示一次
- 发现:监控异常调用行为
- 处置:支持立即重置密钥使旧密钥失效
R03 API被恶意调用
- 当前:签名验证 + 时间戳防重放
- 预留:接口频率限制(可后续添加)
- 预留:IP白名单机制(可后续添加)
附录
附录A:墨刀原型对照
| 原型页面 |
对应功能 |
实现状态 |
| 应用管理-列表 |
应用列表查询 |
待开发 |
| 应用管理-编辑 |
新增/编辑应用、接口授权 |
待开发 |
附录B:相关文档
| 文档名称 |
路径 |
| 需求规格说明书 |
docs/01-需求文档/需求规格说明书_v1.0.md |
| 系统设计文档 |
docs/02-系统设计/系统设计文档_v1.0.md |
| 数据库设计文档 |
docs/03-数据库设计/数据库设计文档_v1.0.md |
| 接口设计文档 |
docs/04-接口文档/接口设计文档_v1.0.md |
审核签字
| 角色 |
姓名 |
日期 |
签字 |
| 技术负责人 |
|
|
|
| 前端负责人 |
|
|
|
| 后端负责人 |
|
|
|
| 测试负责人 |
|
|
|
文档结束