Files
hzhub/hzhub-erp/migrate_erp_api.py
大壮 c2513849b4 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>
2026-05-08 08:00:19 +00:00

293 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
ERP API 迁移脚本
将硬编码的CustomerController API转换为动态配置
"""
import pymysql
import os
from datetime import datetime
# MySQL连接配置
MYSQL_HOST = os.getenv('MYSQL_HOST', '192.168.120.60')
MYSQL_PORT = int(os.getenv('MYSQL_PORT', '3306'))
MYSQL_DB = os.getenv('MYSQL_DB', 'hzhub')
MYSQL_USER = os.getenv('MYSQL_USERNAME', 'root')
MYSQL_PASSWORD = os.getenv('MYSQL_PASSWORD', 'hzhub123')
print(f"连接MySQL: {MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DB}")
conn = pymysql.connect(
host=MYSQL_HOST,
port=MYSQL_PORT,
database=MYSQL_DB,
user=MYSQL_USER,
password=MYSQL_PASSWORD,
charset='utf8mb4'
)
cursor = conn.cursor()
# API配置数据
api_configs = [
{
'api_name': '客户列表查询',
'api_path': '/erp/dynamic/v1/customer/list',
'api_method': 'GET',
'api_desc': '分页查询客户档案列表,支持多条件筛选',
'api_version': 'v1',
'data_source': 'erp',
'sql_template': '''
SELECT TOP #{pageSize} * FROM (
SELECT ROW_NUMBER() OVER (ORDER BY CLTCODE) AS rn,
CLTCODE AS customerCode,
CLTNAME AS customerName,
COMPANY_ID AS companyCode,
COMPANY_NAME AS companyName,
BRAND AS brand,
BRANDNAME AS brandName,
LINKMAN AS contactName,
AREAID AS salesAreaCode,
AREANAME AS salesAreaName,
SALESID_T AS salesPersonCode,
SALESNAME_T AS salesPersonName,
SALEDOCID AS saleDocCode,
SALEDOCNAME AS saleDocName,
CLTPRICENO AS pricePlanCode,
CLTPRICENAME AS pricePlanName,
CLTTYPE AS customerType,
STREET AS address,
TEL1 AS phone,
EMAIL AS email,
SDORGID AS sdOrgCode,
SDORGNAME AS sdOrgName,
province, city, ISSTOP AS isStop
FROM SCLTGENERAL
WHERE 1=1
AND #{keyword} IS NOT NULL THEN (CLTCODE LIKE '%#{keyword}%'
OR CLTNAME LIKE '%#{keyword}%'
OR LINKMAN LIKE '%#{keyword}%'
OR AREANAME LIKE '%#{keyword}%'
OR SALESNAME_T LIKE '%#{keyword}%')
AND #{companyCode} IS NOT NULL THEN COMPANY_ID = #{companyCode}
AND #{salesAreaCode} IS NOT NULL THEN AREAID = #{salesAreaCode}
AND #{brand} IS NOT NULL THEN BRAND = #{brand}
) t WHERE rn > (#{pageNum} - 1) * #{pageSize} ORDER BY rn
''',
'result_type': 'LIST',
'support_pagination': 1,
'page_param_name': 'pageNum',
'size_param_name': 'pageSize',
'require_auth': 0,
'permission_code': None,
'enable_cache': 0,
'source_table': 'SCLTGENERAL',
'source_table_comment': '客户档案主表',
'status': 1,
'params': [
{'param_name': 'pageNum', 'param_desc': '页码', 'param_type': 'Integer', 'param_position': 'QUERY', 'is_required': 0, 'default_value': '1', 'sort': 1},
{'param_name': 'pageSize', 'param_desc': '页大小', 'param_type': 'Integer', 'param_position': 'QUERY', 'is_required': 0, 'default_value': '10', 'sort': 2},
{'param_name': 'keyword', 'param_desc': '关键词(客户编码/名称/联系人/销区/销售员)', 'param_type': 'String', 'param_position': 'QUERY', 'is_required': 0, 'default_value': None, 'sort': 3},
{'param_name': 'companyCode', 'param_desc': '公司编码', 'param_type': 'String', 'param_position': 'QUERY', 'is_required': 0, 'default_value': None, 'sort': 4},
{'param_name': 'salesAreaCode', 'param_desc': '销区编码', 'param_type': 'String', 'param_position': 'QUERY', 'is_required': 0, 'default_value': None, 'sort': 5},
{'param_name': 'brand', 'param_desc': '品牌编码', 'param_type': 'String', 'param_position': 'QUERY', 'is_required': 0, 'default_value': None, 'sort': 6},
]
},
{
'api_name': '客户详情查询',
'api_path': '/erp/dynamic/v1/customer/detail',
'api_method': 'GET',
'api_desc': '根据客户编码查询客户详细信息',
'api_version': 'v1',
'data_source': 'erp',
'sql_template': '''
SELECT
CLTCODE AS customerCode,
CLTNAME AS customerName,
COMPANY_ID AS companyCode,
COMPANY_NAME AS companyName,
BRAND AS brand,
BRANDNAME AS brandName,
LINKMAN AS contactName,
AREAID AS salesAreaCode,
AREANAME AS salesAreaName,
SALESID_T AS salesPersonCode,
SALESNAME_T AS salesPersonName,
SALEDOCID AS saleDocCode,
SALEDOCNAME AS saleDocName,
CLTPRICENO AS pricePlanCode,
CLTPRICENAME AS pricePlanName,
CLTTYPE AS customerType,
STREET AS address,
TEL1 AS phone,
EMAIL AS email,
SDORGID AS sdOrgCode,
SDORGNAME AS sdOrgName,
province, city, ISSTOP AS isStop
FROM SCLTGENERAL
WHERE CLTCODE = #{customerCode}
''',
'result_type': 'SINGLE',
'support_pagination': 0,
'require_auth': 0,
'enable_cache': 1,
'cache_key_template': 'customer:#{customerCode}',
'cache_ttl': 600,
'source_table': 'SCLTGENERAL',
'source_table_comment': '客户档案主表',
'status': 1,
'params': [
{'param_name': 'customerCode', 'param_desc': '客户编码', 'param_type': 'String', 'param_position': 'QUERY', 'is_required': 1, 'default_value': None, 'sort': 1},
]
},
{
'api_name': '销区列表查询',
'api_path': '/erp/dynamic/v1/customer/sales-areas',
'api_method': 'GET',
'api_desc': '获取所有销区列表从OSDORG销售组织表',
'api_version': 'v1',
'data_source': 'erp',
'sql_template': '''
SELECT DISTINCT ORGCODE AS salesAreaCode, ORGNAME AS salesAreaName
FROM OSDORG
WHERE ORGLEVEL = 3
AND ORGCODE IS NOT NULL
AND ORGNAME IS NOT NULL
AND ISENABLE = 1
ORDER BY ORGCODE
''',
'result_type': 'LIST',
'support_pagination': 0,
'require_auth': 0,
'enable_cache': 1,
'cache_key_template': 'sales-areas',
'cache_ttl': 3600,
'source_table': 'OSDORG',
'source_table_comment': '销售组织表',
'status': 1,
'params': []
},
{
'api_name': '品牌列表查询',
'api_path': '/erp/dynamic/v1/customer/brands',
'api_method': 'GET',
'api_desc': '获取所有品牌列表',
'api_version': 'v1',
'data_source': 'erp',
'sql_template': '''
SELECT DISTINCT BRAND AS brand, BRANDNAME AS brandName
FROM SCLTGENERAL
WHERE BRAND IS NOT NULL AND BRANDNAME IS NOT NULL
ORDER BY BRAND
''',
'result_type': 'LIST',
'support_pagination': 0,
'require_auth': 0,
'enable_cache': 1,
'cache_key_template': 'brands',
'cache_ttl': 3600,
'source_table': 'SCLTGENERAL',
'source_table_comment': '客户档案主表',
'status': 1,
'params': []
},
]
# 插入API配置
print("\n开始迁移ERP API配置...")
for api in api_configs:
print(f"\n处理API: {api['api_name']}")
# 插入主表
sql_insert_config = """
INSERT INTO erp_api_config (
api_name, api_path, api_method, api_desc, api_version,
data_source, sql_template, result_type,
support_pagination, page_param_name, size_param_name,
require_auth, permission_code,
enable_cache, cache_key_template, cache_ttl,
source_table, source_table_comment,
status, create_time, create_by
) VALUES (
%s, %s, %s, %s, %s,
%s, %s, %s,
%s, %s, %s,
%s, %s,
%s, %s, %s,
%s, %s,
%s, %s, %s
)
"""
cursor.execute(sql_insert_config, (
api['api_name'], api['api_path'], api['api_method'], api['api_desc'], api['api_version'],
api['data_source'], api['sql_template'].strip(), api['result_type'],
api['support_pagination'], api.get('page_param_name', 'pageNum'), api.get('size_param_name', 'pageSize'),
api['require_auth'], api.get('permission_code'),
api['enable_cache'], api.get('cache_key_template'), api.get('cache_ttl', 300),
api['source_table'], api['source_table_comment'],
api['status'], datetime.now(), 'admin'
))
api_id = cursor.lastrowid
print(f" ✓ API配置已插入 (api_id: {api_id})")
# 插入参数表
for param in api['params']:
sql_insert_param = """
INSERT INTO erp_api_param (
api_id, param_name, param_desc, param_type,
param_position, is_required, default_value, sort,
create_time
) VALUES (
%s, %s, %s, %s,
%s, %s, %s, %s,
%s
)
"""
cursor.execute(sql_insert_param, (
api_id, param['param_name'], param['param_desc'], param['param_type'],
param['param_position'], param['is_required'], param.get('default_value'), param['sort'],
datetime.now()
))
if api['params']:
print(f" ✓ 已插入 {len(api['params'])} 个参数配置")
conn.commit()
# 验证插入结果
print("\n验证迁移结果:")
cursor.execute("SELECT COUNT(*) FROM erp_api_config")
total_configs = cursor.fetchone()[0]
print(f" ✓ erp_api_config: {total_configs} 条记录")
cursor.execute("SELECT COUNT(*) FROM erp_api_param")
total_params = cursor.fetchone()[0]
print(f" ✓ erp_api_param: {total_params} 条记录")
# 显示插入的API列表
print("\n已迁移的API列表:")
cursor.execute("""
SELECT api_id, api_name, api_path, api_method, result_type, support_pagination
FROM erp_api_config
ORDER BY api_id
""")
for row in cursor.fetchall():
api_id, name, path, method, result_type, pagination = row
pagination_str = "分页" if pagination else "不分页"
print(f" [{api_id}] {name} - {method} {path} ({result_type}, {pagination_str})")
cursor.close()
conn.close()
print("\n✅ ERP API迁移完成")
print("\n下一步:")
print(" 1. 测试动态API:")
print(" curl 'http://192.168.120.60:8080/erp/dynamic/v1/customer/list?pageNum=1&pageSize=10'")
print(" 2. 前端访问动态API管理界面:")
print(" http://192.168.120.60:5666/erp/api")
print(" 3. 可选废弃旧的CustomerController保留作为备用或对比测试")