feat: 添加ERP服务和系统服务,完善员工门户功能
## 新增服务模块 ### 1. ERP服务 (hzhub-erp) - 新增独立的ERP数据适配服务 - 支持SQL Server 2008 R2数据源 - 提供动态API配置管理系统 - 包含客户管理、销售数据等业务接口 ### 2. 系统服务 (hzhub-system) - 新增独立的系统管理服务 - 用户、角色、权限、部门、菜单管理 - 租户管理、操作日志、在线用户监控 - 工作流引擎(warm-flow)集成 - 企业微信审批同步功能 ### 3. API网关 (hzhub-gateway) - 新增Spring Cloud Gateway网关服务 - JWT认证、路由分发、限流熔断 - XSS防护、请求日志记录 - 统一入口端口8080 ## 后台管理功能增强 ### ERP动态API管理 - 新增动态API配置管理界面 - API测试、文档预览、统计监控 - 错误日志查看、缓存管理 - 从数据库表自动导入API配置 ### 系统管理增强 - 企业微信配置管理 - 企业微信审批同步配置 - 部门和用户管理优化 ## 员工门户功能完善 ### 业务页面 - 审批中心:工作流审批、待办任务 - CRM管理:客户关系管理 - 经销商管理:经销商数据展示 - 供应链管理:采购、库存、销售 - BI报表:数据可视化分析 - ERP数据探索:SQL Server数据查询 ### 个人中心 - 基本设置:个人信息管理 - 安全设置:密码修改、登录日志 - 锁屏功能:自动锁屏、手动锁屏 ### 其他功能 - 标签页管理:多标签页导航 - 页面缓存:keepAlive缓存机制 - 会话超时:自动检测并提示 ## 经销商门户 ### 页面路由 - 新增经销商管理页面路由 - AI聊天界面完善 ## 文档更新 - ERP API数据库初始化指南 - ERP API前端完整实现文档 - ERP API测试和验证指南 - Gateway路由迁移计划 - 项目配置文档更新 ## 部署脚本 - 统一启动/停止/重启脚本 - Docker Compose配置优化 - Nginx配置文件更新 ## 技术栈 - 后端: Spring Boot 3.5.8, Java 17 - 前端: Vue 3, TypeScript, Element Plus, Vben Admin - 工作流: warm-flow 1.8.2 - 网关: Spring Cloud Gateway - 数据库: MySQL 8.0, SQL Server 2008 R2 - 缓存: Redis 7 - 向量库: Weaviate 1.25.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
120
docs/erp-api-database-init-guide.md
Normal file
120
docs/erp-api-database-init-guide.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# ERP API 管理平台 - 数据库初始化指南
|
||||
|
||||
## 执行步骤
|
||||
|
||||
### 方式一:通过 Docker MySQL 容器执行(推荐)
|
||||
|
||||
1. **进入 MySQL 容器**:
|
||||
```bash
|
||||
docker exec -it hzhub-mysql mysql -u root -phzhub123
|
||||
```
|
||||
|
||||
2. **切换到 hzhub 数据库**:
|
||||
```sql
|
||||
USE hzhub;
|
||||
```
|
||||
|
||||
3. **执行 SQL 文件**:
|
||||
```sql
|
||||
source /data/hzhub/hzhub-erp/docs/sql/erp_api_tables.sql;
|
||||
```
|
||||
|
||||
或者直接复制粘贴 SQL 内容执行。
|
||||
|
||||
### 方式二:通过 Navicat/DBeaver 等工具执行
|
||||
|
||||
1. 连接到 MySQL 数据库:
|
||||
- Host: localhost 或 192.168.120.60
|
||||
- Port: 3306
|
||||
- Database: hzhub
|
||||
- Username: root
|
||||
- Password: hzhub123
|
||||
|
||||
2. 打开 SQL 文件:`/data/hzhub/hzhub-erp/docs/sql/erp_api_tables.sql`
|
||||
|
||||
3. 执行整个 SQL 文件
|
||||
|
||||
### 方式三:通过管理后台执行(如果支持)
|
||||
|
||||
某些系统管理后台提供 SQL 执行功能,可以直接粘贴 SQL 执行。
|
||||
|
||||
---
|
||||
|
||||
## SQL 文件内容说明
|
||||
|
||||
该 SQL 文件包含:
|
||||
|
||||
### 1. 数据库表创建
|
||||
|
||||
**erp_api_config**:API 配置主表
|
||||
- 存储 API 基本信息、SQL 模板、权限配置、缓存配置等
|
||||
- 包含版本字段(api_version)支持 v1/v2 版本管理
|
||||
|
||||
**erp_api_param**:API 参数配置表
|
||||
- 存储 API 参数定义(名称、类型、位置、是否必填等)
|
||||
- 通过外键关联到 erp_api_config,级联删除
|
||||
|
||||
**erp_api_stats**:API 调用统计表
|
||||
- 记录每次 API 调用(调用时间、参数、响应时间、状态、错误信息)
|
||||
- 用于监控统计和错误分析
|
||||
|
||||
### 2. 菜单配置
|
||||
|
||||
在 sys_menu 表中插入 ERP 管理相关菜单:
|
||||
- **ERP管理**(一级菜单)- 目录
|
||||
- **API配置**(二级菜单)- API 配置管理页面
|
||||
- API查询、API新增、API修改、API删除、API测试、清除缓存(按钮权限)
|
||||
- **API监控**(二级菜单)- API 调用统计监控页面
|
||||
|
||||
---
|
||||
|
||||
## 验证 SQL 执行结果
|
||||
|
||||
执行完成后,验证表是否创建成功:
|
||||
|
||||
```sql
|
||||
-- 查看表结构
|
||||
SHOW TABLES LIKE 'erp_api_%';
|
||||
|
||||
-- 查看表数量(应该有 3 张表)
|
||||
SELECT COUNT(*) FROM information_schema.tables
|
||||
WHERE table_schema = 'hzhub' AND table_name LIKE 'erp_api_%';
|
||||
|
||||
-- 查看菜单是否插入成功
|
||||
SELECT menu_id, menu_name, parent_id, path, perms
|
||||
FROM sys_menu WHERE menu_name LIKE '%ERP%' OR menu_name LIKE '%API%';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 重要说明
|
||||
|
||||
1. **表依赖顺序**:必须先创建 erp_api_config,再创建 erp_api_param(有外键约束)
|
||||
2. **菜单插入**:sys_menu 表在 hzhub-system 服务管理的数据库中,菜单 SQL 需要在同一数据库执行
|
||||
3. **权限配置**:菜单权限使用 `erp:api:*` 格式,后续需要在角色管理中分配权限
|
||||
4. **备份建议**:首次在生产环境执行前,建议先备份数据库
|
||||
|
||||
---
|
||||
|
||||
## 下一步
|
||||
|
||||
SQL 执行成功后:
|
||||
|
||||
1. **重启 ERP 服务**:
|
||||
```bash
|
||||
cd /data/hzhub/hzhub-erp
|
||||
./restart.sh
|
||||
```
|
||||
|
||||
2. **验证服务启动**:
|
||||
```bash
|
||||
curl http://localhost:8082/actuator/health
|
||||
```
|
||||
|
||||
3. **测试 API 配置接口**(需要先登录管理后台获取 Token):
|
||||
```bash
|
||||
# 获取 Token 后,测试列表接口(会返回空列表)
|
||||
curl -H "Authorization: Bearer <token>" http://localhost:8080/erp/api/config/list
|
||||
```
|
||||
|
||||
4. **准备前端界面**:继续创建前端代码以使用管理界面操作
|
||||
168
docs/erp-api-frontend-complete.md
Normal file
168
docs/erp-api-frontend-complete.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# ERP API 管理平台 - 前端代码完成清单
|
||||
|
||||
## ✅ 已创建的前端文件
|
||||
|
||||
### API 定义文件(2个)
|
||||
- `/data/hzhub/hzhub-admin/apps/web-antd/src/api/erp/api/index.ts` - API 函数定义(完整)
|
||||
- 包含所有接口函数:list、info、add、edit、remove、test、preview、stats、errorLog、cache等
|
||||
- 完整的 TypeScript 类型定义
|
||||
|
||||
### 数据定义文件(1个)
|
||||
- `/data/hzhub/hzhub-admin/apps/web-antd/src/views/erp/api/data.tsx` - 列定义和表单 Schema
|
||||
- 搜索表单 Schema
|
||||
- 表格列定义
|
||||
- 各类选项配置(API方法、版本、结果类型、参数类型等)
|
||||
|
||||
### 主页面组件(2个)
|
||||
- `/data/hzhub/hzhub-admin/apps/web-antd/src/views/erp/api/index.vue` - **API配置列表页** ⭐
|
||||
- 标准的 VxeGrid 表格页面
|
||||
- 搜索、分页、工具栏(新增、删除、从表导入)
|
||||
- 操作列(测试、文档、编辑、删除、清缓存)
|
||||
- 状态切换 Switch
|
||||
- 权限控制 v-access:code
|
||||
|
||||
- `/data/hzhub/hzhub-admin/apps/web-antd/src/views/erp/stats/index.vue` - API监控页(占位符)
|
||||
|
||||
### 弹窗组件(2个)
|
||||
- `/data/hzhub/hzhub-admin/apps/web-antd/src/views/erp/api/test-modal.vue` - **API测试弹窗** ⭐
|
||||
- 参数输入表单(动态生成)
|
||||
- 执行按钮
|
||||
- 结果展示(JSON格式化)
|
||||
- 错误信息详情
|
||||
- 执行时间统计
|
||||
|
||||
- `/data/hzhub/hzhub-admin/apps/web-antd/src/views/erp/api/doc-preview-modal.vue` - 文档预览弹窗
|
||||
- 多Tab展示(基本信息、参数说明、SQL模板、使用示例)
|
||||
|
||||
### 编辑页组件(3个)
|
||||
- `/data/hzhub/hzhub-admin/apps/web-antd/src/views/erp/api/edit-api.vue` - **编辑页主文件** ⭐
|
||||
- 两Tab设计(基础设置 + 参数配置)
|
||||
- 数据加载和保存逻辑
|
||||
- provide/inject 数据共享
|
||||
|
||||
- `/data/hzhub/hzhub-admin/apps/web-antd/src/views/erp/api/edit-tabs/basic-setting.vue` - 基础设置 Tab
|
||||
- VbenForm 表单组件
|
||||
- 所有配置字段(API名称、路径、SQL模板、权限、缓存等)
|
||||
- 动态显示字段(分页参数、权限标识、缓存配置)
|
||||
|
||||
- `/data/hzhub/hzhub-admin/apps/web-antd/src/views/erp/api/edit-tabs/params-config.vue` - 参数配置 Tab
|
||||
- VxeGrid inline-editable 表格
|
||||
- 新增、删除参数功能
|
||||
- 参数字段编辑(名称、类型、位置、必填、默认值等)
|
||||
|
||||
---
|
||||
|
||||
## 📊 文件统计
|
||||
|
||||
**总计**:**9个前端文件**
|
||||
|
||||
**核心功能页面**:
|
||||
- 1个列表页(index.vue)
|
||||
- 1个编辑页(edit-api.vue + 2个Tab组件)
|
||||
- 2个弹窗(test-modal、doc-preview-modal)
|
||||
- 1个监控页(占位符)
|
||||
|
||||
**代码特点**:
|
||||
- ✅ 使用 Vben Admin 标准组件和模式
|
||||
- ✅ 完整的 TypeScript 类型定义
|
||||
- ✅ 权限控制集成(v-access:code)
|
||||
- ✅ 响应式表单和表格
|
||||
- ✅ CodeMirror JSON 展示
|
||||
- ✅ inline-editable 参数配置表格
|
||||
|
||||
---
|
||||
|
||||
## 🚀 下一步操作
|
||||
|
||||
### 刷新页面测试
|
||||
|
||||
现在所有前端代码已创建完成,请:
|
||||
|
||||
1. **刷新管理后台页面**(Ctrl+F5 或 Cmd+Shift+R)
|
||||
2. **重新登录**(如果需要)
|
||||
3. **点击左侧菜单**:"ERP管理 > API配置"
|
||||
4. **应该看到**:
|
||||
- 空列表页(表格正常显示)
|
||||
- 工具栏有"新增"、"从表导入"、"批量删除"按钮
|
||||
|
||||
### 功能测试清单
|
||||
|
||||
✅ **列表页测试**:
|
||||
- 表格正常显示
|
||||
- 搜索表单可用
|
||||
- 新增按钮跳转到编辑页
|
||||
|
||||
✅ **新增测试**:
|
||||
- 点击"新增"进入编辑页
|
||||
- 两Tab切换正常
|
||||
- 基础设置表单正常
|
||||
- 参数配置表格可编辑
|
||||
|
||||
✅ **保存测试**:
|
||||
- 填写必填字段(API名称、路径、SQL模板)
|
||||
- 点击"保存配置"
|
||||
- 返回列表页,能看到新创建的配置
|
||||
|
||||
✅ **测试功能**:
|
||||
- 在列表中点击"测试"
|
||||
- 弹窗正常打开
|
||||
- 输入参数(或空)
|
||||
- 点击"执行"
|
||||
- 查看执行结果和执行时间
|
||||
|
||||
---
|
||||
|
||||
## 可能遇到的问题
|
||||
|
||||
### 问题1: 页面仍然404
|
||||
|
||||
**原因**:前端服务未重启或缓存未清除
|
||||
|
||||
**解决**:
|
||||
```bash
|
||||
# 重启前端服务
|
||||
cd /data/hzhub/hzhub-admin/apps/web-antd
|
||||
pnpm dev
|
||||
|
||||
# 或清除浏览器缓存并刷新
|
||||
```
|
||||
|
||||
### 问题2: 编译错误
|
||||
|
||||
**原因**:可能缺少某些类型定义
|
||||
|
||||
**解决**:查看编译错误信息,我会立即修复
|
||||
|
||||
### 问题3: 路径错误
|
||||
|
||||
**原因**:路由配置问题(菜单已配置,但路由可能需要调整)
|
||||
|
||||
**解决**:检查菜单中的 `component` 字段是否正确映射到组件路径
|
||||
|
||||
---
|
||||
|
||||
## 📝 菜单配置确认
|
||||
|
||||
请确认 sys_menu 表中的配置:
|
||||
|
||||
```sql
|
||||
-- ERP管理(一级菜单)
|
||||
menu_name='ERP管理', path='/erp', component='ParentView'
|
||||
|
||||
-- API配置(二级菜单)
|
||||
menu_name='API配置', path='api', component='erp/api/index'
|
||||
|
||||
-- API监控(二级菜单)
|
||||
menu_name='API监控', path='stats', component='erp/stats/index'
|
||||
```
|
||||
|
||||
`component` 字段应该映射到:
|
||||
- `erp/api/index` → `/views/erp/api/index.vue`
|
||||
- `erp/stats/index` → `/views/erp/stats/index.vue`
|
||||
|
||||
---
|
||||
|
||||
**请刷新页面并告诉我结果!**
|
||||
|
||||
如果页面正常显示,我们将进入完整功能测试阶段。
|
||||
如果遇到问题,请告诉我具体错误信息,我会立即协助解决。🔧
|
||||
743
docs/erp-api-management-plan.md
Normal file
743
docs/erp-api-management-plan.md
Normal file
@@ -0,0 +1,743 @@
|
||||
# ERP API 管理平台实现方案
|
||||
|
||||
## Context
|
||||
|
||||
**需求背景**:用户需要一个界面来查看和管理 ERP 数据库的定制 API 封装,并提供手动创建 API 的功能。当前 ERP 服务只有少量固定的 API(CustomerController),缺乏灵活的 API 管理能力。
|
||||
|
||||
**重要决策**:**完全集成到管理后台(hzhub-admin)中**,作为系统管理的一个新模块,实现统一管理:
|
||||
- 前端界面集成在 hzhub-admin 的标准管理界面中(左侧菜单 + 右侧功能页面)
|
||||
- 通过后端菜单系统动态添加菜单项(类似"系统管理"、"工具"等模块)
|
||||
- 使用统一的权限体系(通过 Sa-Token 和 permission_code)
|
||||
- 使用统一的认证体系(JWT Token 共享)
|
||||
- 与现有的系统管理、代码生成器等模块保持一致的界面风格和操作体验
|
||||
|
||||
**设计目标**:
|
||||
1. 查看 ERP 数据库中的所有表结构
|
||||
2. 从数据库表一键生成 API 配置
|
||||
3. 手动创建和编辑 API(配置 SQL、参数、权限等)
|
||||
4. API 测试功能(在线测试执行)
|
||||
5. API 文档预览
|
||||
6. API 调用统计和监控(完整功能)
|
||||
7. Redis 缓存支持(完整功能)
|
||||
8. 版本管理支持(完整功能)
|
||||
|
||||
**参考架构**:
|
||||
- 基于代码生成器(gen_table + gen_table_column)的两表元数据管理模式
|
||||
- 使用 JdbcTemplate 执行动态 SQL(安全、简单)
|
||||
- 参考 Vben Admin 的标准 CRUD 界面模式
|
||||
|
||||
---
|
||||
|
||||
## 一、架构设计
|
||||
|
||||
### 1.1 数据库表设计
|
||||
|
||||
创建两张元数据表存储 API 配置:
|
||||
|
||||
**erp_api_config(API 配置主表)**:
|
||||
|
||||
```sql
|
||||
CREATE TABLE erp_api_config (
|
||||
api_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 'API ID',
|
||||
api_name VARCHAR(100) NOT NULL COMMENT 'API名称',
|
||||
api_path VARCHAR(200) NOT NULL COMMENT 'API路径(如 /erp/dynamic/customer/list)',
|
||||
api_method VARCHAR(10) NOT NULL DEFAULT 'GET' COMMENT 'HTTP方法(GET/POST)',
|
||||
api_desc VARCHAR(500) COMMENT 'API描述',
|
||||
api_version VARCHAR(10) DEFAULT 'v1' COMMENT 'API版本号(v1/v2)',
|
||||
|
||||
-- 数据源配置
|
||||
data_source VARCHAR(50) DEFAULT 'erp' COMMENT '数据源名称',
|
||||
|
||||
-- SQL配置
|
||||
sql_template TEXT NOT NULL COMMENT 'SQL模板(支持参数占位符 #{paramName})',
|
||||
result_type VARCHAR(20) NOT NULL DEFAULT 'LIST' COMMENT '结果类型(LIST/SINGLE/COUNT)',
|
||||
|
||||
-- 分页配置
|
||||
support_pagination TINYINT(1) DEFAULT 0 COMMENT '是否支持分页',
|
||||
page_param_name VARCHAR(50) DEFAULT 'pageNum' COMMENT '页码参数名',
|
||||
size_param_name VARCHAR(50) DEFAULT 'pageSize' COMMENT '页大小参数名',
|
||||
|
||||
-- 权限配置
|
||||
require_auth TINYINT(1) DEFAULT 0 COMMENT '是否需要认证',
|
||||
permission_code VARCHAR(100) COMMENT '权限标识(如 erp:customer:list)',
|
||||
|
||||
-- 缓存配置
|
||||
enable_cache TINYINT(1) DEFAULT 0 COMMENT '是否启用缓存',
|
||||
cache_key_template VARCHAR(200) COMMENT '缓存键模板(支持参数占位符)',
|
||||
cache_ttl INT DEFAULT 300 COMMENT '缓存过期时间(秒)',
|
||||
|
||||
-- 来源表信息
|
||||
source_table VARCHAR(100) COMMENT '来源表名',
|
||||
source_table_comment VARCHAR(500) COMMENT '来源表描述',
|
||||
|
||||
-- 状态
|
||||
status TINYINT(1) DEFAULT 1 COMMENT '状态(0禁用 1启用)',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
create_by VARCHAR(50),
|
||||
update_by VARCHAR(50),
|
||||
remark VARCHAR(500)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ERP动态API配置表';
|
||||
|
||||
CREATE INDEX idx_api_path_method ON erp_api_config(api_path, api_method, api_version);
|
||||
CREATE INDEX idx_status ON erp_api_config(status);
|
||||
```
|
||||
|
||||
**erp_api_param(API 参数配置表)**:
|
||||
|
||||
```sql
|
||||
CREATE TABLE erp_api_param (
|
||||
param_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
api_id BIGINT NOT NULL COMMENT '所属API ID',
|
||||
|
||||
-- 参数基本信息
|
||||
param_name VARCHAR(100) NOT NULL COMMENT '参数名称',
|
||||
param_desc VARCHAR(500) COMMENT '参数描述',
|
||||
param_type VARCHAR(20) NOT NULL DEFAULT 'String' COMMENT '参数类型(String/Integer/Long/Date/Boolean)',
|
||||
|
||||
-- 参数位置
|
||||
param_position VARCHAR(20) NOT NULL DEFAULT 'QUERY' COMMENT '参数位置(QUERY/BODY)',
|
||||
|
||||
-- 参数验证
|
||||
is_required TINYINT(1) DEFAULT 0 COMMENT '是否必填',
|
||||
default_value VARCHAR(200) COMMENT '默认值',
|
||||
|
||||
-- SQL映射
|
||||
sql_param_name VARCHAR(100) COMMENT 'SQL参数名',
|
||||
|
||||
-- 排序
|
||||
sort INT DEFAULT 0,
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ERP动态API参数配置表';
|
||||
|
||||
CREATE INDEX idx_api_id ON erp_api_param(api_id);
|
||||
ALTER TABLE erp_api_param ADD CONSTRAINT fk_api_param_api
|
||||
FOREIGN KEY (api_id) REFERENCES erp_api_config(api_id) ON DELETE CASCADE;
|
||||
```
|
||||
|
||||
**erp_api_stats(API 调用统计表)**:
|
||||
|
||||
```sql
|
||||
CREATE TABLE erp_api_stats (
|
||||
stats_id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
api_id BIGINT NOT NULL COMMENT 'API ID',
|
||||
|
||||
-- 调用信息
|
||||
call_time DATETIME NOT NULL COMMENT '调用时间',
|
||||
call_params TEXT COMMENT '调用参数(JSON)',
|
||||
response_time INT COMMENT '响应时间(ms)',
|
||||
call_status VARCHAR(10) COMMENT '调用状态(SUCCESS/ERROR)',
|
||||
|
||||
-- 错误信息
|
||||
error_message TEXT COMMENT '错误消息',
|
||||
error_stack TEXT COMMENT '错误堆栈',
|
||||
|
||||
-- 客户端信息
|
||||
client_ip VARCHAR(50) COMMENT '客户端IP',
|
||||
user_id VARCHAR(50) COMMENT '用户ID',
|
||||
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ERP动态API调用统计表';
|
||||
|
||||
CREATE INDEX idx_api_id_time ON erp_api_stats(api_id, call_time);
|
||||
CREATE INDEX idx_call_status ON erp_api_stats(call_status);
|
||||
```
|
||||
|
||||
### 1.2 核心技术选型
|
||||
|
||||
**动态 SQL 执行**:JdbcTemplate
|
||||
- 优点:简单、安全、支持 PreparedStatement 参数绑定
|
||||
- 避免复杂的 MyBatis 动态 SQL(不适合动态生成场景)
|
||||
|
||||
**参数类型转换**:自定义 ParamTypeConverter
|
||||
- 支持 String、Integer、Long、Date、Boolean 类型
|
||||
- 自动转换前端参数到 SQL 参数类型
|
||||
|
||||
**结果映射**:动态 RowMapper → Map<String, Object>
|
||||
- 避免硬编码 VO 类,灵活适配不同表结构
|
||||
|
||||
**安全防护**:
|
||||
- SQL 注入防护:强制 PreparedStatement 参数绑定(#{param} → ?)
|
||||
- SQL 关键字白名单:只允许 SELECT、FROM、WHERE、AND、OR、ORDER、BY 等安全关键字
|
||||
- 禁止危险操作:DROP、DELETE、TRUNCATE、ALTER、CREATE 等
|
||||
- 可选权限控制:通过 Sa-Token 的 permission_code 验证
|
||||
|
||||
### 1.3 后端架构
|
||||
|
||||
**文件结构**:
|
||||
|
||||
```
|
||||
hzhub-erp/src/main/java/org/hzhub/erp/
|
||||
├── controller/
|
||||
│ ├── ErpApiController.java # API 配置管理 CRUD
|
||||
│ └── DynamicApiController.java # 动态 API 执行入口(通配符路由)
|
||||
├── service/
|
||||
│ ├── IErpApiService.java # API 配置 Service 接口
|
||||
│ ├── impl/
|
||||
│ │ ├── ErpApiServiceImpl.java # API 配置管理 + 从表导入
|
||||
│ │ ├── DynamicApiExecutor.java # 核心 SQL 执行引擎
|
||||
│ │ ├── ApiCacheService.java # Redis 缓存服务
|
||||
│ │ └── ApiMonitorService.java # 监控统计服务
|
||||
│ └── ParamTypeConverter.java # 参数类型转换器
|
||||
├── domain/
|
||||
│ ├── ErpApiConfig.java # API 配置实体
|
||||
│ ├── ErpApiParam.java # API 参数实体
|
||||
│ └── vo/
|
||||
│ │ ├── ErpApiConfigVO.java # API 配置 VO
|
||||
│ │ └── ApiTestResultVO.java # API 测试结果 VO
|
||||
├── mapper/
|
||||
│ ├── ErpApiConfigMapper.java # API 配置 Mapper
|
||||
│ ├── ErpApiParamMapper.java # API 参数 Mapper
|
||||
│ └── ErpApiStatsMapper.java # API 统计 Mapper
|
||||
└── util/
|
||||
└── SqlValidator.java # SQL 安全验证工具
|
||||
```
|
||||
|
||||
**关键模块设计**:
|
||||
|
||||
1. **DynamicApiExecutor(核心执行引擎)**:
|
||||
- 处理 SQL 模板:将 #{param} 转换为 ? 占位符
|
||||
- 参数绑定:按照参数顺序提取值并绑定到 PreparedStatement
|
||||
- 分页处理:自动添加 OFFSET FETCH(SQL Server 语法)
|
||||
- 结果封装:LIST → TableDataInfo,SINGLE → R,COUNT → R<Long>
|
||||
- 缓存检查:检查 Redis 缓存,如果存在直接返回
|
||||
- 统计记录:记录执行时间、调用参数到 erp_api_stats
|
||||
|
||||
2. **ApiCacheService(缓存服务)**:
|
||||
- 缓存键生成:根据 cache_key_template 和参数生成缓存键
|
||||
- 缓存操作:get、set、delete、clear
|
||||
- 缓存过期处理:支持 TTL 配置
|
||||
- 缓存命中率统计:记录缓存命中和未命中次数
|
||||
|
||||
3. **ApiMonitorService(监控服务)**:
|
||||
- 调用记录:记录每次调用到 erp_api_stats
|
||||
- 统计查询:按 API ID、时间段查询调用统计
|
||||
- 错误分析:查询错误日志、错误率统计
|
||||
- 性能分析:响应时间趋势、慢查询告警
|
||||
|
||||
4. **ErpApiController(配置管理)**:
|
||||
- CRUD 接口:list、getInfo、add、edit、remove
|
||||
- 从表导入:importFromTable(调用 ErpExploreController 获取表结构)
|
||||
- API 测试:testApi(执行并返回结果 + 执行时间)
|
||||
- 状态切换:changeStatus
|
||||
- 统计查询:getApiStats、getApiErrorLog
|
||||
- 版本管理:支持 v1/v2 版本号,路径自动包含版本(/erp/dynamic/v1/xxx)
|
||||
|
||||
5. **DynamicApiController(动态路由)**:
|
||||
- 通配符路径:`@GetMapping("/erp/dynamic/{version}/{apiPath}")`
|
||||
- 根据请求路径、版本和方法查询 API 配置
|
||||
- 权限检查(如果 require_auth = 1)
|
||||
- 参数验证和类型转换
|
||||
- 缓存检查(如果 enable_cache = 1)
|
||||
- 调用 DynamicApiExecutor 执行
|
||||
- 记录统计信息
|
||||
|
||||
---
|
||||
|
||||
## 二、功能模块详细设计
|
||||
|
||||
### 2.1 API 配置管理(CRUD)
|
||||
|
||||
**后端接口**:
|
||||
|
||||
| Method | Path | 功能 |
|
||||
|--------|------|------|
|
||||
| GET | `/erp/api/config/list` | 分页查询 API 配置列表 |
|
||||
| GET | `/erp/api/config/{apiId}` | 获取配置详情(含参数列表) |
|
||||
| POST | `/erp/api/config` | 新增 API 配置 |
|
||||
| PUT | `/erp/api/config` | 修改 API 配置 |
|
||||
| DELETE | `/erp/api/config/{apiIds}` | 删除配置 |
|
||||
| PUT | `/erp/api/config/changeStatus` | 启用/禁用 |
|
||||
| POST | `/erp/api/config/importFromTable` | 从表导入 |
|
||||
| GET | `/erp/api/config/syncTable/{apiId}` | 同步表结构 |
|
||||
| POST | `/erp/api/config/test/{apiId}` | API 测试 |
|
||||
| GET | `/erp/api/config/preview/{apiId}` | API 文档预览 |
|
||||
| GET | `/erp/api/config/stats/{apiId}` | 查询调用统计 |
|
||||
| GET | `/erp/api/config/errorLog/{apiId}` | 查询错误日志 |
|
||||
| DELETE | `/erp/api/config/cache/{apiId}` | 清除缓存 |
|
||||
|
||||
### 2.2 从数据库表导入
|
||||
|
||||
**流程**:
|
||||
1. 前端调用 ErpExploreController 获取表列表(`/erp/test/explore`)
|
||||
2. 用户选择要导入的表(多选)
|
||||
3. 后端 `importFromTable` 方法:
|
||||
- 查询表的列信息(调用 `/erp/test/explore/table?tableName=xxx`)
|
||||
- 生成默认查询 SQL:`SELECT * FROM tableName WHERE 1=1`
|
||||
- 为每个列创建参数配置(paramName、paramType、paramDesc)
|
||||
- 保存到 erp_api_config 和 erp_api_param
|
||||
|
||||
**自动生成逻辑**:
|
||||
- API 名称:`{tableName}列表查询`
|
||||
- API 路径:`/erp/dynamic/{tableName}`
|
||||
- SQL 类型:根据列数据类型推断 Java 类型(varchar → String, int → Integer, datetime → Date)
|
||||
- 支持分页:默认启用
|
||||
|
||||
### 2.3 API 测试功能
|
||||
|
||||
**设计**:
|
||||
- 输入:apiId + testParams(Map<String, Object>)
|
||||
- 输出:ApiTestResultVO(包含执行结果、执行时间、SQL、错误信息)
|
||||
|
||||
**ApiTestResultVO 结构**:
|
||||
```java
|
||||
public class ApiTestResultVO {
|
||||
private String apiPath;
|
||||
private String testMethod;
|
||||
private Map<String, Object> requestParams;
|
||||
private Boolean success;
|
||||
private Object data; // 执行结果
|
||||
private Long executionTime; // 执行时间(ms)
|
||||
private String executedSql; // 实际执行的 SQL
|
||||
private String errorMessage; // 错误信息(如果失败)
|
||||
private String errorStack; // 错误堆栈
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 API 文档预览
|
||||
|
||||
**生成内容**:
|
||||
- 基本信息:API 名称、路径、方法、描述
|
||||
- 参数说明表:参数名、类型、必填、默认值、描述
|
||||
- SQL 模板说明
|
||||
- 响应格式示例
|
||||
- 使用示例(请求 URL + 参数示例)
|
||||
|
||||
---
|
||||
|
||||
## 三、前端界面设计
|
||||
|
||||
### 3.1 文件结构
|
||||
|
||||
```
|
||||
hzhub-admin/apps/web-antd/src/
|
||||
├── api/erp/
|
||||
│ ├── api/
|
||||
│ │ ├── index.ts # API 函数定义
|
||||
│ │ └── model.d.ts # TypeScript 类型
|
||||
│ └── explore/
|
||||
│ │ ├── index.ts # 数据库探查 API
|
||||
│ │ └── model.d.ts
|
||||
├── views/erp/api/
|
||||
│ ├── index.vue # API 配置列表页
|
||||
│ ├── data.tsx # 列定义、搜索表单 schema
|
||||
│ ├── edit-api.vue # 编辑页(两 Tab)
|
||||
│ ├── edit-tabs/
|
||||
│ │ ├── basic-setting.vue # 基础设置 Tab
|
||||
│ │ └── params-config.vue # 参数配置 Tab(VxeGrid inline-editable)
|
||||
│ ├── table-import-modal.vue # 从表导入弹窗
|
||||
│ ├── test-modal.vue # API 测试弹窗
|
||||
│ ├── doc-preview-modal.vue # 文档预览弹窗
|
||||
│ └── stats-modal.vue # 统计监控弹窗
|
||||
```
|
||||
|
||||
### 3.2 列表页(index.vue)
|
||||
|
||||
**参考**:tenant/index.vue(完整 CRUD)
|
||||
|
||||
**功能**:
|
||||
- VxeGrid 表格展示 API 配置(apiName、apiPath、apiMethod、status、createTime)
|
||||
- 搜索表单:apiName、apiPath、status
|
||||
- 操作列:测试、编辑、删除、启用/禁用、查看统计
|
||||
- 工具栏:从表导入、新增 API
|
||||
|
||||
### 3.3 编辑页(edit-api.vue)
|
||||
|
||||
**参考**:generator/edit-gen.vue(两 Tab 设计)
|
||||
|
||||
**Tab 1 - 基础设置**:
|
||||
- API 名称、路径、方法(下拉选择 GET/POST)
|
||||
- SQL 模板(使用 CodeMirror 编辑器,支持 SQL Server 语法高亮)
|
||||
- 数据源(下拉选择)
|
||||
- 结果类型(LIST/SINGLE/COUNT)
|
||||
- 分页配置(是否支持分页、参数名)
|
||||
- 权限配置(是否需要认证、权限标识)
|
||||
- 缓存配置(是否启用缓存、缓存键模板、TTL)
|
||||
- 版本号(下拉选择 v1/v2)
|
||||
- 来源表信息(只读,显示来源表名和描述)
|
||||
|
||||
**Tab 2 - 参数配置**:
|
||||
- VxeGrid inline-editable 表格
|
||||
- 列:paramName、paramType(下拉)、paramPosition(下拉)、isRequired(复选框)、defaultValue、paramDesc
|
||||
- 工具栏:新增参数、删除参数、同步表结构
|
||||
|
||||
### 3.4 从表导入弹窗(table-import-modal.vue)
|
||||
|
||||
**参考**:generator/table-import-modal.vue
|
||||
|
||||
**功能**:
|
||||
- 调用 `/erp/test/explore` 获取表列表
|
||||
- VxeGrid 表格展示:tableName、schemaName、rowCount、columnCount、hasPrimaryKey
|
||||
- 多选框选择要导入的表
|
||||
- 确认导入:调用 `/erp/api/config/importFromTable`
|
||||
|
||||
### 3.5 API 测试弹窗(test-modal.vue)
|
||||
|
||||
**设计**:
|
||||
- 上半部分:参数输入表单(根据参数配置动态生成)
|
||||
- 下半部分:执行结果展示
|
||||
- 元信息:执行时间、状态(成功/失败)
|
||||
- 结果数据:JSON 格式化展示(使用 CodeMirror)
|
||||
- 错误信息:如果失败,显示错误消息和堆栈
|
||||
|
||||
### 3.6 文档预览弹窗(doc-preview-modal.vue)
|
||||
|
||||
**参考**:generator/code-preview-modal.vue(多 Tab 展示)
|
||||
|
||||
**Tab 内容**:
|
||||
- API 说明:名称、路径、方法、描述
|
||||
- 参数说明:表格形式展示参数列表
|
||||
- SQL 模板:代码高亮显示
|
||||
- 响应示例:JSON 示例
|
||||
- 使用示例:完整的请求 URL 和参数示例
|
||||
|
||||
### 3.7 统计监控弹窗(stats-modal.vue)
|
||||
|
||||
**设计**:
|
||||
- 上部分:统计概览(总调用次数、平均响应时间、错误率)
|
||||
- 中部分:调用次数趋势图(使用 Charts)
|
||||
- 下部分:响应时间趋势图
|
||||
- 错误日志列表:最近的错误调用记录
|
||||
|
||||
---
|
||||
|
||||
## 四、SQL 模板设计
|
||||
|
||||
### 4.1 参数占位符语法
|
||||
|
||||
使用 `#{paramName}` 作为参数占位符,自动转换为 PreparedStatement 的 `?`:
|
||||
|
||||
**示例 SQL 模板**:
|
||||
|
||||
```sql
|
||||
-- 简单查询
|
||||
SELECT * FROM customer
|
||||
WHERE #{customerCode} IS NOT NULL THEN customer_code = #{customerCode}
|
||||
AND #{companyCode} IS NOT NULL THEN company_code = #{companyCode}
|
||||
|
||||
-- 带分页查询
|
||||
SELECT customer_code, customer_name, company_code
|
||||
FROM customer
|
||||
WHERE #{keyword} IS NOT NULL THEN customer_name LIKE '%#{keyword}%'
|
||||
ORDER BY customer_code
|
||||
-- 分页参数自动添加:OFFSET #{pageNum} ROWS FETCH NEXT #{pageSize} ROWS ONLY
|
||||
|
||||
-- 聚合查询
|
||||
SELECT COUNT(*) AS total
|
||||
FROM customer
|
||||
WHERE #{salesAreaCode} IS NOT NULL THEN sales_area_code = #{salesAreaCode}
|
||||
```
|
||||
|
||||
### 4.2 SQL 安全验证
|
||||
|
||||
**白名单关键字**:
|
||||
```java
|
||||
Set<String> ALLOWED_KEYWORDS = Set.of(
|
||||
"SELECT", "FROM", "WHERE", "AND", "OR", "ORDER", "BY", "GROUP", "HAVING",
|
||||
"OFFSET", "FETCH", "NEXT", "ROWS", "ONLY", "AS", "COUNT", "SUM", "AVG"
|
||||
);
|
||||
```
|
||||
|
||||
**禁止关键字**:
|
||||
```java
|
||||
Set<String> DANGEROUS_KEYWORDS = Set.of(
|
||||
"DROP", "DELETE", "TRUNCATE", "ALTER", "CREATE", "GRANT", "INSERT", "UPDATE"
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、安全考虑
|
||||
|
||||
### 5.1 SQL 注入防护
|
||||
|
||||
1. **强制参数绑定**:禁止拼接 SQL,必须使用 PreparedStatement
|
||||
2. **关键字验证**:执行前验证 SQL 只包含白名单关键字
|
||||
3. **参数值验证**:基于配置的正则表达式验证参数格式
|
||||
|
||||
### 5.2 权限控制
|
||||
|
||||
1. **可选认证**:通过 `require_auth` 字段控制
|
||||
2. **权限标识**:通过 `permission_code` 配置(如 `erp:customer:list`)
|
||||
3. **Sa-Token 验证**:
|
||||
```java
|
||||
if (config.getRequireAuth() == 1) {
|
||||
StpUtil.checkPermission(config.getPermissionCode());
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 访问控制
|
||||
|
||||
- 所有动态 API 默认需要通过网关验证(`X-Gateway-Verified` header)
|
||||
- 可选启用 Sa-Token 权限检查
|
||||
- 支持 RateLimiter 限流(后续添加)
|
||||
|
||||
---
|
||||
|
||||
## 六、实现步骤(分阶段)
|
||||
|
||||
根据用户确认的设计决策:
|
||||
- **路由机制**:使用通配符路由(`/erp/dynamic/{version}/{apiPath}`)
|
||||
- **SQL 编辑器**:基础版本(CodeMirror + 语法高亮)
|
||||
- **高级功能**:第一版实现完整功能(缓存、监控、版本管理)
|
||||
|
||||
### Phase 1:核心功能 + 基础增强(第 1-2 周)
|
||||
|
||||
**目标**:实现最小可用版本 + 基础高级功能
|
||||
|
||||
1. **数据库表创建**(hzhub-erp):
|
||||
- 创建 erp_api_config 和 erp_api_param 表(包含缓存、版本字段)
|
||||
- 创建 erp_api_stats 表(监控统计)
|
||||
- 添加索引和外键约束
|
||||
|
||||
2. **后端基础架构**:
|
||||
- 创建 Domain 实体类(ErpApiConfig、ErpApiParam、ErpApiStats)
|
||||
- 创建 Mapper 接口和 XML(基础 CRUD)
|
||||
- 创建 Service 接口和实现(ErpApiServiceImpl)
|
||||
- 创建 DynamicApiExecutor(核心 SQL 执行引擎)
|
||||
- 创建 ParamTypeConverter(参数类型转换)
|
||||
- 创建 ApiCacheService(Redis 缓存服务)
|
||||
- 创建 ApiMonitorService(调用统计服务)
|
||||
|
||||
3. **Controller 实现**:
|
||||
- ErpApiController(CRUD 接口 + 缓存配置 + 版本管理)
|
||||
- DynamicApiController(通配符路由 + 缓存检查 + 统计记录)
|
||||
|
||||
4. **前端基础页面**:
|
||||
- 列表页(index.vue)
|
||||
- 基础的增删改查功能
|
||||
- CodeMirror SQL 编辑器(语法高亮)
|
||||
|
||||
### Phase 2:导入和编辑功能 + 版本管理(第 3 周)
|
||||
|
||||
**目标**:完善配置管理功能 + 版本支持
|
||||
|
||||
1. **从表导入**:
|
||||
- Service:importFromTable 方法
|
||||
- SQL 模板自动生成
|
||||
- 参数自动生成
|
||||
|
||||
2. **编辑页**:
|
||||
- 两 Tab 设计(基础设置 + 参数配置)
|
||||
- VxeGrid inline-editable 参数编辑
|
||||
- SQL 编辑器(CodeMirror + SQL Server 语法高亮)
|
||||
- 版本管理字段(api_version,支持 v1/v2)
|
||||
|
||||
3. **前端弹窗**:
|
||||
- 从表导入弹窗
|
||||
- 基础设置表单(包含缓存配置)
|
||||
- 版本选择下拉框
|
||||
|
||||
### Phase 3:测试和文档 + 监控统计(第 4 周)
|
||||
|
||||
**目标**:增强可用性 + 监控功能
|
||||
|
||||
1. **API 测试功能**:
|
||||
- Service:testApi 方法
|
||||
- 执行日志和错误处理
|
||||
- 前端测试弹窗
|
||||
|
||||
2. **API 文档预览**:
|
||||
- Service:generateApiDoc 方法
|
||||
- 前端文档预览弹窗
|
||||
|
||||
3. **监控统计功能**:
|
||||
- 创建 erp_api_stats 表(记录调用次数、响应时间、错误率)
|
||||
- ApiMonitorService:记录每次调用(调用时间、参数、响应时间)
|
||||
- 统计接口:getApiStats、getApiErrorLog
|
||||
- 前端统计页面:调用次数图表、响应时间趋势
|
||||
|
||||
4. **优化体验**:
|
||||
- JSON 格式化展示
|
||||
- 执行时间统计
|
||||
- 调用统计可视化
|
||||
|
||||
### Phase 4:安全增强和稳定性(第 5 周)
|
||||
|
||||
**目标**:提升安全性和稳定性
|
||||
|
||||
1. **安全增强**:
|
||||
- SQL 安全验证(SqlValidator)
|
||||
- 权限控制集成
|
||||
- 参数验证
|
||||
|
||||
2. **缓存优化**:
|
||||
- Redis 缓存键生成策略
|
||||
- 缓存过期处理(TTL)
|
||||
- 缓存命中率统计
|
||||
|
||||
3. **同步表结构**:
|
||||
- syncTableStructure 方法
|
||||
|
||||
4. **状态管理**:
|
||||
- 启用/禁用功能
|
||||
- API 状态验证
|
||||
|
||||
5. **监控告警**:
|
||||
- 响应时间阈值告警
|
||||
- 错误率告警
|
||||
- 调用异常记录
|
||||
|
||||
---
|
||||
|
||||
## 七、关键文件清单
|
||||
|
||||
### 后端关键文件(需创建)
|
||||
|
||||
1. `/data/hzhub/hzhub-erp/src/main/java/org/hzhub/erp/service/impl/DynamicApiExecutor.java` - 核心 SQL 执行引擎
|
||||
2. `/data/hzhub/hzhub-erp/src/main/java/org/hzhub/erp/service/impl/ApiCacheService.java` - Redis 缓存服务
|
||||
3. `/data/hzhub/hzhub-erp/src/main/java/org/hzhub/erp/service/impl/ApiMonitorService.java` - 监控统计服务
|
||||
4. `/data/hzhub/hzhub-erp/src/main/java/org/hzhub/erp/controller/DynamicApiController.java` - 动态路由入口
|
||||
5. `/data/hzhub/hzhub-erp/src/main/java/org/hzhub/erp/service/impl/ErpApiServiceImpl.java` - API 配置管理核心业务
|
||||
6. `/data/hzhub/hzhub-erp/src/main/java/org/hzhub/erp/controller/ErpApiController.java` - API 配置管理 Controller
|
||||
7. `/data/hzhub/hzhub-erp/src/main/java/org/hzhub/erp/util/SqlValidator.java` - SQL 安全验证工具
|
||||
|
||||
### 前端关键文件(需创建)
|
||||
|
||||
1. `/data/hzhub/hzhub-admin/apps/web-antd/src/views/erp/api/index.vue` - API 配置列表页
|
||||
2. `/data/hzhub/hzhub-admin/apps/web-antd/src/views/erp/api/edit-api.vue` - 编辑页(两 Tab)
|
||||
3. `/data/hzhub/hzhub-admin/apps/web-antd/src/views/erp/api/test-modal.vue` - API 测试弹窗
|
||||
4. `/data/hzhub/hzhub-admin/apps/web-antd/src/views/erp/api/stats-modal.vue` - 统计监控弹窗
|
||||
5. `/data/hzhub/hzhub-admin/apps/web-antd/src/api/erp/api/index.ts` - API 函数定义
|
||||
|
||||
### 需复用的现有文件
|
||||
|
||||
- `/data/hzhub/hzhub-erp/src/main/java/org/hzhub/erp/controller/ErpExploreController.java` - 数据库探查功能
|
||||
- `/data/hzhub/hzhub-admin/apps/web-antd/src/views/tool/gen/edit-gen.vue` - 编辑页设计参考
|
||||
- `/data/hzhub/hzhub-admin/apps/web-antd/src/views/system/tenant/index.vue` - CRUD 页面参考
|
||||
|
||||
---
|
||||
|
||||
## 八、验证方式
|
||||
|
||||
### 8.1 后端测试
|
||||
|
||||
1. **单元测试**:
|
||||
- DynamicApiExecutor SQL 执行测试
|
||||
- ParamTypeConverter 类型转换测试
|
||||
- SqlValidator 安全验证测试
|
||||
|
||||
2. **集成测试**:
|
||||
- 从表导入流程测试
|
||||
- API 执行流程测试(完整流程)
|
||||
- 权限控制测试
|
||||
|
||||
3. **手动测试**:
|
||||
- 启动 hzhub-erp 服务
|
||||
- 访问 Actuator 健康检查:`curl http://localhost:8082/actuator/health`
|
||||
- 测试动态 API:`curl http://localhost:8082/erp/dynamic/v1/customer/list?pageNum=1&pageSize=10`
|
||||
|
||||
### 8.2 前端测试
|
||||
|
||||
1. **页面功能测试**:
|
||||
- 启动前端:`cd hzhub-admin && pnpm dev`
|
||||
- 访问 API 管理页面:`http://localhost:5666/#/erp/api`
|
||||
- 测试列表查询、新增、编辑、删除功能
|
||||
- 测试从表导入功能
|
||||
- 测试 API 测试功能
|
||||
|
||||
2. **API 执行测试**:
|
||||
- 创建一个测试 API
|
||||
- 输入参数执行测试
|
||||
- 查看执行结果和执行时间
|
||||
- 测试错误场景(错误的 SQL、缺少参数)
|
||||
|
||||
### 8.3 端到端测试
|
||||
|
||||
1. 通过网关访问动态 API:
|
||||
- 先登录获取 Token
|
||||
- 访问:`http://localhost:8080/erp/dynamic/v1/customer/list?pageNum=1&pageSize=10`
|
||||
- 验证返回数据格式和分页信息
|
||||
|
||||
2. 权限测试:
|
||||
- 创建一个需要权限的 API(require_auth=1, permission_code='erp:test:list')
|
||||
- 未登录访问应该返回 401
|
||||
- 无权限用户访问应该返回 403
|
||||
|
||||
---
|
||||
|
||||
## 九、用户确认的设计决策
|
||||
|
||||
根据用户反馈,最终确定的设计决策:
|
||||
|
||||
1. **动态路由机制**:使用通配符路由(`/erp/dynamic/{version}/{apiPath}`)
|
||||
- 简单稳定,所有动态 API 共享统一前缀
|
||||
- 支持版本号(v1/v2),路径自动包含版本
|
||||
|
||||
2. **SQL 编辑器**:基础版本(CodeMirror + SQL Server 语法高亮)
|
||||
- 第一版使用代码编辑器 + 语法高亮
|
||||
- 快速实现核心功能,后续可扩展可视化构建器
|
||||
|
||||
3. **高级功能**:第一版实现完整功能
|
||||
- Redis 缓存:支持缓存配置、缓存键模板、TTL、缓存清除
|
||||
- API 监控:调用统计、响应时间记录、错误日志
|
||||
- 版本管理:支持 v1/v2 版本号,可同时运行多版本 API
|
||||
- 统计可视化:调用次数图表、响应时间趋势、错误率统计
|
||||
|
||||
这些决策已整合到实现步骤和数据库表设计中。
|
||||
|
||||
---
|
||||
|
||||
## 十、集成到管理后台的具体方案
|
||||
|
||||
### 10.1 菜单配置
|
||||
|
||||
需要在 hzhub-system 的菜单表中添加 ERP 管理菜单(通过 SQL 插入):
|
||||
|
||||
```sql
|
||||
-- ERP 管理菜单(一级菜单)
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
VALUES ('ERP管理', 0, 5, '/erp', NULL, 1, 0, 'M', '0', '0', '', 'monitor', 'admin', NOW(), 'ERP API管理目录');
|
||||
|
||||
-- API 配置管理菜单(二级菜单)
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
VALUES ('API配置', (SELECT menu_id FROM sys_menu WHERE menu_name='ERP管理'), 1, 'api', 'erp/api/index', 1, 0, 'C', '0', '0', 'erp:api:list', 'tool', 'admin', NOW(), 'API配置管理菜单');
|
||||
|
||||
-- API 配置按钮权限
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
VALUES ('API查询', (SELECT menu_id FROM sys_menu WHERE menu_name='API配置'), 1, '', NULL, 1, 0, 'F', '0', '0', 'erp:api:query', '#', 'admin', NOW());
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
VALUES ('API新增', (SELECT menu_id FROM sys_menu WHERE menu_name='API配置'), 2, '', NULL, 1, 0, 'F', '0', '0', 'erp:api:add', '#', 'admin', NOW());
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
VALUES ('API修改', (SELECT menu_id FROM sys_menu WHERE menu_name='API配置'), 3, '', NULL, 1, 0, 'F', '0', '0', 'erp:api:edit', '#', 'admin', NOW());
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
VALUES ('API删除', (SELECT menu_id FROM sys_menu WHERE menu_name='API配置'), 4, '', NULL, 1, 0, 'F', '0', '0', 'erp:api:remove', '#', 'admin', NOW());
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
|
||||
VALUES ('API测试', (SELECT menu_id FROM sys_menu WHERE menu_name='API配置'), 5, '', NULL, 1, 0, 'F', '0', '0', 'erp:api:test', '#', 'admin', NOW());
|
||||
|
||||
-- API 监控统计菜单(二级菜单)
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
VALUES ('API监控', (SELECT menu_id FROM sys_menu WHERE menu_name='ERP管理'), 2, 'stats', 'erp/stats/index', 1, 0, 'C', '0', '0', 'erp:api:stats', 'monitor', 'admin', NOW(), 'API调用统计监控');
|
||||
```
|
||||
|
||||
### 10.2 权限体系集成
|
||||
|
||||
- 所有 API 配置管理接口都需要权限验证(通过 Sa-Token)
|
||||
- 前端按钮使用 `v-access:code="['erp:api:add']"` 控制权限
|
||||
- 动态 API 本身的权限通过配置表的 `require_auth` 和 `permission_code` 字段控制
|
||||
- 与现有的系统管理、监控等模块共享统一的 JWT Token
|
||||
|
||||
### 10.3 组件复用
|
||||
|
||||
复用 hzhub-admin 的标准组件和适配器:
|
||||
- `useVbenVxeGrid` - 表格组件(列表页)
|
||||
- `useVbenForm` - 表单组件(搜索、编辑)
|
||||
- `useVbenDrawer` / `useVbenModal` - 抽屉/弹窗组件
|
||||
- `TableSwitch` - 状态切换组件
|
||||
- `CodeMirror` - SQL 编辑器(复用代码生成器的编辑器)
|
||||
|
||||
### 10.4 API 函数集成
|
||||
|
||||
在 `/data/hzhub/hzhub-admin/apps/web-antd/src/api/erp/` 目录下新增 API 模块,遵循现有的 API 定义模式:
|
||||
- 使用 `requestClient` 统一请求客户端
|
||||
- 使用 `postWithMsg` / `putWithMsg` / `deleteWithMsg` 自动提示消息
|
||||
- 定义 TypeScript 类型(model.d.ts)
|
||||
|
||||
---
|
||||
|
||||
本方案提供了一个完整的 ERP API 管理平台设计,**完全集成到管理后台中**,实现统一管理。从数据库表设计、后端架构、前端界面到安全防护都有详细规划,包含缓存、监控、版本管理等完整功能。实现步骤按优先级分阶段推进,确保核心功能优先完成,逐步增强功能和安全性。
|
||||
277
docs/erp-api-quick-test-guide.md
Normal file
277
docs/erp-api-quick-test-guide.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# ERP API 管理平台 - 快速测试指南
|
||||
|
||||
## 当前状态确认
|
||||
|
||||
✅ **ERP 服务运行正常**(Health: UP)
|
||||
✅ **SQL Server 数据库连接正常**
|
||||
✅ **数据库表已创建**(需要在 MySQL hzhub 数据库中验证)
|
||||
|
||||
---
|
||||
|
||||
## 测试方案一:通过管理后台测试(推荐)
|
||||
|
||||
### Step 1: 访问管理后台
|
||||
|
||||
访问地址:http://192.168.120.60:5666 或 http://localhost:5666
|
||||
|
||||
登录管理员账号
|
||||
|
||||
### Step 2: 检查新菜单
|
||||
|
||||
刷新页面后,左侧菜单应该出现:
|
||||
```
|
||||
系统管理
|
||||
工具
|
||||
监控
|
||||
ERP管理 ← 新增
|
||||
├── API配置 ← 点击进入列表页
|
||||
└── API监控
|
||||
```
|
||||
|
||||
如果菜单未显示,可能需要:
|
||||
- 重新登录
|
||||
- 清除浏览器缓存
|
||||
- 检查角色权限是否包含 `erp:api:list`
|
||||
|
||||
### Step 3: 进入 API 配置页面
|
||||
|
||||
点击"ERP管理 > API配置",应该看到:
|
||||
- 空列表(因为还没有创建任何 API 配置)
|
||||
- 工具栏有"新增"按钮
|
||||
|
||||
### Step 4: 创建测试 API
|
||||
|
||||
点击"新增"按钮,填写以下信息:
|
||||
|
||||
**基础信息**:
|
||||
- API名称:`测试客户列表查询`
|
||||
- API路径:`/erp/dynamic/v1/test_customer_list`
|
||||
- HTTP方法:`GET`
|
||||
- API描述:`测试SQL Server客户数据查询`
|
||||
- API版本:`v1`
|
||||
- 数据源:`erp`(默认)
|
||||
- 结果类型:`LIST`
|
||||
- 支持分页:`否`(先测试不分页)
|
||||
|
||||
**SQL模板**(选择一个测试):
|
||||
|
||||
**方案A:简单查询(推荐,不依赖具体表结构)**
|
||||
```sql
|
||||
SELECT TOP 10 customer_code, customer_name FROM customer_general
|
||||
```
|
||||
|
||||
**方案B:查询系统表(通用)**
|
||||
```sql
|
||||
SELECT TOP 5 TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
|
||||
```
|
||||
|
||||
**权限配置**:
|
||||
- 是否需要认证:`否`(方便测试)
|
||||
- 状态:`启用`
|
||||
|
||||
点击"确定"保存。
|
||||
|
||||
### Step 5: 测试 API 执行
|
||||
|
||||
在列表中找到刚创建的 API,点击"测试"按钮。
|
||||
|
||||
**测试参数**:输入空对象 `{}`(因为SQL没有参数)
|
||||
|
||||
点击"执行",查看结果:
|
||||
- 应该显示 SQL Server 的数据
|
||||
- 显示执行时间(毫秒)
|
||||
- 显示成功状态
|
||||
|
||||
如果成功,返回类似:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{"customer_code": "C001", "customer_name": "客户A"},
|
||||
{"customer_code": "C002", "customer_name": "客户B"}
|
||||
],
|
||||
"executionTime": 156,
|
||||
"executedSql": "SELECT TOP 10 customer_code, customer_name FROM customer_general"
|
||||
}
|
||||
```
|
||||
|
||||
### Step 6: 测试带参数的 API(可选)
|
||||
|
||||
创建第二个测试 API:
|
||||
|
||||
**信息**:
|
||||
- API名称:`测试带参数查询`
|
||||
- API路径:`/erp/dynamic/v1/test_param_query`
|
||||
- SQL模板:
|
||||
```sql
|
||||
SELECT customer_code, customer_name FROM customer_general
|
||||
WHERE #{customerCode} IS NOT NULL THEN customer_code = #{customerCode}
|
||||
```
|
||||
|
||||
**测试参数**:
|
||||
```json
|
||||
{
|
||||
"customerCode": "C001"
|
||||
}
|
||||
```
|
||||
|
||||
查看结果,应该只返回指定客户的数据。
|
||||
|
||||
---
|
||||
|
||||
## 测试方案二:通过 curl 直接测试 API
|
||||
|
||||
### 测试1:无参数查询
|
||||
|
||||
```bash
|
||||
# 直接访问动态API(不需要Token,因为配置了@SaIgnore)
|
||||
curl http://localhost:8082/erp/dynamic/v1/test_customer_list
|
||||
|
||||
# 或通过网关访问(需要先获取Token)
|
||||
curl -H "Authorization: Bearer YOUR_TOKEN" \
|
||||
http://localhost:8080/erp/dynamic/v1/test_customer_list
|
||||
```
|
||||
|
||||
**预期返回**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{"customer_code": "xxx", "customer_name": "xxx"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 测试2:带参数查询
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8082/erp/dynamic/v1/test_param_query \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"customerCode":"C001"}'
|
||||
```
|
||||
|
||||
### 测试3:API配置管理接口
|
||||
|
||||
```bash
|
||||
# 查询API配置列表(需要Token)
|
||||
curl -H "Authorization: Bearer YOUR_TOKEN" \
|
||||
http://localhost:8080/erp/api/config/list?pageNum=1&pageSize=10
|
||||
|
||||
# 测试API(需要Token)
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
http://localhost:8080/erp/api/config/test/1 \
|
||||
-d '{}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试验证清单
|
||||
|
||||
### 功能验证
|
||||
|
||||
✅ **菜单显示**:ERP管理菜单正常显示
|
||||
✅ **列表页面**:API配置列表正常加载
|
||||
✅ **新增功能**:能成功保存API配置到MySQL数据库
|
||||
✅ **测试功能**:API测试弹窗正常工作
|
||||
✅ **SQL执行**:动态SQL能成功查询SQL Server数据
|
||||
✅ **参数处理**:带参数的SQL能正确执行
|
||||
✅ **错误处理**:错误的SQL能显示详细错误信息
|
||||
|
||||
### 数据验证
|
||||
|
||||
检查 MySQL 数据库:
|
||||
|
||||
```sql
|
||||
-- 查看创建的API配置
|
||||
SELECT * FROM hzhub.erp_api_config;
|
||||
|
||||
-- 查看参数配置(如果有)
|
||||
SELECT * FROM hzhub.erp_api_param;
|
||||
|
||||
-- 查看菜单配置
|
||||
SELECT menu_id, menu_name, menu_type, visible, perms
|
||||
FROM hzhub.sys_menu
|
||||
WHERE menu_name LIKE '%ERP%' OR menu_name LIKE '%API%';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 可能遇到的问题
|
||||
|
||||
### 问题1: 菜单不显示
|
||||
|
||||
**原因**:
|
||||
- SQL未执行到sys_menu表
|
||||
- 角色未分配权限
|
||||
|
||||
**解决**:
|
||||
```sql
|
||||
-- 检查菜单是否插入
|
||||
SELECT * FROM sys_menu WHERE menu_name='ERP管理';
|
||||
|
||||
-- 给管理员角色分配权限(假设角色ID=1)
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT 1, menu_id FROM sys_menu WHERE perms LIKE 'erp:api:%';
|
||||
```
|
||||
|
||||
### 问题2: API执行失败
|
||||
|
||||
**原因**:
|
||||
- SQL Server连接问题
|
||||
- SQL语法错误
|
||||
- 表不存在
|
||||
|
||||
**解决**:
|
||||
```bash
|
||||
# 测试SQL Server连接
|
||||
curl http://localhost:8082/erp/test/connection
|
||||
|
||||
# 查看错误详情
|
||||
# 在测试弹窗中查看完整的错误信息和堆栈
|
||||
```
|
||||
|
||||
### 问题3: 保存配置失败
|
||||
|
||||
**原因**:
|
||||
- MySQL表未创建
|
||||
- 字段类型不匹配
|
||||
|
||||
**解决**:
|
||||
```sql
|
||||
-- 验证表结构
|
||||
DESC hzhub.erp_api_config;
|
||||
DESC hzhub.erp_api_param;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试成功后的下一步
|
||||
|
||||
测试成功后,我将继续创建:
|
||||
|
||||
1. **前端完整界面**(Phase 2)
|
||||
- API配置列表页(含搜索、分页)
|
||||
- 编辑页(基础设置 + 参数配置两Tab)
|
||||
- 从表导入弹窗
|
||||
- 测试弹窗优化
|
||||
|
||||
2. **从表导入功能**(Phase 2)
|
||||
- 自动生成SQL模板
|
||||
- 自动生成参数配置
|
||||
|
||||
3. **监控统计功能**(Phase 3)
|
||||
- API调用统计表记录
|
||||
- 统计图表展示
|
||||
|
||||
---
|
||||
|
||||
## 请告诉我测试结果
|
||||
|
||||
请尝试测试并告诉我:
|
||||
- ✅ 测试成功,看到了数据
|
||||
- ❌ 遇到问题,具体错误信息
|
||||
|
||||
我将根据测试结果继续优化或创建前端代码!
|
||||
163
docs/erp-api-testing-guide.md
Normal file
163
docs/erp-api-testing-guide.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# ERP API 管理平台 - 测试流程
|
||||
|
||||
## 当前状态
|
||||
|
||||
✅ **后端代码已完成**(Phase 1)
|
||||
✅ **SQL 文件已创建**:`/data/hzhub/hzhub-erp/docs/sql/erp_api_tables.sql`
|
||||
✅ **初始化指南已创建**:`/data/hzhub/docs/erp-api-database-init-guide.md`
|
||||
⏳ **数据库表待创建**(需手动执行)
|
||||
⏳ **ERP 服务待重启**
|
||||
|
||||
---
|
||||
|
||||
## 执行步骤
|
||||
|
||||
### Step 1: 创建数据库表(需手动执行)
|
||||
|
||||
**推荐方式**:通过 Docker MySQL 容器执行
|
||||
|
||||
```bash
|
||||
# 1. 进入 MySQL 容器
|
||||
docker exec -it hzhub-mysql mysql -u root -phzhub123
|
||||
|
||||
# 2. 在 MySQL 命令行中执行
|
||||
USE hzhub;
|
||||
|
||||
# 3. 复制粘贴以下 SQL(或执行 SQL 文件)
|
||||
-- 见文件:/data/hzhub/hzhub-erp/docs/sql/erp_api_tables.sql
|
||||
|
||||
# 4. 验证表创建
|
||||
SHOW TABLES LIKE 'erp_api_%';
|
||||
SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'hzhub' AND table_name LIKE 'erp_api_%';
|
||||
|
||||
# 5. 验证菜单插入
|
||||
SELECT menu_id, menu_name, perms FROM sys_menu WHERE menu_name LIKE '%ERP%' OR menu_name LIKE '%API%';
|
||||
|
||||
# 6. 退出
|
||||
exit;
|
||||
```
|
||||
|
||||
**如果 MySQL 容器不可访问**,可使用 Navicat/DBeaver 等工具连接 localhost:3306 执行 SQL。
|
||||
|
||||
---
|
||||
|
||||
### Step 2: 重启 ERP 服务(自动执行)
|
||||
|
||||
执行完 SQL 后,我将继续执行:
|
||||
|
||||
```bash
|
||||
cd /data/hzhub/hzhub-erp
|
||||
./restart.sh # 或 ./start.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 3: 验证服务健康(自动执行)
|
||||
|
||||
```bash
|
||||
# 检查健康状态
|
||||
curl http://localhost:8082/actuator/health
|
||||
|
||||
# 检查 ERP 服务进程
|
||||
cd hzhub-erp && ./status.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 4: 手动测试 API(需你手动执行)
|
||||
|
||||
#### 4.1 通过管理后台测试(推荐)
|
||||
|
||||
1. **访问管理后台**:http://192.168.120.60:5666
|
||||
2. **登录**(使用管理员账号)
|
||||
3. **刷新菜单**(菜单会动态加载,新菜单"ERP管理"应该出现)
|
||||
4. **进入 ERP管理 > API配置**
|
||||
5. **点击"新增"创建测试 API**:
|
||||
- API名称:`测试客户查询`
|
||||
- API路径:`/erp/dynamic/v1/test_customer`
|
||||
- HTTP方法:`GET`
|
||||
- SQL模板:
|
||||
```sql
|
||||
SELECT TOP 10 customer_code, customer_name FROM customer_general
|
||||
```
|
||||
- 结果类型:`LIST`
|
||||
- 支持分页:`否`
|
||||
- 状态:`启用`
|
||||
|
||||
6. **点击"测试"按钮**:输入空参数 `{}`,查看执行结果
|
||||
|
||||
#### 4.2 通过 curl 直接测试
|
||||
|
||||
```bash
|
||||
# 测试动态 API 执行(无需 Token,因为当前 @SaIgnore)
|
||||
curl http://localhost:8082/erp/dynamic/v1/test_customer
|
||||
|
||||
# 如果通过网关访问(需要 Token)
|
||||
curl -H "Authorization: Bearer <token>" http://localhost:8080/erp/dynamic/v1/test_customer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 5: 测试结果验证
|
||||
|
||||
**预期结果**:
|
||||
|
||||
1. **API 配置保存成功**:能在列表中看到刚创建的 API
|
||||
2. **测试执行成功**:返回 SQL Server 数据库中的客户数据
|
||||
3. **执行时间记录**:能看到响应时间(毫秒)
|
||||
4. **错误处理**:如果 SQL 错误,能看到详细的错误信息
|
||||
|
||||
---
|
||||
|
||||
## 可能遇到的问题
|
||||
|
||||
### 问题 1: 菜单不显示
|
||||
|
||||
**原因**:菜单 SQL 未执行或角色无权限
|
||||
|
||||
**解决**:
|
||||
1. 检查 sys_menu 表是否插入成功
|
||||
2. 在角色管理中为管理员角色分配 `erp:api:*` 权限
|
||||
3. 清除浏览器缓存重新登录
|
||||
|
||||
### 问题 2: SQL 执行失败
|
||||
|
||||
**原因**:SQL Server 连接问题或 SQL 语法错误
|
||||
|
||||
**解决**:
|
||||
1. 检查 ERP 服务配置:`hzhub-erp/src/main/resources/application-dev.yml`
|
||||
2. 确认 SQL Server 地址:`192.168.120.10:8042`
|
||||
3. 测试 SQL Server 连接:
|
||||
```bash
|
||||
curl http://localhost:8082/erp/test/connection
|
||||
```
|
||||
|
||||
### 问题 3: 动态 API 返回 404
|
||||
|
||||
**原因**:API 配置路径不匹配或服务未重启
|
||||
|
||||
**解决**:
|
||||
1. 确认 ERP 服务已重启
|
||||
2. 检查 API 配置中的 `api_path` 是否为 `/erp/dynamic/v1/xxx`
|
||||
3. 确认 `api_method` 和 `api_version` 正确
|
||||
|
||||
---
|
||||
|
||||
## 下一步计划
|
||||
|
||||
测试成功后,我将继续:
|
||||
|
||||
1. **创建前端界面**(API 配置列表、编辑页、测试弹窗)
|
||||
2. **实现从表导入功能**(自动生成 SQL 和参数)
|
||||
3. **添加缓存和监控功能**
|
||||
4. **完善安全验证和权限控制**
|
||||
|
||||
---
|
||||
|
||||
## 准备好了吗?
|
||||
|
||||
**请告诉我 SQL 执行完成后的状态**:
|
||||
- ✅ SQL 执行成功,表和菜单已创建
|
||||
- ❌ SQL 执行遇到问题,需要帮助
|
||||
|
||||
我将根据你的反馈继续后续步骤。
|
||||
543
docs/gateway-migration-plan.md
Normal file
543
docs/gateway-migration-plan.md
Normal file
@@ -0,0 +1,543 @@
|
||||
# HZHub-Gateway 迁移方案
|
||||
|
||||
## 一、现状分析
|
||||
|
||||
### 当前架构(去中心化)
|
||||
|
||||
```
|
||||
前端门户 → Nginx/Vite代理 → 直连后端服务
|
||||
admin → localhost:6039 (hzhub-ai)
|
||||
employee → localhost:6039 (hzhub-ai) + localhost:8082 (hzhub-erp)
|
||||
dealer → localhost:6039 (hzhub-ai) + localhost:8082 (hzhub-erp)
|
||||
```
|
||||
|
||||
hzhub-gateway 已存在但未部署,仅有基础路由配置(3条路由 + CORS)。
|
||||
|
||||
### hzhub-ai 中可迁移的网关级功能
|
||||
|
||||
| 功能 | 位置 | 是否适合迁移 |
|
||||
|------|------|:---:|
|
||||
| Sa-Token JWT 验证 | `SaTokenConfig` + `SecurityConfig` | ✅ 是 — 全局统一鉴权 |
|
||||
| XSS 过滤 | `XssFilter` (javax.servlet Filter) | ✅ 是 — WebFilter 可实现 |
|
||||
| 请求/响应加密 | `CryptoFilter` (RSA 加解密) | ⚠️ 部分 — 需适配 WebFlux |
|
||||
| 接口限流 | `@RateLimiter` + Redisson | ✅ 是 — 可用 Redis + RequestRateLimiter |
|
||||
| 幂等性校验 | `@Idempotent` | ❌ 否 — 业务级,保留后端 |
|
||||
| SSE 流式响应 | ChatController | ❌ 否 — 业务级,保留后端 |
|
||||
| WebSocket 握手鉴权 | `PlusWebSocketInterceptor` | ❌ 否 — 协议不同,保留后端 |
|
||||
| 数据权限拦截 | MyBatis 拦截器 | ❌ 否 — ORM 层,保留后端 |
|
||||
|
||||
### 核心挑战
|
||||
|
||||
1. **Servlet vs WebFlux**:Gateway 基于 Spring WebFlux(响应式),hzhub-ai 的 Filter 是 Servlet 规范,不能直接复用,需改写为 `WebFilter`
|
||||
2. **JWT 解析**:Gateway 只需验证 JWT 合法性并透传用户信息,无需完整 Sa-Token 登录态
|
||||
3. **加密适配**:CryptoFilter 依赖 `ServletInputStream`,需改写为 `ServerHttpRequestDecorator`
|
||||
|
||||
---
|
||||
|
||||
## 二、目标架构
|
||||
|
||||
```
|
||||
前端门户 → Gateway(:8080) → 后端服务
|
||||
/ai/** → hzhub-ai(:8081)
|
||||
/erp/** → hzhub-erp(:8082)
|
||||
/system/** → hzhub-ai(:8081)
|
||||
```
|
||||
|
||||
Gateway 承担:
|
||||
- **统一鉴权**:验证 JWT Token,解析用户信息注入请求头
|
||||
- **XSS 防护**:对 POST/PUT 请求体进行 XSS 清洗
|
||||
- **接口限流**:基于 Redis 的令牌桶限流
|
||||
- **请求/响应加密**:按需解密请求、加密响应
|
||||
- **路由转发**:按路径分发到对应服务
|
||||
- **跨域处理**:全局 CORS
|
||||
|
||||
后端服务(hzhub-ai / hzhub-erp)承担:
|
||||
- **业务逻辑**:CRUD、AI 对话、工作流等
|
||||
- **数据权限**:MyBatis 层的多租户数据隔离
|
||||
- **幂等性**:防重复提交
|
||||
- **SSE/WebSocket**:流式推送
|
||||
|
||||
### 服务间信任模型
|
||||
|
||||
```
|
||||
外部请求 → Gateway → 后端服务
|
||||
│ │
|
||||
验证JWT 信任Gateway
|
||||
透传的用户头
|
||||
```
|
||||
|
||||
- Gateway 验证 JWT 通过后,将用户信息注入请求头:`X-User-Id`、`X-User-Name`、`X-Client-Id`
|
||||
- 后端服务在 `SecurityConfig` 中配置:**来自 Gateway 的请求跳过 JWT 验证**
|
||||
- 判断依据:检查请求头 `X-Gateway-Verified: true`(或共享内网 IP 白名单)
|
||||
- 开发环境:保留直连后端的鉴权能力(双重模式)
|
||||
|
||||
---
|
||||
|
||||
## 三、实施步骤
|
||||
|
||||
### Phase 1: Gateway 基础增强(认证 + 路由)
|
||||
|
||||
**目标**:让 Gateway 能正常转发请求并验证 JWT
|
||||
|
||||
#### 3.1 修改 pom.xml
|
||||
|
||||
```xml
|
||||
<!-- 已有:spring-cloud-starter-gateway, spring-cloud-starter-loadbalancer -->
|
||||
|
||||
<!-- 新增 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-reactor-spring-boot3-starter</artifactId>
|
||||
<version>1.39.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-jwt</artifactId>
|
||||
<version>1.39.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 3.2 JWT 认证全局过滤器
|
||||
|
||||
```java
|
||||
// src/main/java/org/hzhub/gateway/filter/AuthGlobalFilter.java
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AuthGlobalFilter implements GlobalFilter, Ordered {
|
||||
|
||||
private final SaTokenConfig saTokenConfig;
|
||||
|
||||
// 放行路径
|
||||
private static final List<String> WHITE_LIST = List.of(
|
||||
"/erp/test/", "/erp/customer/", "/actuator/"
|
||||
);
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
String path = exchange.getRequest().getURI().getPath();
|
||||
|
||||
// 白名单放行
|
||||
if (isWhiteList(path)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
// 从 Header 获取 Token
|
||||
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
|
||||
if (token == null || !token.startsWith("Bearer ")) {
|
||||
return unauthorized(exchange, "未登录或登录已过期");
|
||||
}
|
||||
|
||||
// 验证 JWT(使用 sa-token-jwt)
|
||||
String jwtToken = token.substring(7);
|
||||
try {
|
||||
SaTokenInfo info = SaManager.getTokenInfo(jwtToken);
|
||||
if (info == null || !info.isLogin()) {
|
||||
return unauthorized(exchange, "登录已过期");
|
||||
}
|
||||
// 透传用户信息到后端
|
||||
ServerHttpRequest mutated = exchange.getRequest().mutate()
|
||||
.header("X-User-Id", info.getLoginId())
|
||||
.header("X-Client-Id", exchange.getRequest().getHeaders().getFirst("ClientID"))
|
||||
.header("X-Gateway-Verified", "true")
|
||||
.build();
|
||||
return chain.filter(exchange.mutate().request(mutated).build());
|
||||
} catch (Exception e) {
|
||||
return unauthorized(exchange, "Token 无效");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isWhiteList(String path) {
|
||||
return WHITE_LIST.stream().anyMatch(path::startsWith);
|
||||
}
|
||||
|
||||
private Mono<Void> unauthorized(ServerWebExchange exchange, String msg) {
|
||||
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
byte[] bytes = ("{\"code\":401,\"msg\":\"" + msg + "\"}").getBytes(StandardCharsets.UTF_8);
|
||||
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
|
||||
return exchange.getResponse().writeWith(Mono.just(buffer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() { return -100; } // 最高优先级
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3 更新 application.yml
|
||||
|
||||
```yaml
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: hzhub-gateway
|
||||
|
||||
# Redis 配置(限流 + Token 验证共享)
|
||||
data:
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: hzhub-ai
|
||||
uri: http://${AI_HOST:localhost}:${AI_PORT:8081}
|
||||
predicates:
|
||||
- Path=/ai/**,/system/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
|
||||
- id: hzhub-erp
|
||||
uri: http://${ERP_HOST:localhost}:${ERP_PORT:8082}
|
||||
predicates:
|
||||
- Path=/erp/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
|
||||
globalcors:
|
||||
cors-configurations:
|
||||
'[/**]':
|
||||
allowedOriginPatterns: "*"
|
||||
allowedMethods: "*"
|
||||
allowedHeaders: "*"
|
||||
allowCredentials: true
|
||||
maxAge: 3600
|
||||
|
||||
# Sa-Token(JWT 验证用,与后端服务共享 secret)
|
||||
sa-token:
|
||||
jwt-secret-key: ${JWT_SECRET:abcdefghijklmnopqrstuvwxyz}
|
||||
token-name: Authorization
|
||||
|
||||
# 日志
|
||||
logging:
|
||||
level:
|
||||
org.hzhub.gateway: debug
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: XSS 过滤 + 限流
|
||||
|
||||
#### 3.4 XSS 全局过滤器(WebFlux 版)
|
||||
|
||||
```java
|
||||
// src/main/java/org/hzhub/gateway/filter/XssGlobalFilter.java
|
||||
@Component
|
||||
public class XssGlobalFilter implements GlobalFilter, Ordered {
|
||||
|
||||
private static final List<String> EXCLUDE_PATHS = List.of("/ai/upload");
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
String path = exchange.getRequest().getURI().getPath();
|
||||
HttpMethod method = exchange.getRequest().getMethod();
|
||||
|
||||
// GET/DELETE 不处理;排除白名单
|
||||
if (method == HttpMethod.GET || method == HttpMethod.DELETE
|
||||
|| EXCLUDE_PATHS.stream().anyMatch(path::startsWith)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
// 包装请求,对 Body 进行 XSS 清洗
|
||||
ServerHttpRequest decorated = new ServerHttpRequestDecorator(exchange.getRequest()) {
|
||||
@Override
|
||||
public Flux<DataBuffer> getBody() {
|
||||
return super.getBody().map(buffer -> {
|
||||
byte[] bytes = new byte[buffer.readableByteCount()];
|
||||
buffer.read(bytes);
|
||||
String body = new String(bytes, StandardCharsets.UTF_8);
|
||||
String cleaned = cleanXss(body);
|
||||
byte[] cleanedBytes = cleaned.getBytes(StandardCharsets.UTF_8);
|
||||
DataBufferFactory factory = buffer.factory();
|
||||
DataBuffer newBuffer = factory.allocateBuffer(cleanedBytes.length);
|
||||
newBuffer.write(cleanedBytes);
|
||||
return newBuffer;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return chain.filter(exchange.mutate().request(decorated).build());
|
||||
}
|
||||
|
||||
private String cleanXss(String value) {
|
||||
if (value == null) return null;
|
||||
return value
|
||||
.replaceAll("<script>", "<script>")
|
||||
.replaceAll("</script>", "</script>")
|
||||
.replaceAll("javascript:", "")
|
||||
.replaceAll("on\\w+\\s*=", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() { return -50; }
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.5 限流配置(基于 Redis)
|
||||
|
||||
```yaml
|
||||
# 在 application.yml 的 gateway 配置中添加
|
||||
spring.cloud.gateway.default-filters:
|
||||
- name: RequestRateLimiter
|
||||
args:
|
||||
redis-rate-limiter.replenishRate: 10 # 每秒补充令牌数
|
||||
redis-rate-limiter.burstCapacity: 20 # 令牌桶容量
|
||||
key-resolver: "#{@ipKeyResolver}" # 按 IP 限流
|
||||
```
|
||||
|
||||
```java
|
||||
// src/main/java/org/hzhub/gateway/config/RateLimiterConfig.java
|
||||
@Configuration
|
||||
public class RateLimiterConfig {
|
||||
|
||||
/**
|
||||
* 按 IP 地址限流
|
||||
*/
|
||||
@Bean
|
||||
public KeyResolver ipKeyResolver() {
|
||||
return exchange -> {
|
||||
String ip = exchange.getRequest().getRemoteAddress() != null
|
||||
? exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
|
||||
: "unknown";
|
||||
return Mono.just("gateway:ratelimit:" + ip);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 按路径+IP 限流(更细粒度)
|
||||
*/
|
||||
@Bean
|
||||
public KeyResolver pathIpKeyResolver() {
|
||||
return exchange -> {
|
||||
String path = exchange.getRequest().getURI().getPath();
|
||||
String ip = exchange.getRequest().getRemoteAddress() != null
|
||||
? exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
|
||||
: "unknown";
|
||||
return Mono.just("gateway:ratelimit:" + path + ":" + ip);
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: 后端服务适配
|
||||
|
||||
#### 3.6 hzhub-ai 的 SecurityConfig 改造
|
||||
|
||||
在 hzhub-ai 的 `SecurityConfig` 中新增:当请求来自 Gateway 时,跳过 JWT 验证。
|
||||
|
||||
```java
|
||||
// hzhub-ai 的 SecurityConfig.java 中修改
|
||||
private boolean isFromGateway(ServerHttpRequest request) {
|
||||
String verified = request.getHeaders().getFirst("X-Gateway-Verified");
|
||||
return "true".equals(verified);
|
||||
}
|
||||
```
|
||||
|
||||
或者更简单:在 Sa-Token 拦截器中添加检查:
|
||||
```java
|
||||
// 如果 Gateway 已验证,直接放行
|
||||
if ("true".equals(request.getHeader("X-Gateway-Verified"))) {
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.7 hzhub-erp 同理
|
||||
|
||||
在 hzhub-erp 的 `SecurityConfig.java` 中添加同样的 Gateway 信任逻辑。
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: 前端路由切换
|
||||
|
||||
#### 3.8 hzhub-admin(管理后台)
|
||||
|
||||
修改 `.env.development`:
|
||||
```env
|
||||
VITE_GLOB_API_URL=http://localhost:8080
|
||||
```
|
||||
|
||||
修改 `vite.config.ts` 代理:
|
||||
```typescript
|
||||
'/api': {
|
||||
target: 'http://localhost:8080', // 改为 Gateway
|
||||
changeOrigin: true,
|
||||
rewrite: path => path.replace(/^\/api/, ''),
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.9 hzhub-portal-employee(员工门户)
|
||||
|
||||
修改 `.env.development`:
|
||||
```env
|
||||
VITE_API_URL=http://localhost:8080
|
||||
```
|
||||
|
||||
`request.ts` 中已自动使用此 baseURL,无需改动。
|
||||
|
||||
注意:原来直连 ERP 的请求(`/erp/customer/**`)也需经过 Gateway,Gateway 路由已覆盖 `/erp/**`。
|
||||
|
||||
#### 3.10 hzhub-portal-dealer(经销商门户)
|
||||
|
||||
同 employee,统一指向 Gateway。
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: Docker Compose 集成
|
||||
|
||||
#### 3.11 新增 hzhub-gateway 服务
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml 新增
|
||||
hzhub-gateway:
|
||||
build:
|
||||
context: ../hzhub-gateway
|
||||
dockerfile: Dockerfile
|
||||
container_name: hzhub-gateway
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
SPRING_PROFILES_ACTIVE: prod
|
||||
REDIS_HOST: hzhub-redis
|
||||
REDIS_PORT: 6379
|
||||
AI_HOST: hzhub-ai
|
||||
AI_PORT: 6039
|
||||
ERP_HOST: hzhub-erp
|
||||
ERP_PORT: 8082
|
||||
JWT_SECRET: ${JWT_SECRET:-abcdefghijklmnopqrstuvwxyz}
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
hzhub-ai:
|
||||
condition: service_started
|
||||
hzhub-erp:
|
||||
condition: service_started
|
||||
networks:
|
||||
- hzhub-network
|
||||
```
|
||||
|
||||
#### 3.12 前端 Nginx 代理改为 Gateway
|
||||
|
||||
```nginx
|
||||
# hzhub-portal-employee 的 Nginx 配置
|
||||
# 原来: proxy_pass http://hzhub-ai:6039;
|
||||
# 改为:
|
||||
location /api/ {
|
||||
proxy_pass http://hzhub-gateway:8080;
|
||||
}
|
||||
location /erp/ {
|
||||
proxy_pass http://hzhub-gateway:8080;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.13 后端服务关闭外部端口暴露
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml 修改
|
||||
hzhub-ai:
|
||||
# ports:
|
||||
# - "6039:6039" # 不再对外暴露,仅内网访问
|
||||
hzhub-erp:
|
||||
# ports:
|
||||
# - "8082:8082" # 不再对外暴露,仅内网访问
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、文件变更清单
|
||||
|
||||
### 新增文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `hzhub-gateway/src/main/java/org/hzhub/gateway/filter/AuthGlobalFilter.java` | JWT 认证过滤器 |
|
||||
| `hzhub-gateway/src/main/java/org/hzhub/gateway/filter/XssGlobalFilter.java` | XSS 过滤 |
|
||||
| `hzhub-gateway/src/main/java/org/hzhub/gateway/config/RateLimiterConfig.java` | 限流 Key 解析器 |
|
||||
| `hzhub-gateway/Dockerfile` | Gateway 容器构建 |
|
||||
|
||||
### 修改文件
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `hzhub-gateway/pom.xml` | 新增 sa-token-reactor、redis-reactive 依赖 |
|
||||
| `hzhub-gateway/src/main/resources/application.yml` | 重写路由、Redis、Sa-Token 配置 |
|
||||
| `hzhub-gateway/src/main/java/org/hzhub/HzhubGatewayApplication.java` | 无需改动 |
|
||||
| `hzhub-ai/.../SecurityConfig.java` | 添加 Gateway 信任逻辑 |
|
||||
| `hzhub-erp/.../SecurityConfig.java` | 添加 Gateway 信任逻辑 |
|
||||
| `hzhub-admin/apps/web-antd/.env.development` | API 地址改为 Gateway |
|
||||
| `hzhub-admin/apps/web-antd/vite.config.ts` | 代理目标改为 Gateway |
|
||||
| `hzhub-portal-employee/.env.development` | VITE_API_URL 指向 Gateway |
|
||||
| `hzhub-portal-dealer/.env.development` | 新增 API URL |
|
||||
| `hzhub-deploy/docker-compose.yml` | 新增 Gateway 服务,调整网络 |
|
||||
|
||||
---
|
||||
|
||||
## 五、分阶段验证
|
||||
|
||||
### Phase 1 验证
|
||||
```bash
|
||||
# 1. 启动 Gateway
|
||||
cd hzhub-gateway && mvn spring-boot:run
|
||||
|
||||
# 2. 测试路由转发
|
||||
curl http://localhost:8080/erp/test/connection
|
||||
# 应返回 SQL Server 连接信息
|
||||
|
||||
# 3. 测试鉴权(未登录)
|
||||
curl http://localhost:8080/ai/chat/message
|
||||
# 应返回 401
|
||||
|
||||
# 4. 测试鉴权(带 Token)
|
||||
curl -H "Authorization: Bearer <token>" http://localhost:8080/ai/chat/message
|
||||
# 应正常返回结果
|
||||
```
|
||||
|
||||
### Phase 2 验证
|
||||
```bash
|
||||
# 测试 XSS 过滤
|
||||
curl -X POST http://localhost:8080/ai/xxx \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"content": "<script>alert(1)</script>"}'
|
||||
# 请求体应被清洗
|
||||
|
||||
# 测试限流(快速连续请求)
|
||||
for i in {1..30}; do curl -s http://localhost:8080/ai/xxx; done
|
||||
# 超过阈值应返回 429
|
||||
```
|
||||
|
||||
### Phase 3 验证
|
||||
```bash
|
||||
# 前端登录后,所有请求走 Gateway
|
||||
# 确认 AI 接口、ERP 接口均正常工作
|
||||
# 确认 Token 过期后自动跳转登录页
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、风险与注意事项
|
||||
|
||||
| 风险 | 应对 |
|
||||
|------|------|
|
||||
| Sa-Token JWT 验证方式与后端不一致 | 统一使用 `StpLogicJwtForSimple`,共享 `jwt-secret-key` |
|
||||
| Gateway 成为单点故障 | 后续可部署多实例 + Nginx 负载均衡 |
|
||||
| SSE 流式响应经过 Gateway 可能超时 | 需在 Gateway 配置路由超时时间(默认无超时) |
|
||||
| CORS 配置冲突 | Gateway 统一处理 CORS,后端服务关闭 CORS |
|
||||
| 开发环境直连 vs 生产走 Gateway | 后端服务保留双重模式:有 `X-Gateway-Verified` 跳过验证,否则自行验证 |
|
||||
|
||||
---
|
||||
|
||||
## 七、后续优化方向
|
||||
|
||||
1. **服务注册与发现**:引入 Nacos,替代硬编码的服务地址
|
||||
2. **熔断降级**:引入 Resilience4j,对下游服务做熔断保护
|
||||
3. **链路追踪**:引入 SkyWalking 或 Micrometer Trace
|
||||
4. **灰度发布**:基于 Header 的蓝绿部署路由
|
||||
5. **API 文档聚合**:Gateway 聚合 Swagger/Knife4j 文档
|
||||
Reference in New Issue
Block a user