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:
大壮
2026-05-08 08:00:19 +00:00
parent e6fc123b1f
commit c2513849b4
1564 changed files with 52903 additions and 641 deletions

View 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. **准备前端界面**:继续创建前端代码以使用管理界面操作

View 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`
---
**请刷新页面并告诉我结果!**
如果页面正常显示,我们将进入完整功能测试阶段。
如果遇到问题,请告诉我具体错误信息,我会立即协助解决。🔧

View File

@@ -0,0 +1,743 @@
# ERP API 管理平台实现方案
## Context
**需求背景**:用户需要一个界面来查看和管理 ERP 数据库的定制 API 封装,并提供手动创建 API 的功能。当前 ERP 服务只有少量固定的 APICustomerController缺乏灵活的 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_configAPI 配置主表)**
```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_paramAPI 参数配置表)**
```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_statsAPI 调用统计表)**
```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 FETCHSQL Server 语法)
- 结果封装LIST → TableDataInfoSINGLE → RCOUNT → 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 + testParamsMap<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 # 参数配置 TabVxeGrid 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参数类型转换
- 创建 ApiCacheServiceRedis 缓存服务)
- 创建 ApiMonitorService调用统计服务
3. **Controller 实现**
- ErpApiControllerCRUD 接口 + 缓存配置 + 版本管理)
- DynamicApiController通配符路由 + 缓存检查 + 统计记录)
4. **前端基础页面**
- 列表页index.vue
- 基础的增删改查功能
- CodeMirror SQL 编辑器(语法高亮)
### Phase 2导入和编辑功能 + 版本管理(第 3 周)
**目标**:完善配置管理功能 + 版本支持
1. **从表导入**
- ServiceimportFromTable 方法
- SQL 模板自动生成
- 参数自动生成
2. **编辑页**
- 两 Tab 设计(基础设置 + 参数配置)
- VxeGrid inline-editable 参数编辑
- SQL 编辑器CodeMirror + SQL Server 语法高亮)
- 版本管理字段api_version支持 v1/v2
3. **前端弹窗**
- 从表导入弹窗
- 基础设置表单(包含缓存配置)
- 版本选择下拉框
### Phase 3测试和文档 + 监控统计(第 4 周)
**目标**:增强可用性 + 监控功能
1. **API 测试功能**
- ServicetestApi 方法
- 执行日志和错误处理
- 前端测试弹窗
2. **API 文档预览**
- ServicegenerateApiDoc 方法
- 前端文档预览弹窗
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. 权限测试:
- 创建一个需要权限的 APIrequire_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 管理平台设计,**完全集成到管理后台中**,实现统一管理。从数据库表设计、后端架构、前端界面到安全防护都有详细规划,包含缓存、监控、版本管理等完整功能。实现步骤按优先级分阶段推进,确保核心功能优先完成,逐步增强功能和安全性。

View 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"}'
```
### 测试3API配置管理接口
```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调用统计表记录
- 统计图表展示
---
## 请告诉我测试结果
请尝试测试并告诉我:
- ✅ 测试成功,看到了数据
- ❌ 遇到问题,具体错误信息
我将根据测试结果继续优化或创建前端代码!

View 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 执行遇到问题,需要帮助
我将根据你的反馈继续后续步骤。

View 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-TokenJWT 验证用,与后端服务共享 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>", "&lt;script&gt;")
.replaceAll("</script>", "&lt;/script&gt;")
.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/**`)也需经过 GatewayGateway 路由已覆盖 `/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 文档