pangu-user-platform/docs/05-模块技术方案/应用管理模块技术方案_v1.0.md

1230 lines
49 KiB
Markdown
Raw Normal View History

# 盘古用户平台 - 应用管理模块技术方案
---
| 文档信息 | 内容 |
|---------|------|
| **文档版本** | V1.0 |
| **项目名称** | 盘古用户平台Pangu User Platform |
| **模块名称** | 应用管理模块 |
| **编写团队 | pangu |
| **创建日期** | 2026-01-31 |
| **审核状态** | 待评审 |
---
## 修订记录
| 版本 | 日期 | 修订人 | 修订内容 |
|------|------|--------|----------|
| V1.0 | 2026-01-31 | pangu | 初稿 |
---
## 目录
1. [模块概述](#1-模块概述)
2. [需求分析](#2-需求分析)
3. [系统设计](#3-系统设计)
4. [前端技术方案](#4-前端技术方案)
5. [后端技术方案](#5-后端技术方案)
6. [数据库设计](#6-数据库设计)
7. [接口设计](#7-接口设计)
8. [开发计划](#8-开发计划)
9. [测试方案](#9-测试方案)
10. [风险评估](#10-风险评估)
---
## 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 ... [>] │
└─────────────────────────────────────────────────────────────────┘
```
**核心代码结构**
```vue
<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接口封装
```javascript
/**
* 应用管理API接口
* @author pangu
*/
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)
```java
/**
* 应用实体
* @author pangu
*/
@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)
```java
/**
* 应用管理服务接口
* @author pangu
*/
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)
```java
/**
* 应用管理控制器
* @author pangu
*/
@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 应用编码生成
```java
/**
* 生成应用编码
* 格式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 密钥生成
```java
/**
* 生成32位随机密钥
*/
public String generateAppSecret() {
return RandomUtil.randomString(32);
}
```
#### 5.4.3 签名验证
```java
/**
* 验证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分钟 | 应用授权的接口列表 |
```java
/**
* 清除应用缓存
* 在修改、删除、重置密钥时调用
*/
public void clearAppCache(String appCode) {
redisCache.deleteObject("app:info:" + appCode);
redisCache.deleteObject("app:apis:" + appCode);
}
```
---
## 6. 数据库设计
### 6.1 表结构
#### 6.1.1 应用表 (pg_application)
```sql
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)
```sql
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)
```sql
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 初始化数据
```sql
-- 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 |
**请求示例**
```json
{
"appName": "AI智慧平台",
"appDesc": "AI教育智慧平台",
"contactPerson": "张经理",
"contactPhone": "13812345678",
"status": "0",
"apiCodes": ["STUDENT_LIST", "SCHOOL_LIST", "GRADE_LIST"]
}
```
**响应示例**
```json
{
"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 |
**响应示例**
```json
{
"code": 200,
"msg": "重置成功",
"data": {
"appSecret": "n1e2w3s4e5c6r7e8t9k0e1y2v3a4l5u6"
}
}
```
#### 7.1.7 获取API接口列表
| 项目 | 内容 |
|------|------|
| **接口地址** | `GET /api/application/apiList` |
| **请求参数** | 无 |
| **响应格式** | AjaxResult |
**响应示例**
```json
{
"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签名示例**
```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 |
---
## 审核签字
| 角色 | 姓名 | 日期 | 签字 |
|-----|------|------|------|
| 技术负责人 | | | |
| 前端负责人 | | | |
| 后端负责人 | | | |
| 测试负责人 | | | |
---
*文档结束*