diff --git a/CLAUDE.md b/CLAUDE.md index 329a39c..9d8940b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,14 +4,14 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -HZHub (汇智中台) is an enterprise-level business platform built on HZHub-AI, integrating AI capabilities with ERP data adaptation. It consists of multiple frontend portals, a backend AI service, an ERP service, and a gateway, orchestrated via Docker Compose. +HZHub (汇智中台) is an enterprise-level business platform integrating AI capabilities, system management, approval workflows, and ERP data adaptation. It consists of multiple frontend portals, backend microservices, and an API gateway, orchestrated via Docker Compose. ## Commands ### Docker Deployment (Production-like) ```bash -# Start all services (recommended for integration testing) +# Start all services cd hzhub-deploy docker-compose up -d @@ -20,7 +20,7 @@ docker-compose ps # View logs docker-compose logs -f hzhub-ai -docker-compose logs -f hzhub-admin +docker-compose logs -f hzhub-system # Restart services docker-compose restart hzhub-ai @@ -32,61 +32,47 @@ docker-compose down ### Backend Development (Spring Boot) ```bash -# Run AI service locally (foreground) +# Build hzhub-ai common modules first (required by hzhub-system) cd hzhub-ai +mvn clean install -DskipTests + +# Run AI service locally (foreground) +cd hzhub-ai/hzhub-admin mvn spring-boot:run -Dspring-boot.run.profiles=dev -# Run AI service locally (background) -cd hzhub-ai -./start.sh # Start service in background -./status.sh # Check service status -./logs.sh # View logs -./stop.sh # Stop service -./restart.sh # Restart service +# Run system service locally (foreground) +cd hzhub-system +mvn spring-boot:run -Dspring-boot.run.profiles=dev -# Build all modules -cd hzhub-ai -mvn clean package - -# Build specific module -cd hzhub-ai/hzhub-modules/hzhub-chat -mvn clean package +# Run ERP service locally (foreground) +cd hzhub-erp +mvn spring-boot:run -Dspring-boot.run.profiles=dev # Run tests mvn test ``` -**💡 Tip:** For background service management, see [SERVICE_MANAGEMENT.md](../SERVICE_MANAGEMENT.md) +**Important:** hzhub-system depends on hzhub-ai's common modules. Always run `mvn clean install -DskipTests` in hzhub-ai first. -### Frontend Development (Vue 3 + Vben Admin) +### Frontend Development (Vue 3) ```bash -# Admin portal development +# Admin portal (Vben Admin + Ant Design Vue) cd hzhub-admin -pnpm install # Install dependencies -pnpm dev # Start dev server (foreground) -./start.sh # Start dev server (background) -./status.sh # Check service status -./logs.sh # View logs -pnpm build # Build all packages -pnpm --filter=@vben/web-antd build:prod # Build admin frontend +pnpm install +pnpm dev # Port 5666 -# Employee portal development +# Employee portal (Element Plus) cd hzhub-portal-employee pnpm install -pnpm dev # Start dev server (foreground) -./start.sh # Start dev server (background) -./status.sh # Check service status -./logs.sh # View logs +pnpm dev # Port 5137 -# Dealer portal development +# Dealer portal (Element Plus) cd hzhub-portal-dealer pnpm install -pnpm dev +pnpm dev # Port 5138 ``` -**💡 Tip:** For background service management (start/stop/restart/status/logs), see [SERVICE_MANAGEMENT.md](../SERVICE_MANAGEMENT.md) - ## Architecture ### Multi-Service Structure @@ -99,64 +85,79 @@ pnpm dev └────────────┬────────────────────────────┘ │ ┌────────┴────────┐ - │ hzhub-gateway │ (API Gateway - planned) - │ Spring Cloud │ Auth, routing, rate limiting - └────────┬────────┘ - │ - ┌────────┴────────┬────────────┐ - │ hzhub-ai │ hzhub-erp │ - │ (AI Service) │ (Planned) │ - │ Spring Boot │ JDBC to │ - │ 3.5.8 │ SQL Server│ - └─────────────────┴────────────┘ + │ hzhub-gateway │ (API Gateway, port 8080) + │ Spring Cloud │ JWT auth, routing, rate limiting, XSS + └───┬──────┬──────┘ + │ │ + ┌───▼──┐ ┌▼────────────┐ + │hzhub │ │ hzhub-system│ hzhub-erp + │ -ai │ │ (System Mgmt)│ (SQL Server) + │ 6039 │ │ 8083 │ 8082 + └──────┘ └─────────────┘ ``` -### Backend Module Organization +### Gateway Routes -**hzhub-ai** is organized as a multi-module Maven project: +| Path Prefix | Target Service | Features | +|-------------|---------------|----------| +| `/ai/**` | hzhub-ai:6039 | AI chat, knowledge base, AI workflow | +| `/system/**` | hzhub-system:8083 | Users, roles, permissions, tenants, OSS | +| `/monitor/**` | hzhub-system:8083 | Operation logs, online users, cache | +| `/auth/**` | hzhub-system:8083 | Login, register, captcha, tenant list | +| `/resource/**` | hzhub-system:8083 | Email/SMS code, SSE, WebSocket | +| `/workflow/**` | hzhub-system:8083 | Approval workflows (warm-flow) | +| `/tool/**` | hzhub-system:8083 | Code generator (velocity) | +| `/erp/**` | hzhub-erp:8082 | ERP customer data | -- **hzhub-admin**: Main application entry point (`HZHubAIApplication.java`), configuration files -- **hzhub-common**: Shared utilities (core, redis, mybatis, security, satoken, oss, chat, etc.) -- **hzhub-modules**: Business modules - - **hzhub-chat**: Chat/AI conversation functionality - - **hzhub-system**: System management, users, roles, permissions - - **hzhub-workflow**: Workflow engine (Flowable-based) - - **hzhub-aiflow**: AI workflow orchestration - - **hzhub-generator**: Code generator -- **hzhub-extend**: Extensions (monitoring, job scheduling) +### Backend Service Organization + +**hzhub-ai** (AI Service, port 6039): +- **hzhub-admin**: Main application entry point (`HZHubAIApplication.java`) +- **hzhub-common**: Shared utility modules (core, redis, mybatis, security, oss, chat, etc.) +- **hzhub-modules**: + - **hzhub-chat**: AI conversation, knowledge base, MCP + - **hzhub-aiflow**: AI workflow orchestration (LangGraph4j) +- **hzhub-extend**: Monitoring (spring-boot-admin), job scheduling + +**hzhub-system** (System Service, port 8083): +- Independent Spring Boot service +- Depends on hzhub-ai common modules (must build hzhub-ai first) +- Entry point: `HZHubSystemApplication.java` +- **Auth**: login, register, captcha, tenant list (`/auth/**`, `/resource/**`) +- **System**: users, roles, menus, departments, dicts, config, posts, tenants, OSS, clients, social login (`/system/**`) +- **Monitor**: operation logs, online users, login logs, cache (`/monitor/**`) +- **Workflow**: approval process definitions, instances, tasks (`/workflow/**`) +- **Generator**: code generation from database tables (`/tool/gen`) + +**hzhub-erp** (ERP Service, port 8082): +- SQL Server 2008 R2 data adapter +- Customer management, sales data exploration ### Frontend Architecture -**hzhub-admin** uses a monorepo structure with pnpm + turbo: +**hzhub-admin** (Vben Admin monorepo): +- Vue 3 + TypeScript + Ant Design Vue +- Pinia state management +- Features: system management, workflow, AI flow, knowledge base, chat, monitoring, code generator -``` -hzhub-admin/ -├── apps/ -│ └── web-antd/ # Main admin application (Ant Design Vue) -│ ├── src/ -│ │ ├── api/ # API calls -│ │ ├── views/ # Page components -│ │ ├── router/ # Vue Router config -│ │ └── store/ # Pinia stores -│ └── package.json -├── packages/ # Shared packages -└── package.json # Root monorepo config -``` +**hzhub-portal-employee** (Element Plus): +- Vue 3 + TypeScript + Element Plus +- Features: dashboard, approval center, CRM, dealer management, supply chain, BI reports, AI chat, ERP -**Portal applications** (hzhub-portal-employee, hzhub-portal-dealer) are Vue 3 apps with: -- Composition API (`", "</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 " 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": ""}' +# 请求体应被清洗 + +# 测试限流(快速连续请求) +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 文档 diff --git a/erp-api-verify.sh b/erp-api-verify.sh new file mode 100755 index 0000000..45ee546 --- /dev/null +++ b/erp-api-verify.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# ERP API 管理平台快速验证脚本 +# 用途:通过API快速创建测试数据并验证功能 + +echo "=========================================" +echo " ERP API 管理平台 - 快速功能验证" +echo "=========================================" +echo "" + +# 检查ERP服务健康状态 +echo "1️⃣ 检查ERP服务健康状态..." +HEALTH=$(curl -s http://localhost:8082/actuator/health | jq -r '.status') +if [ "$HEALTH" = "UP" ]; then + echo "✅ ERP服务运行正常 (status: $HEALTH)" +else + echo "❌ ERP服务异常 (status: $HEALTH)" + exit 1 +fi +echo "" + +# 测试SQL Server连接 +echo "2️⃣ 测试SQL Server连接..." +CONN_TEST=$(curl -s http://localhost:8082/erp/test/connection) +echo "响应: $CONN_TEST" +echo "" + +# 提供手动测试说明 +echo "3️⃣ 下一步:手动测试" +echo "---" +echo "请按以下步骤操作:" +echo "" +echo "方法一:通过管理后台(推荐)" +echo " 1. 访问:http://192.168.120.60:5666 或 http://localhost:5666" +echo " 2. 登录后查看左侧菜单 'ERP管理 > API配置'" +echo " 3. 点击'新增'创建测试API:" +echo " - API名称: 测试系统表查询" +echo " - API路径: /erp/dynamic/v1/test_system_tables" +echo " - HTTP方法: GET" +echo " - SQL模板: SELECT TOP 5 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE'" +echo " - 结果类型: LIST" +echo " - 支持分页: 否" +echo " 4. 保存后点击'测试'按钮,输入空参数 {}" +echo "" +echo "方法二:通过curl直接测试" +echo " # 先通过管理后台创建API配置,然后执行:" +echo " curl http://localhost:8082/erp/dynamic/v1/test_system_tables" +echo "" +echo "=========================================" +echo " 测试指南已保存到:" +echo " /data/hzhub/docs/erp-api-quick-test-guide.md" +echo "=========================================" \ No newline at end of file diff --git a/hzhub-admin/apps/web-antd/src/api/erp/api/index.ts b/hzhub-admin/apps/web-antd/src/api/erp/api/index.ts new file mode 100644 index 0000000..cc15f06 --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/api/erp/api/index.ts @@ -0,0 +1,220 @@ +/** + * ERP API 配置管理接口定义 + */ +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; +import { requestClient } from '#/api/request'; + +enum Api { + root = '/erp/api/config', + test = '/erp/api/config/test', + preview = '/erp/api/config/preview', + import = '/erp/api/config/importFromTable', + stats = '/erp/api/config/stats', + errorLog = '/erp/api/config/errorLog', + cache = '/erp/api/config/cache', +} + +/** + * 分页查询API配置列表 + */ +export function apiConfigList(params?: PageQuery) { + return requestClient.get>(Api.root + '/list', { params }); +} + +/** + * 获取API配置详情 + */ +export function apiConfigInfo(apiId: ID) { + return requestClient.get(`${Api.root}/${apiId}`); +} + +/** + * 新增API配置 + */ +export function apiConfigAdd(data: Partial) { + return requestClient.postWithMsg(Api.root, data); +} + +/** + * 修改API配置 + */ +export function apiConfigEdit(data: Partial) { + return requestClient.putWithMsg(Api.root, data); +} + +/** + * 删除API配置 + */ +export function apiConfigRemove(apiIds: IDS) { + return requestClient.deleteWithMsg(`${Api.root}/${apiIds}`); +} + +/** + * 更新API状态 + */ +export function apiConfigChangeStatus(data: Partial) { + return requestClient.putWithMsg(`${Api.root}/changeStatus`, data); +} + +/** + * 从表导入 + */ +export function apiConfigImportFromTable(data: ImportTableRequest) { + return requestClient.postWithMsg(Api.import, data); +} + +/** + * 同步表结构 + */ +export function apiConfigSyncTable(apiId: ID) { + return requestClient.get(`${Api.root}/syncTable/${apiId}`); +} + +/** + * API测试 + */ +export function apiConfigTest(apiId: ID, params: Record) { + return requestClient.post(`${Api.test}/${apiId}`, params); +} + +/** + * API文档预览 + */ +export function apiConfigPreview(apiId: ID) { + return requestClient.get>(`${Api.preview}/${apiId}`); +} + +/** + * 查询调用统计 + */ +export function apiConfigStats(apiId: ID, startTime?: string, endTime?: string) { + return requestClient.get(`${Api.stats}/${apiId}`, { + params: { startTime, endTime }, + }); +} + +/** + * 查询错误日志 + */ +export function apiConfigErrorLog(apiId: ID, limit?: number) { + return requestClient.get(`${Api.errorLog}/${apiId}`, { + params: { limit: limit || 10 }, + }); +} + +/** + * 清除缓存 + */ +export function apiConfigClearCache(apiId: ID) { + return requestClient.deleteWithMsg(`${Api.cache}/${apiId}`); +} + +/** + * API配置VO + */ +export interface ErpApiConfigVO { + apiId: number; + apiName: string; + apiPath: string; + apiMethod: string; + apiDesc: string; + apiVersion: string; + dataSource: string; + sqlTemplate: string; + resultType: string; + supportPagination: number; + pageParamName: string; + sizeParamName: string; + requireAuth: number; + permissionCode: string; + enableCache: number; + cacheKeyTemplate: string; + cacheTtl: number; + sourceTable: string; + sourceTableComment: string; + status: number; + createTime: string; + updateTime: string; + createBy: string; + updateBy: string; + remark: string; +} + +/** + * API配置实体 + */ +export interface ErpApiConfig extends ErpApiConfigVO { + params?: ErpApiParam[]; +} + +/** + * API参数配置 + */ +export interface ErpApiParam { + paramId: number; + apiId: number; + paramName: string; + paramDesc: string; + paramType: string; + paramPosition: string; + isRequired: number; + defaultValue: string; + sqlParamName: string; + sort: number; +} + +/** + * API配置详情响应 + */ +export interface ErpApiConfigInfoResponse { + info: ErpApiConfig; + params: ErpApiParam[]; +} + +/** + * API测试结果 + */ +export interface ApiTestResultVO { + apiPath: string; + testMethod: string; + requestParams: Record; + success: boolean; + data: any; + executionTime: number; + executedSql: string; + errorMessage: string; + errorStack: string; +} + +/** + * API统计响应 + */ +export interface ApiStatsResponse { + totalCalls: number; + avgResponseTime: number; + errorRate: number; +} + +/** + * API错误日志项 + */ +export interface ApiErrorLogItem { + statsId: number; + apiId: number; + callTime: string; + callParams: string; + responseTime: number; + callStatus: string; + errorMessage: string; + errorStack: string; + clientIp: string; + userId: string; +} + +/** + * 从表导入请求 + */ +export interface ImportTableRequest { + tableNames: string[]; + dataSource?: string; +} \ No newline at end of file diff --git a/hzhub-admin/apps/web-antd/src/api/erp/api/model.d.ts b/hzhub-admin/apps/web-antd/src/api/erp/api/model.d.ts new file mode 100644 index 0000000..6696309 --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/api/erp/api/model.d.ts @@ -0,0 +1,97 @@ +/** + * ERP动态API配置类型定义 + */ + +export interface ErpApiConfig { + apiId: number; + apiName: string; + apiPath: string; + apiMethod: string; + apiDesc?: string; + apiVersion: string; + + // 数据源配置 + dataSource: string; + + // SQL配置 + sqlTemplate: string; + resultType: string; + + // 分页配置 + supportPagination: number; + pageParamName?: string; + sizeParamName?: string; + + // 权限配置 + requireAuth: number; + permissionCode?: string; + + // 缓存配置 + enableCache: number; + cacheKeyTemplate?: string; + cacheTtl?: number; + + // 来源表信息 + sourceTable?: string; + sourceTableComment?: string; + + // 状态 + status: number; + createTime?: string; + updateTime?: string; + createBy?: string; + updateBy?: string; + remark?: string; +} + +export interface ErpApiParam { + paramId: number; + apiId: number; + + // 参数基本信息 + paramName: string; + paramDesc?: string; + paramType: string; + + // 参数位置 + paramPosition: string; + + // 参数验证 + isRequired: number; + defaultValue?: string; + + // SQL映射 + sqlParamName?: string; + + // 排序 + sort?: number; + createTime?: string; + updateTime?: string; +} + +export interface ApiTestResult { + apiPath: string; + testMethod: string; + requestParams?: Record; + success: boolean; + data?: any; + executionTime?: number; + executedSql?: string; + errorMessage?: string; + errorStack?: string; +} + +export interface ApiStats { + totalCalls: number; + successCalls: number; + errorCalls: number; + avgResponseTime: number; + maxResponseTime: number; + minResponseTime: number; + errorRate: number; +} + +export interface ApiConfigDetail { + info: ErpApiConfig; + params: ErpApiParam[]; +} \ No newline at end of file diff --git a/hzhub-admin/apps/web-antd/src/api/system/dept/index.ts b/hzhub-admin/apps/web-antd/src/api/system/dept/index.ts index bf7b721..0a6badd 100644 --- a/hzhub-admin/apps/web-antd/src/api/system/dept/index.ts +++ b/hzhub-admin/apps/web-antd/src/api/system/dept/index.ts @@ -60,3 +60,10 @@ export function deptUpdate(data: Partial) { export function deptRemove(deptId: ID) { return requestClient.deleteWithMsg(`${Api.root}/${deptId}`); } + +/** + * 从企业微信同步部门 + */ +export function deptSyncFromWecom() { + return requestClient.postWithMsg(`${Api.root}/syncFromWecom`); +} diff --git a/hzhub-admin/apps/web-antd/src/api/system/user/index.ts b/hzhub-admin/apps/web-antd/src/api/system/user/index.ts index a16cedf..fcf45f8 100644 --- a/hzhub-admin/apps/web-antd/src/api/system/user/index.ts +++ b/hzhub-admin/apps/web-antd/src/api/system/user/index.ts @@ -169,3 +169,10 @@ export function getDeptTree() { export function listUserByDeptId(deptId: ID) { return requestClient.get(`${Api.listDeptUsers}/${deptId}`); } + +/** + * 从企业微信同步用户 + */ +export function userSyncFromWecom() { + return requestClient.postWithMsg(`${Api.root}/syncFromWecom`); +} diff --git a/hzhub-admin/apps/web-antd/src/api/system/wecom-approval-sync/index.ts b/hzhub-admin/apps/web-antd/src/api/system/wecom-approval-sync/index.ts new file mode 100644 index 0000000..b0814df --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/api/system/wecom-approval-sync/index.ts @@ -0,0 +1,68 @@ +import { requestClient } from '#/api/request'; + +enum Api { + syncFull = '/wecom/approval/sync/full', + syncCurrent = '/wecom/approval/sync/current', + syncLogs = '/wecom/approval/sync/logs', +} + +/** + * 全量同步审批数据(管理员) + * @param daysBack 同步近 N 天数据,默认30天 + */ +export function wecomApprovalSyncFull(daysBack = 30) { + return requestClient.post(`${Api.syncFull}`, null, { + params: { daysBack }, + // 设置较长的超时时间(60秒),因为即使异步,启动同步也需要时间 + timeout: 60000, + }); +} + +/** + * 同步当前用户审批数据 + */ +export function wecomApprovalSyncCurrent() { + return requestClient.post(Api.syncCurrent, {}); +} + +/** + * 同步审批模板(管理员) + */ +export function wecomApprovalSyncTemplates() { + return requestClient.post('/wecom/approval/templates/sync', {}); +} + +/** + * 查询同步日志 + */ +export function wecomApprovalSyncLogs(params: { pageNum: number; pageSize: number }) { + return requestClient.get<{ rows: any[]; total: number }>(Api.syncLogs, { params }); +} + +/** + * 获取定时任务状态 + */ +export function getTaskStatus() { + return requestClient.get<{ running: boolean; cron: string }>('/wecom/approval/sync/task/status'); +} + +/** + * 启动定时任务 + */ +export function startTask() { + return requestClient.post('/wecom/approval/sync/task/start', {}); +} + +/** + * 停止定时任务 + */ +export function stopTask() { + return requestClient.post('/wecom/approval/sync/task/stop', {}); +} + +/** + * 设置同步频率 + */ +export function setCron(cron: string) { + return requestClient.post('/wecom/approval/sync/task/cron', null, { params: { cron } }); +} diff --git a/hzhub-admin/apps/web-antd/src/api/system/wecom-config.ts b/hzhub-admin/apps/web-antd/src/api/system/wecom-config.ts new file mode 100644 index 0000000..454b45d --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/api/system/wecom-config.ts @@ -0,0 +1,29 @@ +import { requestClient } from '#/api/request'; + +enum Api { + root = '/wecom/tenant-config', + test = '/wecom/tenant-config/test', +} + +/** + * 获取当前租户的企业微信配置 + */ +export function getWecomConfig() { + return requestClient.get(Api.root); +} + +/** + * 保存/更新企业微信配置 + */ +export function saveWecomConfig(data: any) { + return requestClient.put(Api.root, data); +} + +/** + * 测试企业微信连接 + */ +export function testWecomConfig(corpid: string, corpsecret: string) { + return requestClient.post(Api.test, null, { + params: { corpid, corpsecret }, + }); +} diff --git a/hzhub-admin/apps/web-antd/src/views/erp/api/api-drawer.vue b/hzhub-admin/apps/web-antd/src/views/erp/api/api-drawer.vue new file mode 100644 index 0000000..5b6f601 --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/views/erp/api/api-drawer.vue @@ -0,0 +1,421 @@ + + + \ No newline at end of file diff --git a/hzhub-admin/apps/web-antd/src/views/erp/api/data.tsx b/hzhub-admin/apps/web-antd/src/views/erp/api/data.tsx new file mode 100644 index 0000000..370b1af --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/views/erp/api/data.tsx @@ -0,0 +1,240 @@ +/** + * ERP API 配置管理 - 数据定义 + */ +import type { FormSchemaGetter } from '#/adapter/form'; +import type { VxeGridProps } from '#/adapter/vxe-table'; + +/** + * 搜索表单 Schema + */ +export const querySchema: FormSchemaGetter = () => [ + { + component: 'Input', + fieldName: 'apiName', + label: 'API名称', + componentProps: { + placeholder: '请输入API名称', + allowClear: true, + }, + }, + { + component: 'Input', + fieldName: 'apiPath', + label: 'API路径', + componentProps: { + placeholder: '请输入API路径', + allowClear: true, + }, + }, + { + component: 'Select', + fieldName: 'apiMethod', + label: 'HTTP方法', + componentProps: { + placeholder: '请选择HTTP方法', + options: [ + { label: 'GET', value: 'GET' }, + { label: 'POST', value: 'POST' }, + ], + allowClear: true, + }, + }, + { + component: 'Select', + fieldName: 'status', + label: '状态', + componentProps: { + placeholder: '请选择状态', + options: [ + { label: '启用', value: '1' }, + { label: '禁用', value: '0' }, + ], + allowClear: true, + }, + }, +]; + +/** + * 表格列定义 + */ +export const columns: VxeGridProps['columns'] = [ + { type: 'checkbox', width: 60 }, + { + field: 'apiName', + title: 'API名称', + minWidth: 150, + showOverflow: 'tooltip', + }, + { + field: 'apiPath', + title: 'API路径', + minWidth: 200, + showOverflow: 'tooltip', + }, + { + field: 'apiMethod', + title: 'HTTP方法', + width: 100, + }, + { + field: 'apiVersion', + title: '版本', + width: 80, + }, + { + field: 'resultType', + title: '结果类型', + width: 100, + }, + { + field: 'supportPagination', + title: '分页', + width: 80, + slots: { default: 'pagination' }, + }, + { + field: 'requireAuth', + title: '认证', + width: 80, + slots: { default: 'auth' }, + }, + { + field: 'enableCache', + title: '缓存', + width: 80, + slots: { default: 'cache' }, + }, + { + field: 'status', + title: '状态', + width: 100, + slots: { default: 'status' }, + }, + { + field: 'createTime', + title: '创建时间', + width: 150, + formatter: 'formatDateTime', + }, + { + field: 'action', + title: '操作', + width: 250, + fixed: 'right', + slots: { default: 'action' }, + }, +]; + +/** + * API方法选项 + */ +export const apiMethodOptions = [ + { label: 'GET', value: 'GET' }, + { label: 'POST', value: 'POST' }, +]; + +/** + * API版本选项 + */ +export const apiVersionOptions = [ + { label: 'v1', value: 'v1' }, + { label: 'v2', value: 'v2' }, +]; + +/** + * 结果类型选项 + */ +export const resultTypeOptions = [ + { label: '列表', value: 'LIST' }, + { label: '单条', value: 'SINGLE' }, + { label: '计数', value: 'COUNT' }, +]; + +/** + * 参数类型选项 + */ +export const paramTypeOptions = [ + { label: 'String', value: 'String' }, + { label: 'Integer', value: 'Integer' }, + { label: 'Long', value: 'Long' }, + { label: 'Double', value: 'Double' }, + { label: 'Date', value: 'Date' }, + { label: 'DateTime', value: 'DateTime' }, + { label: 'Boolean', value: 'Boolean' }, +]; + +/** + * 参数位置选项 + */ +export const paramPositionOptions = [ + { label: 'QUERY', value: 'QUERY' }, + { label: 'BODY', value: 'BODY' }, +]; + +/** + * 数据源选项(待扩展) + */ +export const dataSourceOptions = [ + { label: 'ERP数据源', value: 'erp' }, +]; + +/** + * 参数配置表格列定义 + */ +export const paramColumns: VxeGridProps['columns'] = [ + { type: 'checkbox', width: 60 }, + { + field: 'paramName', + title: '参数名称', + minWidth: 150, + editRender: { name: 'input' }, + }, + { + field: 'paramType', + title: '参数类型', + width: 120, + editRender: { + name: 'select', + options: paramTypeOptions, + }, + }, + { + field: 'paramPosition', + title: '参数位置', + width: 100, + editRender: { + name: 'select', + options: paramPositionOptions, + }, + }, + { + field: 'isRequired', + title: '必填', + width: 80, + editRender: { name: 'checkbox' }, + }, + { + field: 'defaultValue', + title: '默认值', + minWidth: 120, + editRender: { name: 'input' }, + }, + { + field: 'paramDesc', + title: '参数描述', + minWidth: 200, + editRender: { name: 'input' }, + }, + { + field: 'sort', + title: '排序', + width: 80, + editRender: { name: 'input', attrs: { type: 'number' } }, + }, + { + field: 'action', + title: '操作', + width: 100, + slots: { default: 'paramAction' }, + }, +]; \ No newline at end of file diff --git a/hzhub-admin/apps/web-antd/src/views/erp/api/doc-preview-modal.vue b/hzhub-admin/apps/web-antd/src/views/erp/api/doc-preview-modal.vue new file mode 100644 index 0000000..2a37b9d --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/views/erp/api/doc-preview-modal.vue @@ -0,0 +1,83 @@ + + + + + \ No newline at end of file diff --git a/hzhub-admin/apps/web-antd/src/views/erp/api/edit-api.vue b/hzhub-admin/apps/web-antd/src/views/erp/api/edit-api.vue new file mode 100644 index 0000000..d60a83d --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/views/erp/api/edit-api.vue @@ -0,0 +1,149 @@ + + + \ No newline at end of file diff --git a/hzhub-admin/apps/web-antd/src/views/erp/api/edit-tabs/basic-setting.vue b/hzhub-admin/apps/web-antd/src/views/erp/api/edit-tabs/basic-setting.vue new file mode 100644 index 0000000..36f0587 --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/views/erp/api/edit-tabs/basic-setting.vue @@ -0,0 +1,240 @@ + + + \ No newline at end of file diff --git a/hzhub-admin/apps/web-antd/src/views/erp/api/edit-tabs/params-config.vue b/hzhub-admin/apps/web-antd/src/views/erp/api/edit-tabs/params-config.vue new file mode 100644 index 0000000..3b98ca9 --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/views/erp/api/edit-tabs/params-config.vue @@ -0,0 +1,93 @@ + + + + + \ No newline at end of file diff --git a/hzhub-admin/apps/web-antd/src/views/erp/api/edit-tabs/sql-template.vue b/hzhub-admin/apps/web-antd/src/views/erp/api/edit-tabs/sql-template.vue new file mode 100644 index 0000000..59876c2 --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/views/erp/api/edit-tabs/sql-template.vue @@ -0,0 +1,293 @@ + + + + + \ No newline at end of file diff --git a/hzhub-admin/apps/web-antd/src/views/erp/api/index.vue b/hzhub-admin/apps/web-antd/src/views/erp/api/index.vue new file mode 100644 index 0000000..66f3711 --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/views/erp/api/index.vue @@ -0,0 +1,248 @@ + + + \ No newline at end of file diff --git a/hzhub-admin/apps/web-antd/src/views/erp/api/test-modal.vue b/hzhub-admin/apps/web-antd/src/views/erp/api/test-modal.vue new file mode 100644 index 0000000..a6f7e48 --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/views/erp/api/test-modal.vue @@ -0,0 +1,203 @@ + + + + + \ No newline at end of file diff --git a/hzhub-admin/apps/web-antd/src/views/erp/stats/error-detail-modal.vue b/hzhub-admin/apps/web-antd/src/views/erp/stats/error-detail-modal.vue new file mode 100644 index 0000000..9eb9e79 --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/views/erp/stats/error-detail-modal.vue @@ -0,0 +1,143 @@ + + + + + \ No newline at end of file diff --git a/hzhub-admin/apps/web-antd/src/views/erp/stats/index.vue b/hzhub-admin/apps/web-antd/src/views/erp/stats/index.vue new file mode 100644 index 0000000..0009ec3 --- /dev/null +++ b/hzhub-admin/apps/web-antd/src/views/erp/stats/index.vue @@ -0,0 +1,588 @@ + + + + + \ No newline at end of file diff --git a/hzhub-admin/apps/web-antd/src/views/system/dept/index.vue b/hzhub-admin/apps/web-antd/src/views/system/dept/index.vue index 0224a8c..3bad1c5 100644 --- a/hzhub-admin/apps/web-antd/src/views/system/dept/index.vue +++ b/hzhub-admin/apps/web-antd/src/views/system/dept/index.vue @@ -4,7 +4,7 @@ import type { VbenFormProps } from '@vben/common-ui'; import type { VxeGridProps } from '#/adapter/vxe-table'; import type { Dept } from '#/api/system/dept/model'; -import { nextTick } from 'vue'; +import { nextTick, ref } from 'vue'; import { Page, useVbenDrawer } from '@vben/common-ui'; import { eachTree, getVxePopupContainer } from '@vben/utils'; @@ -12,7 +12,9 @@ import { eachTree, getVxePopupContainer } from '@vben/utils'; import { Popconfirm, Space } from 'ant-design-vue'; import { useVbenVxeGrid } from '#/adapter/vxe-table'; -import { deptList, deptRemove } from '#/api/system/dept'; +import { deptList, deptRemove, deptSyncFromWecom } from '#/api/system/dept'; + +import { message } from 'ant-design-vue'; import { columns, querySchema } from './data'; import deptDrawer from './dept-drawer.vue'; @@ -95,6 +97,8 @@ const [DeptDrawer, drawerApi] = useVbenDrawer({ connectedComponent: deptDrawer, }); +const syncLoading = ref(false); + function handleAdd() { drawerApi.setData({ update: false }); drawerApi.open(); @@ -116,6 +120,23 @@ async function handleDelete(row: Dept) { await tableApi.query(); } +async function handleSyncFromWecom() { + if (syncLoading.value) return; + syncLoading.value = true; + const loadingMsg = message.loading('正在从企业微信同步部门,请稍候...', 0); + try { + const result = await deptSyncFromWecom(); + loadingMsg(); + message.success(result || '同步成功'); + await tableApi.query(); + } catch { + loadingMsg(); + message.error('同步失败'); + } finally { + syncLoading.value = false; + } +} + /** * 全部展开/折叠 * @param expand 是否展开 @@ -137,6 +158,13 @@ function setExpandOrCollapse(expand: boolean) { {{ $t('pages.common.expand') }} + + 从企业微信同步 + ([]); +const syncLoading = ref(false); const formOptions: VbenFormProps = { schema: querySchema(), @@ -168,6 +172,23 @@ function handleDownloadExcel() { }); } +async function handleSyncFromWecom() { + if (syncLoading.value) return; + syncLoading.value = true; + const loadingMsg = message.loading('正在从企业微信同步用户,请稍候...', 0); + try { + const result = await userSyncFromWecom(); + loadingMsg(); + message.success(result || '同步成功'); + await tableApi.query(); + } catch (error: any) { + loadingMsg(); + message.error(error?.message || '同步失败'); + } finally { + syncLoading.value = false; + } +} + const [UserInfoModal, userInfoModalApi] = useVbenModal({ connectedComponent: userInfoModal, }); @@ -228,6 +249,13 @@ const { hasAccessByCodes } = useAccess(); > {{ $t('pages.common.add') }} + + 从企业微信同步 +