383 lines
9.0 KiB
Markdown
383 lines
9.0 KiB
Markdown
# 开放接口实现说明
|
||
|
||
## 📋 实现概述
|
||
|
||
已按照方案一(独立 OpenApi Service 层)完成了开放接口的完整实现,包括后端业务逻辑和前端管理界面。
|
||
|
||
---
|
||
|
||
## ✅ 已完成的工作
|
||
|
||
### 1️⃣ 后端实现
|
||
|
||
#### 核心文件清单
|
||
|
||
| 文件路径 | 说明 | 状态 |
|
||
|---------|------|------|
|
||
| `openapi/utils/DataMaskUtil.java` | 数据脱敏工具类 | ✅ 新建 |
|
||
| `openapi/domain/vo/OpenStudentVo.java` | 开放接口学生VO | ✅ 已优化 |
|
||
| `openapi/service/IOpenApiStudentService.java` | 开放接口服务接口 | ✅ 已存在 |
|
||
| `openapi/service/impl/OpenApiStudentServiceImpl.java` | 开放接口服务实现 | ✅ 已完善 |
|
||
| `openapi/controller/OpenApiStudentController.java` | 开放接口控制器 | ✅ 已更新 |
|
||
|
||
#### 实现细节
|
||
|
||
**1. DataMaskUtil 工具类**
|
||
|
||
```java
|
||
位置:org.dromara.pangu.openapi.utils.DataMaskUtil
|
||
|
||
提供的脱敏方法:
|
||
- maskName() // 姓名脱敏:张三 -> 张*
|
||
- maskPhone() // 手机号脱敏:13812345678 -> 138****5678
|
||
- maskIdCard() // 身份证脱敏:110101199001011234 -> 110101********1234
|
||
- maskEmail() // 邮箱脱敏:example@qq.com -> e****e@qq.com
|
||
- maskAddress() // 地址脱敏:保留省市区
|
||
```
|
||
|
||
**2. OpenApiStudentServiceImpl 服务实现**
|
||
|
||
核心逻辑:
|
||
```
|
||
1. 调用原有业务服务(IPgStudentService)获取数据
|
||
2. 在 Service 层进行数据转换(StudentVo -> OpenStudentVo)
|
||
3. 使用 DataMaskUtil 对敏感字段进行脱敏
|
||
4. 敏感字段不返回(身份证号、住址、会员信息等)
|
||
5. 返回开放接口专用 VO
|
||
```
|
||
|
||
**3. OpenApiStudentController 控制器**
|
||
|
||
接口定义:
|
||
```
|
||
GET /open/api/student/list
|
||
|
||
查询参数:
|
||
- studentName: 学生姓名(模糊查询)
|
||
- schoolId: 学校ID
|
||
- gradeId: 年级ID
|
||
- classId: 班级ID
|
||
- pageNum: 页码
|
||
- pageSize: 每页条数
|
||
|
||
返回数据:
|
||
{
|
||
"code": 200,
|
||
"msg": "操作成功",
|
||
"rows": [
|
||
{
|
||
"studentId": 1,
|
||
"studentCode": "S2024001",
|
||
"studentName": "张*", // 已脱敏
|
||
"gender": "1",
|
||
"schoolId": 1,
|
||
"schoolName": "第一小学",
|
||
"gradeId": 1,
|
||
"gradeName": "三年级",
|
||
"classId": 1,
|
||
"className": "1班"
|
||
}
|
||
],
|
||
"total": 100
|
||
}
|
||
```
|
||
|
||
#### 技术亮点
|
||
|
||
1. **分层清晰**
|
||
- Controller 只负责接收请求
|
||
- Service 处理业务逻辑(脱敏、转换)
|
||
- 复用原有 Service,避免重复代码
|
||
|
||
2. **数据安全**
|
||
- 敏感字段脱敏(姓名使用 maskName)
|
||
- 高敏感字段不返回(身份证、住址等)
|
||
- 使用专用 VO,与内部数据结构解耦
|
||
|
||
3. **易于扩展**
|
||
- 新增开放接口只需:
|
||
- 创建专用 VO
|
||
- 创建 Service 接口和实现
|
||
- 创建 Controller
|
||
- DataMaskUtil 可复用
|
||
|
||
---
|
||
|
||
### 2️⃣ 前端实现
|
||
|
||
#### 核心文件清单
|
||
|
||
| 文件路径 | 说明 | 状态 |
|
||
|---------|------|------|
|
||
| `views/application/index.vue` | 应用管理主页面 | ✅ 已优化 |
|
||
| `views/application/components/AppDialog.vue` | 新增/编辑弹窗 | ✅ 已优化 |
|
||
| `views/application/components/SecretDialog.vue` | 密钥展示弹窗 | ✅ 已存在 |
|
||
| `api/pangu/application.js` | 应用管理API | ✅ 已存在 |
|
||
|
||
#### 功能实现
|
||
|
||
**1. 应用列表页面**
|
||
|
||
功能点:
|
||
- ✅ 搜索:应用名称、应用编码、状态
|
||
- ✅ 列表展示:应用信息、授权接口、状态
|
||
- ✅ 新增应用
|
||
- ✅ 编辑应用
|
||
- ✅ 查看密钥
|
||
- ✅ 重置密钥(需二次确认)
|
||
- ✅ 删除应用(需二次确认)
|
||
- ✅ 分页功能
|
||
|
||
**2. 新增/编辑弹窗**
|
||
|
||
表单字段:
|
||
- 应用名称(必填)
|
||
- 应用编码(自动生成)
|
||
- 应用描述
|
||
- 联系人
|
||
- 联系电话(手机号格式校验)
|
||
- 状态(正常/停用)
|
||
- 接口授权(多选框,展示接口名称、路径、描述)
|
||
|
||
特性:
|
||
- ✅ 表单校验
|
||
- ✅ 接口授权选项动态加载
|
||
- ✅ 新增成功后自动显示密钥
|
||
- ✅ 编辑时回显数据
|
||
|
||
**3. 密钥展示弹窗**
|
||
|
||
功能:
|
||
- 展示应用名称、编码、密钥
|
||
- 一键复制密钥
|
||
- 安全提示(密钥重置后旧密钥失效)
|
||
|
||
---
|
||
|
||
## 🔍 使用示例
|
||
|
||
### 第三方应用调用示例
|
||
|
||
**1. Java 调用**
|
||
|
||
```java
|
||
// 1. 准备请求参数
|
||
Map<String, String> params = new TreeMap<>();
|
||
params.put("pageNum", "1");
|
||
params.put("pageSize", "10");
|
||
params.put("studentName", "张");
|
||
|
||
// 2. 计算签名
|
||
String timestamp = String.valueOf(System.currentTimeMillis());
|
||
StringBuilder signStr = new StringBuilder();
|
||
params.forEach((k, v) -> signStr.append(k).append("=").append(v).append("&"));
|
||
signStr.append("appSecret=").append(APP_SECRET);
|
||
String sign = DigestUtils.md5Hex(signStr.toString()).toUpperCase();
|
||
|
||
// 3. 发送请求
|
||
HttpRequest request = HttpRequest.get(BASE_URL + "/open/api/student/list")
|
||
.header("X-App-Id", APP_CODE)
|
||
.header("X-Timestamp", timestamp)
|
||
.header("X-Sign", sign)
|
||
.form(params);
|
||
|
||
String response = request.execute().body();
|
||
```
|
||
|
||
**2. Python 调用**
|
||
|
||
```python
|
||
import hashlib
|
||
import time
|
||
import requests
|
||
|
||
# 1. 准备参数
|
||
params = {
|
||
"pageNum": "1",
|
||
"pageSize": "10",
|
||
"studentName": "张"
|
||
}
|
||
|
||
# 2. 计算签名
|
||
timestamp = str(int(time.time() * 1000))
|
||
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}/open/api/student/list",
|
||
params=params,
|
||
headers=headers
|
||
)
|
||
print(response.json())
|
||
```
|
||
|
||
---
|
||
|
||
## 🧪 测试建议
|
||
|
||
### 后端测试
|
||
|
||
1. **单元测试 DataMaskUtil**
|
||
```java
|
||
@Test
|
||
public void testMaskName() {
|
||
assertEquals("张*", DataMaskUtil.maskName("张三"));
|
||
assertEquals("李**", DataMaskUtil.maskName("李四五"));
|
||
}
|
||
```
|
||
|
||
2. **集成测试 OpenApiStudentService**
|
||
- 测试分页查询
|
||
- 测试数据脱敏是否生效
|
||
- 测试敏感字段是否被过滤
|
||
|
||
3. **接口测试 OpenApiStudentController**
|
||
- 使用 Postman 或 curl 测试
|
||
- 验证鉴权是否生效
|
||
- 验证返回数据格式
|
||
|
||
### 前端测试
|
||
|
||
1. **功能测试**
|
||
- 应用列表加载
|
||
- 新增应用(含接口授权)
|
||
- 编辑应用
|
||
- 查看密钥
|
||
- 重置密钥
|
||
- 删除应用
|
||
|
||
2. **表单校验**
|
||
- 必填项校验
|
||
- 手机号格式校验
|
||
|
||
---
|
||
|
||
## 📝 后续扩展指南
|
||
|
||
### 新增开放接口(以教师接口为例)
|
||
|
||
**步骤一:创建专用 VO**
|
||
|
||
```java
|
||
// openapi/domain/vo/OpenTeacherVo.java
|
||
@Data
|
||
@Schema(description = "开放接口教师信息")
|
||
public class OpenTeacherVo implements Serializable {
|
||
private Long teacherId;
|
||
private String teacherName; // 已脱敏
|
||
private String teacherCode;
|
||
private String schoolName;
|
||
// 敏感字段不暴露
|
||
}
|
||
```
|
||
|
||
**步骤二:创建 Service 接口**
|
||
|
||
```java
|
||
// openapi/service/IOpenApiTeacherService.java
|
||
public interface IOpenApiTeacherService {
|
||
TableDataInfo<OpenTeacherVo> selectPageList(
|
||
String teacherName,
|
||
Long schoolId,
|
||
PageQuery pageQuery
|
||
);
|
||
}
|
||
```
|
||
|
||
**步骤三:创建 Service 实现**
|
||
|
||
```java
|
||
// openapi/service/impl/OpenApiTeacherServiceImpl.java
|
||
@Service
|
||
@RequiredArgsConstructor
|
||
public class OpenApiTeacherServiceImpl implements IOpenApiTeacherService {
|
||
|
||
private final IPgTeacherService teacherService; // 复用原有服务
|
||
|
||
@Override
|
||
public TableDataInfo<OpenTeacherVo> selectPageList(...) {
|
||
// 1. 调用原有服务
|
||
// 2. 转换并脱敏
|
||
// 3. 返回
|
||
}
|
||
|
||
private OpenTeacherVo convertToOpenApiVo(TeacherVo source) {
|
||
// 使用 DataMaskUtil 进行脱敏
|
||
}
|
||
}
|
||
```
|
||
|
||
**步骤四:创建 Controller**
|
||
|
||
```java
|
||
// openapi/controller/OpenApiTeacherController.java
|
||
@RestController
|
||
@RequestMapping("/open/api/teacher")
|
||
public class OpenApiTeacherController {
|
||
|
||
private final IOpenApiTeacherService openApiTeacherService;
|
||
|
||
@GetMapping("/list")
|
||
public TableDataInfo<OpenTeacherVo> list(...) {
|
||
return openApiTeacherService.selectPageList(...);
|
||
}
|
||
}
|
||
```
|
||
|
||
**步骤五:添加接口字典**
|
||
|
||
```sql
|
||
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);
|
||
```
|
||
|
||
**步骤六:在应用管理中授权**
|
||
|
||
前端应用管理页面 -> 编辑应用 -> 勾选"教师列表查询"接口
|
||
|
||
---
|
||
|
||
## 🎯 核心优势总结
|
||
|
||
1. **安全可控**
|
||
- 五重验证机制(参数、时间戳、应用状态、签名、接口权限)
|
||
- 敏感数据脱敏
|
||
- 高敏感字段不返回
|
||
|
||
2. **易于扩展**
|
||
- Service 层可复用
|
||
- DataMaskUtil 可复用
|
||
- 新增接口遵循统一规范
|
||
|
||
3. **便于维护**
|
||
- 分层清晰
|
||
- 代码解耦
|
||
- 文档完善
|
||
|
||
4. **用户友好**
|
||
- 前端界面直观
|
||
- 接口授权可视化
|
||
- 密钥管理便捷
|
||
|
||
---
|
||
|
||
## 📞 技术支持
|
||
|
||
如有问题,请参考:
|
||
1. 需求与技术设计方案文档
|
||
2. 代码注释
|
||
3. 本实现说明文档
|
||
|
||
---
|
||
|
||
*实现完成时间:2026-02-04*
|
||
*实现人员:pangu*
|