pangu-user-platform/docs/应用管理-需求与技术设计方案.md

54 KiB
Raw Permalink Blame History

应用管理模块 - 需求与技术设计方案

文档信息

项目 内容
项目名称 盘古用户认证中心 - 应用管理模块
文档版本 V1.0
编写日期 2026-02-04
作者 pangu
状态 已完成

第一部分:需求设计方案

1. 业务背景

1.1 项目背景

盘古用户认证中心作为统一身份认证平台需要对外提供开放API接口供第三方应用系统如AI智慧教育平台、数字图书馆系统、校园OA办公系统、家校通小程序等调用以获取学生、教师、学校等基础数据信息。

为保障数据安全和接口调用的可控性,需要建立一套完整的应用管理机制,实现:

  • 第三方应用的注册与管理
  • 应用凭证AppCode + AppSecret的生成与维护
  • 开放接口的精细化授权控制
  • 接口调用的安全鉴权

1.2 业务目标

  1. 安全可控:通过应用编码和密钥机制,确保只有授权应用才能调用开放接口
  2. 权限精细:支持按应用维度配置可调用的接口列表,实现最小权限原则
  3. 易于扩展:提供标准化的开放接口规范和示例代码,便于甲方二次开发
  4. 运维便捷:支持应用状态管理、密钥重置、调用日志等运维功能

1.3 适用范围

本方案适用于以下场景:

  • 第三方业务系统需要获取学生、教师、学校等基础数据
  • 移动端小程序需要调用认证中心接口
  • 内部子系统之间的数据同步
  • 数据中台、大数据平台的数据采集

2. 需求分析

2.1 用户角色

角色 描述 主要操作
系统管理员 认证中心的管理员 管理第三方应用、配置接口授权、查看调用日志
第三方开发者 对接方的技术人员 获取应用凭证、按规范调用API、查看接口文档

2.2 功能需求

2.2.1 应用管理(管理端)

功能点 描述 优先级
应用列表 展示所有已注册的第三方应用,支持搜索、分页 P0
新增应用 录入应用基本信息系统自动生成AppCode和AppSecret P0
编辑应用 修改应用名称、联系人、状态等信息 P0
删除应用 删除应用(逻辑删除),删除后凭证失效 P0
重置密钥 重新生成AppSecret原密钥立即失效 P0
接口授权 为应用配置可调用的接口列表 P0
查看凭证 查看应用的AppCode和AppSecret P1
调用统计 查看应用的接口调用次数、成功率等 P2

2.2.2 开放接口(对接端)

功能点 描述 优先级
接口鉴权 验证请求的AppCode、时间戳、签名 P0
权限校验 检查应用是否有权调用该接口 P0
学生列表 分页查询学生信息(示例接口) P0
接口文档 提供接口说明、参数定义、示例代码 P1

2.2.3 接口字典管理

功能点 描述 优先级
接口字典 维护可授权的开放接口列表 P0
新增接口 添加新的开放接口定义(二次开发) P1

2.3 非功能需求

类型 需求描述
安全性 签名有效期5分钟防重放攻击密钥使用MD5加密传输
性能 接口鉴权响应时间 < 50ms不影响业务接口性能
可用性 支持应用状态控制,停用后立即生效
可扩展性 支持甲方二次开发,新增开放接口无需修改鉴权逻辑
兼容性 支持多种调用方式HTTP GET/POST支持常见编程语言

3. 业务流程

3.1 应用注册与授权流程

┌─────────────────────────────────────────────────────────────────────────────┐
│                              应用注册与授权流程                               │
└─────────────────────────────────────────────────────────────────────────────┘

  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
  │ 收集需求 │───>│ 创建应用 │───>│ 生成凭证 │───>│ 配置授权 │───>│ 交付凭证 │
  └──────────┘    └──────────┘    └──────────┘    └──────────┘    └──────────┘
       │               │               │               │               │
       v               v               v               v               v
  收集第三方       填写应用名称     自动生成         勾选该应用       将AppCode
  系统信息         联系人等        AppCode         可调用的         AppSecret
  确认对接需求     基本信息        AppSecret       接口列表         发送给对接方

3.2 第三方调用接口流程

┌─────────────────────────────────────────────────────────────────────────────┐
│                              第三方调用接口流程                               │
└─────────────────────────────────────────────────────────────────────────────┘

  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
  │ 构造请求 │───>│ 计算签名 │───>│ 发送请求 │───>│ 服务端校验│───>│ 返回数据 │
  └──────────┘    └──────────┘    └──────────┘    └──────────┘    └──────────┘
       │               │               │               │               │
       v               v               v               v               v
  准备请求参数     MD5(params      添加请求头      1.验证签名       校验通过
  pageNum=1       +appSecret)     X-App-Id        2.验证时间戳     返回业务数据
  pageSize=10     得到签名值      X-Timestamp     3.验证应用状态   校验失败
                                  X-Sign          4.验证接口权限   返回错误信息

3.3 签名算法流程

┌─────────────────────────────────────────────────────────────────────────────┐
│                                签名计算流程                                   │
└─────────────────────────────────────────────────────────────────────────────┘

  1. 收集所有请求参数(不含签名本身)
     ┌─────────────────────────────────────┐
     │ pageNum=1, pageSize=10, status=0   │
     └─────────────────────────────────────┘
                      │
                      v
  2. 按参数名 ASCII 码升序排序
     ┌─────────────────────────────────────┐
     │ pageNum=1 & pageSize=10 & status=0 │
     └─────────────────────────────────────┘
                      │
                      v
  3. 拼接键值对,末尾追加 appSecret
     ┌─────────────────────────────────────────────────────────┐
     │ pageNum=1&pageSize=10&status=0&appSecret=xxxxxxxxxx    │
     └─────────────────────────────────────────────────────────┘
                      │
                      v
  4. 对整个字符串进行 MD5 加密,转大写
     ┌─────────────────────────────────────┐
     │ A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6   │  <-- X-Sign
     └─────────────────────────────────────┘

4. 界面原型说明

4.1 应用列表页面

页面路径http://localhost/application

页面布局

┌─────────────────────────────────────────────────────────────────────────────┐
│ 搜索区域                                                                     │
│ ┌──────────────┐ ┌──────────────┐ ┌────────┐ ┌────────┐ ┌────────┐         │
│ │ 应用名称     │ │ 应用编码     │ │ 状态 ▼ │ │ 🔍搜索 │ │ ↻重置  │         │
│ └──────────────┘ └──────────────┘ └────────┘ └────────┘ └────────┘         │
├─────────────────────────────────────────────────────────────────────────────┤
│ ┌─────────┐                                                                 │
│ │ + 新增  │                                                                 │
│ └─────────┘                                                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │ 应用名称       │ 应用编码  │ 联系人 │ 联系电话    │ 状态 │ 创建时间   │ 操作      │
│ ├────────────────┼──────────┼────────┼─────────────┼──────┼────────────┼──────────┤
│ │ AI智慧教育平台 │ YY000001 │ 张经理 │ 138****3001 │ 正常 │ 2026-02-02 │ 编辑 设置 │
│ │ 数字图书馆系统 │ YY000002 │ 李经理 │ 138****3002 │ 正常 │ 2026-02-02 │ 编辑 设置 │
│ │ ...            │ ...      │ ...    │ ...         │ ...  │ ...        │ ...      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                    共 6 条  10条/页  < 1 >  │
└─────────────────────────────────────────────────────────────────────────────┘

操作按钮说明

按钮 功能 说明
新增 打开新增应用弹窗 录入应用基本信息
编辑 打开编辑应用弹窗 修改应用信息
设置 打开接口授权弹窗 配置应用可调用的接口
重置密钥 重新生成密钥 需二次确认
删除 删除应用 需二次确认

4.2 新增/编辑应用弹窗

┌─────────────────────────────────────────────────┐
│ 新增应用                                    [×] │
├─────────────────────────────────────────────────┤
│                                                 │
│   应用名称 *  ┌────────────────────────────┐   │
│              │ 请输入应用名称              │   │
│              └────────────────────────────┘   │
│                                                 │
│   应用编码    ┌────────────────────────────┐   │
│              │ 保存后自动生成(不可编辑)  │   │
│              └────────────────────────────┘   │
│                                                 │
│   应用描述    ┌────────────────────────────┐   │
│              │                            │   │
│              │ 请输入应用描述             │   │
│              └────────────────────────────┘   │
│                                                 │
│   联系人      ┌────────────────────────────┐   │
│              │ 请输入联系人               │   │
│              └────────────────────────────┘   │
│                                                 │
│   联系电话    ┌────────────────────────────┐   │
│              │ 请输入联系电话             │   │
│              └────────────────────────────┘   │
│                                                 │
│   状态        ○ 正常  ○ 停用                   │
│                                                 │
├─────────────────────────────────────────────────┤
│                    [取消]  [确定]               │
└─────────────────────────────────────────────────┘

4.3 接口授权设置弹窗

┌─────────────────────────────────────────────────┐
│ 接口授权设置 - AI智慧教育平台               [×] │
├─────────────────────────────────────────────────┤
│                                                 │
│   应用编码YY000001                            │
│   应用密钥a1b2c3d4...(点击复制)            │
│                                                 │
│   ─────────────────────────────────────────    │
│                                                 │
│   可授权接口:                                   │
│                                                 │
│   ☑ 学生列表查询   /open/api/student/list      │
│   ☐ 教师列表查询   /open/api/teacher/list      │
│   ☐ 学校信息查询   /open/api/school/info       │
│   ☐ 班级列表查询   /open/api/class/list        │
│   ...                                          │
│                                                 │
├─────────────────────────────────────────────────┤
│                    [取消]  [保存]               │
└─────────────────────────────────────────────────┘

5. 数据字典

5.1 应用状态

状态值 状态名称 说明
0 正常 应用可正常调用接口
1 停用 应用已停用,所有接口调用将被拒绝

5.2 API 状态

状态值 状态名称 说明
0 正常 接口可正常被授权和调用
1 停用 接口已停用,即使授权也无法调用

5.3 请求方法

方法 说明
GET 查询类接口
POST 新增/复杂查询类接口
PUT 修改类接口
DELETE 删除类接口

第二部分:技术设计方案

6. 系统架构

6.1 整体架构图

┌─────────────────────────────────────────────────────────────────────────────┐
│                                 调用方                                       │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │
│   │ AI教育平台   │  │ 图书馆系统   │  │ OA办公系统   │  │ 家校通小程序 │   │
│   └──────┬───────┘  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘   │
└──────────┼─────────────────┼─────────────────┼─────────────────┼───────────┘
           │                 │                 │                 │
           └─────────────────┴────────┬────────┴─────────────────┘
                                      │
                                      v
┌─────────────────────────────────────────────────────────────────────────────┐
│                            盘古用户认证中心                                   │
│  ┌───────────────────────────────────────────────────────────────────────┐  │
│  │                         API 网关层(可选)                             │  │
│  └───────────────────────────────────────────────────────────────────────┘  │
│                                      │                                       │
│                                      v                                       │
│  ┌───────────────────────────────────────────────────────────────────────┐  │
│  │                      开放接口认证拦截器                                 │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐   │  │
│  │  │ 参数校验    │─>│ 签名验证    │─>│ 应用状态    │─>│ 接口权限    │   │  │
│  │  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘   │  │
│  └───────────────────────────────────────────────────────────────────────┘  │
│                                      │                                       │
│                                      v                                       │
│  ┌───────────────────────────────────────────────────────────────────────┐  │
│  │                           开放接口层                                   │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐   │  │
│  │  │ 学生接口    │  │ 教师接口    │  │ 学校接口    │  │ 更多接口... │   │  │
│  │  │ /open/api/  │  │ /open/api/  │  │ /open/api/  │  │ (二次开发)  │   │  │
│  │  │ student/*   │  │ teacher/*   │  │ school/*    │  │             │   │  │
│  │  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘   │  │
│  └───────────────────────────────────────────────────────────────────────┘  │
│                                      │                                       │
│                                      v                                       │
│  ┌───────────────────────────────────────────────────────────────────────┐  │
│  │                           业务服务层                                   │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐   │  │
│  │  │ 学生服务    │  │ 教师服务    │  │ 学校服务    │  │ 应用管理    │   │  │
│  │  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘   │  │
│  └───────────────────────────────────────────────────────────────────────┘  │
│                                      │                                       │
│                                      v                                       │
│  ┌───────────────────────────────────────────────────────────────────────┐  │
│  │                           数据持久层                                   │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐   │  │
│  │  │ pg_student  │  │ pg_teacher  │  │ pg_school   │  │ pg_app*     │   │  │
│  │  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘   │  │
│  └───────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────┘

6.2 技术选型

层次 技术栈 说明
前端框架 Vue 3 + Element Plus 管理后台界面
后端框架 Spring Boot 3.x 基于若依Plus框架
ORM框架 MyBatis Plus 数据库访问
安全框架 Sa-Token 内部系统鉴权(管理端)
数据库 MySQL 8.0 数据存储
缓存 Redis 可选,用于接口限流、凭证缓存
加密算法 MD5 接口签名可升级为HMAC-SHA256

7. 数据库设计

7.1 ER 关系图

┌─────────────────────┐         ┌─────────────────────┐         ┌─────────────────────┐
│   pg_application    │         │     pg_app_api      │         │    pg_api_dict      │
│   (第三方应用表)     │ 1───n   │  (应用API授权表)    │   n───1 │   (API接口字典)     │
├─────────────────────┤         ├─────────────────────┤         ├─────────────────────┤
│ PK app_id          │────────>│ FK app_id           │         │ PK api_id           │
│    app_code        │         │ FK api_id           │<────────│    api_code         │
│    app_name        │         │    create_time      │         │    api_name         │
│    app_secret      │         └─────────────────────┘         │    api_path         │
│    contact_person  │                                          │    api_method       │
│    contact_phone   │                                          │    api_desc         │
│    status          │                                          │    status           │
│    ...             │                                          │    order_num        │
└─────────────────────┘                                          └─────────────────────┘

7.2 表结构详细设计

7.2.1 第三方应用表 (pg_application)

CREATE TABLE `pg_application` (
  `app_id` bigint NOT NULL COMMENT '应用ID雪花算法',
  `app_code` varchar(32) NOT NULL COMMENT '应用编码格式YY000001',
  `app_name` varchar(100) NOT NULL COMMENT '应用名称',
  `app_secret` varchar(100) NOT NULL COMMENT '应用密钥32位UUID',
  `contact_person` varchar(50) DEFAULT NULL COMMENT '联系人',
  `contact_phone` varchar(20) DEFAULT NULL COMMENT '联系电话',
  `status` char(1) DEFAULT '0' COMMENT '状态0正常 1停用',
  `tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
  `create_dept` bigint DEFAULT NULL COMMENT '创建部门',
  `create_by` bigint DEFAULT NULL COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint DEFAULT NULL 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='第三方应用表';

字段说明

字段 类型 必填 说明
app_id bigint 主键,雪花算法生成
app_code varchar(32) 应用编码,格式 YY+6位序号系统自动生成
app_name varchar(100) 应用名称,用于显示
app_secret varchar(100) 应用密钥32位UUID用于签名计算
contact_person varchar(50) 对接联系人
contact_phone varchar(20) 联系电话
status char(1) 0=正常1=停用
del_flag char(1) 逻辑删除标志0=存在1=已删除

7.2.2 API接口字典表 (pg_api_dict)

CREATE TABLE `pg_api_dict` (
  `api_id` bigint NOT NULL COMMENT 'API ID',
  `api_code` varchar(50) NOT NULL COMMENT 'API编码唯一标识',
  `api_name` varchar(100) NOT NULL COMMENT 'API名称显示用',
  `api_path` varchar(200) NOT NULL COMMENT 'API路径如 /open/api/student/list',
  `api_method` varchar(10) DEFAULT 'GET' COMMENT '请求方法GET/POST/PUT/DELETE',
  `api_desc` varchar(500) DEFAULT NULL COMMENT 'API描述',
  `status` char(1) DEFAULT '0' COMMENT '状态0正常 1停用',
  `order_num` int DEFAULT 0 COMMENT '排序号',
  PRIMARY KEY (`api_id`),
  UNIQUE KEY `uk_api_code` (`api_code`)
) ENGINE=InnoDB COMMENT='API接口字典表';

初始数据(示例接口):

INSERT INTO pg_api_dict (api_id, api_code, api_name, api_path, api_method, api_desc, status, order_num) VALUES
(1700000000000000001, 'OPEN_STUDENT_LIST', '学生列表查询', '/open/api/student/list', 'GET', '分页查询学生信息,支持按姓名、学号、班级等条件筛选', '0', 10);

7.2.3 应用API授权表 (pg_app_api)

CREATE TABLE `pg_app_api` (
  `id` bigint NOT NULL COMMENT '主键',
  `app_id` bigint NOT NULL COMMENT '应用ID',
  `api_id` bigint NOT NULL COMMENT 'API ID',
  `create_time` datetime DEFAULT NULL COMMENT '授权时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_app_api` (`app_id`, `api_id`),
  KEY `idx_app_id` (`app_id`),
  KEY `idx_api_id` (`api_id`)
) ENGINE=InnoDB COMMENT='应用API授权表';

7.3 菜单数据

-- 应用管理菜单(一级菜单)
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, menu_type, perms, icon)
VALUES (2300, '应用管理', 0, 4, 'application', 'business/application/index', 'C', 'business:application:list', 'component');

-- 按钮权限
INSERT INTO sys_menu VALUES (2301, '应用查询', 2300, 1, '', '', 'F', 'business:application:query', '#');
INSERT INTO sys_menu VALUES (2302, '应用新增', 2300, 2, '', '', 'F', 'business:application:add', '#');
INSERT INTO sys_menu VALUES (2303, '应用修改', 2300, 3, '', '', 'F', 'business:application:edit', '#');
INSERT INTO sys_menu VALUES (2304, '应用删除', 2300, 4, '', '', 'F', 'business:application:remove', '#');
INSERT INTO sys_menu VALUES (2305, '重置密钥', 2300, 5, '', '', 'F', 'business:application:resetSecret', '#');
INSERT INTO sys_menu VALUES (2306, '接口授权', 2300, 6, '', '', 'F', 'business:application:api', '#');

8. 接口设计

8.1 管理端接口

8.1.1 应用管理接口

接口名称 方法 URL 权限 说明
应用列表 GET /business/application/list business:application:list 分页查询
应用详情 GET /business/application/{appId} business:application:query 获取详情
新增应用 POST /business/application business:application:add 新增
修改应用 PUT /business/application business:application:edit 修改
删除应用 DELETE /business/application/{appIds} business:application:remove 删除
重置密钥 PUT /business/application/resetSecret/{appId} business:application:edit 重置
接口列表 GET /business/application/apiList business:application:list 获取可授权接口

8.1.2 接口详细定义

新增应用

POST /business/application
Content-Type: application/json

{
  "appName": "AI智慧教育平台",
  "contactPerson": "张经理",
  "contactPhone": "13800138001",
  "status": "0",
  "remark": "新华AI智慧教育平台提供智能题库、AI批改等功能",
  "apiCodes": ["OPEN_STUDENT_LIST"]
}

响应

{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "appId": 1890000000000000001,
    "appCode": "YY000001",
    "appSecret": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
  }
}

8.2 开放接口

8.2.1 请求头规范

Header 必填 说明
X-App-Id 应用编码AppCode
X-Timestamp 当前时间戳(毫秒)
X-Sign 请求签名MD5大写

8.2.2 签名算法

1. 将所有请求参数(不含签名)按参数名 ASCII 码升序排序
2. 按照 key1=value1&key2=value2&... 格式拼接
3. 末尾追加 &appSecret={密钥}
4. 对整个字符串进行 MD5 加密,转大写

示例:
请求参数pageNum=1, pageSize=10, status=0
密钥a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

签名字符串pageNum=1&pageSize=10&status=0&appSecret=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
签名结果MD5(签名字符串).toUpperCase()

8.2.3 学生列表接口(示例)

GET /open/api/student/list?pageNum=1&pageSize=10
X-App-Id: YY000001
X-Timestamp: 1738656000000
X-Sign: A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6

响应

{
  "code": 200,
  "msg": "操作成功",
  "rows": [
    {
      "studentId": 1,
      "studentName": "张三",
      "studentCode": "S2024001",
      "schoolName": "第一小学",
      "gradeName": "三年级",
      "className": "1班"
    }
  ],
  "total": 100
}

8.2.4 错误码定义

错误码 错误信息 说明
401 缺少认证参数 X-App-Id/X-Timestamp/X-Sign 缺失
401 时间戳格式错误 X-Timestamp 非数字
401 请求已过期 时间戳超过5分钟有效期
401 应用不存在 AppCode 无效
401 应用已停用 应用状态为停用
401 签名验证失败 签名计算错误
403 无权访问该接口 应用未被授权访问该接口

9. 核心代码设计

9.1 后端代码结构

pangu-modules/pangu-business/src/main/java/org/dromara/pangu/
├── application/                          # 应用管理模块
│   ├── controller/
│   │   └── PgApplicationController.java  # 应用管理控制器
│   ├── domain/
│   │   ├── PgApplication.java            # 第三方应用实体
│   │   ├── PgApiDict.java                # API字典实体
│   │   └── PgAppApi.java                 # 应用API授权实体
│   ├── mapper/
│   │   ├── PgApplicationMapper.java
│   │   ├── PgApiDictMapper.java
│   │   └── PgAppApiMapper.java
│   └── service/
│       ├── IPgApplicationService.java
│       ├── IPgApiDictService.java
│       └── impl/
│           ├── PgApplicationServiceImpl.java
│           └── PgApiDictServiceImpl.java
│
└── openapi/                              # 开放接口模块
    ├── config/
    │   ├── ApiAuthInterceptor.java       # 开放API鉴权拦截器
    │   └── OpenApiWebMvcConfig.java      # 拦截器注册配置
    ├── controller/
    │   └── OpenApiStudentController.java # 学生列表开放接口(示例)
    ├── service/                          # 开放接口业务层
    │   ├── IOpenApiStudentService.java   # 开放接口学生服务接口
    │   └── impl/
    │       └── OpenApiStudentServiceImpl.java  # 脱敏、数据转换等业务逻辑
    ├── domain/
    │   └── vo/
    │       └── OpenApiStudentVo.java     # 开放接口专用VO脱敏后的数据结构
    └── utils/
        └── DataMaskUtil.java             # 数据脱敏工具类

9.2 核心类设计

9.2.1 鉴权拦截器 (ApiAuthInterceptor.java)

@Slf4j
@Component
@RequiredArgsConstructor
public class ApiAuthInterceptor implements HandlerInterceptor {

    private static final String HEADER_APP_ID = "X-App-Id";
    private static final String HEADER_TIMESTAMP = "X-Timestamp";
    private static final String HEADER_SIGN = "X-Sign";
    private static final long TIMESTAMP_EXPIRE_MS = 5 * 60 * 1000L;  // 5分钟

    private final IPgApplicationService applicationService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 1. 参数校验
        String appId = request.getHeader(HEADER_APP_ID);
        String timestamp = request.getHeader(HEADER_TIMESTAMP);
        String sign = request.getHeader(HEADER_SIGN);
        if (StrUtil.isBlank(appId) || StrUtil.isBlank(timestamp) || StrUtil.isBlank(sign)) {
            throw new ServiceException("缺少认证参数");
        }

        // 2. 时间戳校验(防重放攻击)
        long reqTime = Long.parseLong(timestamp.trim());
        if (Math.abs(System.currentTimeMillis() - reqTime) > TIMESTAMP_EXPIRE_MS) {
            throw new ServiceException("请求已过期");
        }

        // 3. 应用状态校验
        PgApplication app = applicationService.selectByAppCode(appId.trim());
        if (app == null) {
            throw new ServiceException("应用不存在");
        }
        if ("1".equals(app.getStatus())) {
            throw new ServiceException("应用已停用");
        }

        // 4. 签名校验
        String expectedSign = buildSign(request, app.getAppSecret());
        if (!expectedSign.equalsIgnoreCase(sign.trim())) {
            throw new ServiceException("签名验证失败");
        }

        // 5. 接口权限校验
        String apiPath = request.getRequestURI();
        if (!applicationService.checkApiPermission(appId.trim(), apiPath)) {
            throw new ServiceException("无权访问该接口");
        }

        return true;
    }

    private String buildSign(HttpServletRequest request, String appSecret) {
        // 参数按 key ASCII 升序排序,拼接后追加 appSecretMD5 大写
        Map<String, String> params = new TreeMap<>();
        request.getParameterMap().forEach((key, values) -> {
            if (values != null && values.length > 0 && StrUtil.isNotBlank(values[0])) {
                params.put(key, values[0].trim());
            }
        });
        StringBuilder sb = new StringBuilder();
        params.forEach((k, v) -> sb.append(sb.length() > 0 ? "&" : "").append(k).append("=").append(v));
        sb.append("&appSecret=").append(appSecret);
        return DigestUtil.md5Hex(sb.toString()).toUpperCase();
    }
}

9.2.2 拦截器注册 (OpenApiWebMvcConfig.java)

@Configuration
@RequiredArgsConstructor
public class OpenApiWebMvcConfig implements WebMvcConfigurer {

    private final ApiAuthInterceptor apiAuthInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(apiAuthInterceptor)
                .addPathPatterns("/open/api/**");  // 仅对开放接口生效
    }
}

9.2.3 开放接口示例 (OpenApiStudentController.java)

@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/open/api/student")
public class OpenApiStudentController extends BaseController {

    private final IOpenApiStudentService openApiStudentService;  // 调用开放接口专用服务

    /**
     * 学生列表分页查询(对外开放接口)
     * 
     * 请求示例:
     * GET /open/api/student/list?pageNum=1&pageSize=10&studentName=张
     * Headers:
     *   X-App-Id: YY000001
     *   X-Timestamp: 1738656000000
     *   X-Sign: A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6
     */
    @GetMapping("/list")
    public TableDataInfo<OpenApiStudentVo> list(PgStudent student, PageQuery pageQuery) {
        return openApiStudentService.selectPageList(student, pageQuery);
    }

    /**
     * 学生详情查询(对外开放接口)
     */
    @GetMapping("/{studentId}")
    public R<OpenApiStudentVo> getInfo(@PathVariable Long studentId) {
        return R.ok(openApiStudentService.selectById(studentId));
    }
}

9.2.4 开放接口业务层 (OpenApiStudentService)

说明:开放接口单独创建 Service 层,用于处理脱敏、数据转换、字段过滤等业务逻辑,与内部业务服务解耦。

服务接口

/**
 * 开放接口学生服务接口
 */
public interface IOpenApiStudentService {
    
    /**
     * 分页查询学生列表(脱敏版本)
     */
    TableDataInfo<OpenApiStudentVo> selectPageList(PgStudent student, PageQuery pageQuery);
    
    /**
     * 查询学生详情(脱敏版本)
     */
    OpenApiStudentVo selectById(Long studentId);
}

服务实现

@Service
@RequiredArgsConstructor
public class OpenApiStudentServiceImpl implements IOpenApiStudentService {
    
    private final IPgStudentService studentService;  // 复用原有业务服务

    @Override
    public TableDataInfo<OpenApiStudentVo> selectPageList(PgStudent student, PageQuery pageQuery) {
        // 1. 调用原有业务服务获取数据
        TableDataInfo<StudentVo> result = studentService.selectPageList(student, pageQuery);
        
        // 2. 转换为开放接口VO并脱敏
        List<OpenApiStudentVo> openApiList = result.getRows().stream()
            .map(this::convertToOpenApiVo)
            .collect(Collectors.toList());
        
        // 3. 返回脱敏后的数据
        TableDataInfo<OpenApiStudentVo> openApiResult = new TableDataInfo<>();
        openApiResult.setRows(openApiList);
        openApiResult.setTotal(result.getTotal());
        return openApiResult;
    }
    
    @Override
    public OpenApiStudentVo selectById(Long studentId) {
        StudentVo student = studentService.selectById(studentId);
        return convertToOpenApiVo(student);
    }
    
    /**
     * 内部转换方法原始VO -> 开放接口VO脱敏处理
     */
    private OpenApiStudentVo convertToOpenApiVo(StudentVo source) {
        if (source == null) {
            return null;
        }
        
        OpenApiStudentVo vo = new OpenApiStudentVo();
        vo.setStudentId(source.getStudentId());
        vo.setStudentCode(source.getStudentCode());
        
        // 敏感字段脱敏
        vo.setStudentName(DataMaskUtil.maskName(source.getStudentName()));
        
        // 非敏感字段直接复制
        vo.setSchoolName(source.getSchoolName());
        vo.setGradeName(source.getGradeName());
        vo.setClassName(source.getClassName());
        
        // 敏感字段不对外暴露(身份证号、手机号等)
        // 不设置 idCard、phone 等字段
        
        return vo;
    }
}

9.2.5 开放接口专用 VO (OpenApiStudentVo.java)

@Data
@Schema(description = "开放接口学生信息")
public class OpenApiStudentVo implements Serializable {
    
    @Schema(description = "学生ID")
    private Long studentId;
    
    @Schema(description = "学生姓名(脱敏)")
    private String studentName;
    
    @Schema(description = "学号")
    private String studentCode;
    
    @Schema(description = "学校名称")
    private String schoolName;
    
    @Schema(description = "年级名称")
    private String gradeName;
    
    @Schema(description = "班级名称")
    private String className;
    
    // 注意:敏感字段如身份证号、手机号等不对外暴露
}

9.2.6 数据脱敏工具类 (DataMaskUtil.java)

/**
 * 数据脱敏工具类
 */
public class DataMaskUtil {
    
    /**
     * 姓名脱敏:张三 -> 张*
     */
    public static String maskName(String name) {
        if (StrUtil.isBlank(name) || name.length() == 1) {
            return name;
        }
        return name.charAt(0) + "*".repeat(name.length() - 1);
    }
    
    /**
     * 手机号脱敏13812345678 -> 138****5678
     */
    public static String maskPhone(String phone) {
        if (StrUtil.isBlank(phone) || phone.length() != 11) {
            return phone;
        }
        return phone.substring(0, 3) + "****" + phone.substring(7);
    }
    
    /**
     * 身份证脱敏110101199001011234 -> 110101********1234
     */
    public static String maskIdCard(String idCard) {
        if (StrUtil.isBlank(idCard) || idCard.length() < 8) {
            return idCard;
        }
        return idCard.substring(0, 6) + "********" + idCard.substring(idCard.length() - 4);
    }
}

9.3 前端代码结构

frontend/src/
├── api/pangu/
│   └── application.js                    # 应用管理API
├── views/application/
│   ├── index.vue                         # 应用列表页面
│   └── components/
│       ├── AppDialog.vue                 # 新增/编辑弹窗
│       ├── SecretDialog.vue              # 密钥展示弹窗
│       └── AuthDialog.vue                # 接口授权弹窗(新增)

10. 二次开发指南

10.1 新增开放接口步骤

步骤一:定义接口字典

在数据库 pg_api_dict 表中新增一条记录:

INSERT INTO pg_api_dict (api_id, api_code, api_name, api_path, api_method, api_desc, status, order_num)
VALUES (1700000000000000002, 'OPEN_TEACHER_LIST', '教师列表查询', '/open/api/teacher/list', 'GET', '分页查询教师信息', '0', 20);

步骤二:创建开放接口专用 VO

openapi/domain/vo 目录下新建 VO

@Data
@Schema(description = "开放接口教师信息")
public class OpenApiTeacherVo implements Serializable {
    
    @Schema(description = "教师ID")
    private Long teacherId;
    
    @Schema(description = "教师姓名(脱敏)")
    private String teacherName;
    
    @Schema(description = "教师工号")
    private String teacherCode;
    
    @Schema(description = "学校名称")
    private String schoolName;
    
    // 敏感字段不对外暴露
}

步骤三:创建开放接口服务层

openapi/service 目录下新建服务接口和实现类:

服务接口

public interface IOpenApiTeacherService {
    TableDataInfo<OpenApiTeacherVo> selectPageList(PgTeacher teacher, PageQuery pageQuery);
}

服务实现

@Service
@RequiredArgsConstructor
public class OpenApiTeacherServiceImpl implements IOpenApiTeacherService {
    
    private final IPgTeacherService teacherService;  // 复用原有业务服务

    @Override
    public TableDataInfo<OpenApiTeacherVo> selectPageList(PgTeacher teacher, PageQuery pageQuery) {
        // 1. 调用原有业务服务
        TableDataInfo<TeacherVo> result = teacherService.selectPageList(teacher, pageQuery);
        
        // 2. 转换并脱敏
        List<OpenApiTeacherVo> openApiList = result.getRows().stream()
            .map(this::convertToOpenApiVo)
            .collect(Collectors.toList());
        
        // 3. 返回
        TableDataInfo<OpenApiTeacherVo> openApiResult = new TableDataInfo<>();
        openApiResult.setRows(openApiList);
        openApiResult.setTotal(result.getTotal());
        return openApiResult;
    }
    
    private OpenApiTeacherVo convertToOpenApiVo(TeacherVo source) {
        if (source == null) return null;
        
        OpenApiTeacherVo vo = new OpenApiTeacherVo();
        vo.setTeacherId(source.getTeacherId());
        vo.setTeacherCode(source.getTeacherCode());
        vo.setTeacherName(DataMaskUtil.maskName(source.getTeacherName()));  // 脱敏
        vo.setSchoolName(source.getSchoolName());
        return vo;
    }
}

步骤四:创建接口控制器

openapi/controller 目录下新建控制器:

@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/open/api/teacher")
public class OpenApiTeacherController extends BaseController {

    private final IOpenApiTeacherService openApiTeacherService;  // 调用开放接口专用服务

    @GetMapping("/list")
    public TableDataInfo<OpenApiTeacherVo> list(PgTeacher teacher, PageQuery pageQuery) {
        return openApiTeacherService.selectPageList(teacher, pageQuery);
    }
}

步骤五:为应用授权

在应用管理界面,勾选新增的接口授权。

10.2 接口开发规范

  1. 路径规范:所有开放接口必须以 /open/api/ 开头
  2. 分层规范
    • Controller 层仅负责接收请求和返回响应
    • Service 层处理业务逻辑(脱敏、转换、字段过滤等)
    • 复用原有业务服务,避免重复代码
  3. VO 设计规范
    • 为开放接口单独创建 VO避免直接使用内部实体
    • VO 中只包含需要对外暴露的字段
    • 敏感字段在 Service 层进行脱敏处理
  4. 返回格式:使用统一的 R<T>TableDataInfo<T> 返回格式
  5. 参数校验:使用 @Validated 注解进行参数校验
  6. 日志记录:重要操作需记录日志
  7. 异常处理:业务异常使用 ServiceException

10.3 调用示例代码

Java 调用示例

public class OpenApiClient {
    private static final String BASE_URL = "http://your-domain.com";
    private static final String APP_CODE = "YY000001";
    private static final String APP_SECRET = "your-app-secret";

    public String callApi(String path, Map<String, String> params) throws Exception {
        // 1. 添加时间戳
        String timestamp = String.valueOf(System.currentTimeMillis());
        
        // 2. 计算签名
        TreeMap<String, String> sortedParams = new TreeMap<>(params);
        StringBuilder sb = new StringBuilder();
        sortedParams.forEach((k, v) -> sb.append(sb.length() > 0 ? "&" : "").append(k).append("=").append(v));
        sb.append("&appSecret=").append(APP_SECRET);
        String sign = DigestUtils.md5Hex(sb.toString()).toUpperCase();
        
        // 3. 构造请求
        HttpRequest request = HttpRequest.get(BASE_URL + path + "?" + buildQueryString(params))
            .header("X-App-Id", APP_CODE)
            .header("X-Timestamp", timestamp)
            .header("X-Sign", sign);
        
        return request.execute().body();
    }
}

Python 调用示例

import hashlib
import time
import requests

APP_CODE = "YY000001"
APP_SECRET = "your-app-secret"
BASE_URL = "http://your-domain.com"

def call_api(path, params):
    # 1. 添加时间戳
    timestamp = str(int(time.time() * 1000))
    
    # 2. 计算签名
    sorted_params = sorted(params.items())
    sign_str = "&".join([f"{k}={v}" for k, v in sorted_params])
    sign_str += f"&appSecret={APP_SECRET}"
    sign = hashlib.md5(sign_str.encode()).hexdigest().upper()
    
    # 3. 发送请求
    headers = {
        "X-App-Id": APP_CODE,
        "X-Timestamp": timestamp,
        "X-Sign": sign
    }
    response = requests.get(f"{BASE_URL}{path}", params=params, headers=headers)
    return response.json()

# 调用示例
result = call_api("/open/api/student/list", {"pageNum": "1", "pageSize": "10"})
print(result)

11. 部署与运维

11.1 SQL 脚本执行顺序

  1. pangu_tables.sql - 创建表结构
  2. pangu_menu.sql - 插入菜单数据
  3. open_api_dict_data.sql - 插入API字典初始数据

11.2 配置项

# application.yml
pangu:
  openapi:
    # 签名有效期(毫秒)
    timestamp-expire: 300000
    # 是否开启调用日志
    log-enabled: true

11.3 监控指标

指标 说明
接口调用次数 按应用、接口维度统计
调用成功率 成功/总调用
平均响应时间 接口性能监控
异常次数 签名失败、权限不足等

12. 文件清单

12.1 后端文件

文件 说明
application/controller/PgApplicationController.java 应用管理控制器
application/domain/PgApplication.java 第三方应用实体
application/domain/PgApiDict.java API字典实体
application/domain/PgAppApi.java 应用API授权实体
application/mapper/PgApplicationMapper.java 应用Mapper
application/mapper/PgApiDictMapper.java API字典Mapper
application/mapper/PgAppApiMapper.java 应用API授权Mapper
application/service/IPgApplicationService.java 应用服务接口
application/service/impl/PgApplicationServiceImpl.java 应用服务实现
openapi/config/ApiAuthInterceptor.java 开放API鉴权拦截器
openapi/config/OpenApiWebMvcConfig.java 拦截器配置
openapi/controller/OpenApiStudentController.java 学生列表开放接口
openapi/service/IOpenApiStudentService.java 开放接口学生服务接口
openapi/service/impl/OpenApiStudentServiceImpl.java 开放接口学生服务实现(脱敏、转换)
openapi/domain/vo/OpenApiStudentVo.java 开放接口专用VO
openapi/utils/DataMaskUtil.java 数据脱敏工具类

12.2 前端文件

文件 说明
api/pangu/application.js 应用管理API
views/application/index.vue 应用列表页面
views/application/components/AppDialog.vue 新增/编辑弹窗
views/application/components/SecretDialog.vue 密钥展示弹窗

12.3 SQL 文件

文件 说明
sql/pangu_tables.sql 表结构pg_application、pg_api_dict、pg_app_api
sql/pangu_menu.sql 菜单数据
sql/open_api_dict_data.sql API字典初始数据

附录

A. 术语表

术语 说明
AppCode 应用编码,唯一标识一个第三方应用
AppSecret 应用密钥,用于签名计算,需保密
开放接口 对外提供的数据查询接口,需鉴权后访问
接口授权 为应用配置可调用的接口列表

B. 修订历史

版本 日期 修订人 说明
V1.0 2026-02-04 pangu 初稿

文档结束