commit 8d04a0fd6eadf53718c3b71932211179a12af5ea Author: wurenzhi Date: Mon Mar 30 16:55:04 2026 +0800 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..f8dc5dc --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# Crawlful Hub Java Backend + +基于 Spring Boot 3.x 的 Java 后端实现,用于 Crawlful Hub 项目。 + +## 技术栈 + +- **框架**: Spring Boot 3.2.0 +- **Java 版本**: Java 17 +- **数据库**: MySQL 8.0 +- **缓存**: Redis +- **认证**: JWT +- **构建工具**: Maven + +## 功能模块 + +- 认证与授权 +- 商品管理 +- 订单管理 +- 支付处理 +- 物流跟踪 +- 数据采集 +- 报表分析 +- 系统监控 +- 告警管理 +- 配置管理 +- 审计日志 + +## 快速开始 + +### 环境要求 + +- JDK 17+ +- MySQL 8.0+ +- Redis 6.0+ +- Maven 3.6+ + +### 配置 + +编辑 `src/main/resources/application.yml` 文件,配置数据库连接、Redis 连接等信息。 + +### 运行 + +```bash +mvn spring-boot:run +``` + +### 构建 + +```bash +mvn clean package +``` + +### 运行测试 + +```bash +mvn test +``` + +## 文档 + +- [项目概览](docs/00_Project_Overview.md) +- [架构设计](docs/01_Architecture_Design.md) +- [API 文档](docs/02_API_Documentation.md) +- [部署指南](docs/03_Deployment_Guide.md) +- [测试指南](docs/04_Test_Guide.md) +- [数据库设计](docs/05_Database_Design.md) +- [安全指南](docs/06_Security_Guide.md) +- [维护指南](docs/07_Maintenance_Guide.md) +- [国际化指南](docs/08_Internationalization_Guide.md) +- [开发指南](docs/09_Development_Guide.md) +- [API 变更日志](docs/10_API_Changelog.md) + +## 许可证 + +MIT License diff --git a/docs/00_Project_Overview.md b/docs/00_Project_Overview.md new file mode 100644 index 0000000..b076401 --- /dev/null +++ b/docs/00_Project_Overview.md @@ -0,0 +1,90 @@ +# 项目概览 + +## 1. 项目简介 + +Crawlful Hub 是一个基于 Spring Boot 3.x 的后端服务,用于管理商品、订单、支付、物流等业务流程。本项目是从原有的 Node.js 后端平移而来,保持了与原系统的 API 兼容性,同时提供了更强大的性能和可扩展性。 + +## 2. 技术栈 + +- **框架**: Spring Boot 3.2.0 +- **语言**: Java 17 +- **数据库**: MySQL 8.0 +- **缓存**: Redis +- **认证**: JWT +- **构建工具**: Maven +- **API 文档**: Springdoc OpenAPI +- **日志系统**: Logback +- **测试**: JUnit 5 + +## 3. 核心功能 + +- **用户认证与授权**: 基于 JWT 的身份验证和基于角色的访问控制 +- **商品管理**: 商品的创建、查询、更新和删除 +- **订单管理**: 订单的创建、查询、更新和状态管理 +- **支付管理**: 支付的创建、查询、更新和状态管理 +- **物流管理**: 物流的创建、查询、更新和状态管理 +- **系统监控**: 系统健康状态、性能指标、服务状态等 +- **告警管理**: 系统告警的创建、查询、更新和处理 +- **审计日志**: 系统操作的审计记录 +- **配置管理**: 系统配置的管理 +- **数据管理**: 数据的导入、导出和处理 +- **报表生成**: 系统报表的生成 + +## 4. 项目结构 + +``` +serverjava/ +├── src/ +│ ├── main/ +│ │ ├── java/ +│ │ │ └── com/ +│ │ │ └── crawlful/ +│ │ │ └── hub/ +│ │ │ ├── api/ # API 控制器 +│ │ │ ├── config/ # 系统配置 +│ │ │ ├── exception/ # 异常处理 +│ │ │ ├── model/ # 数据模型 +│ │ │ ├── service/ # 业务逻辑 +│ │ │ ├── util/ # 工具类 +│ │ │ └── Application.java # 应用入口 +│ │ └── resources/ +│ │ ├── db/ # 数据库迁移脚本 +│ │ ├── i18n/ # 国际化资源 +│ │ ├── application.yml # 应用配置 +│ │ └── logback.xml # 日志配置 +│ └── test/ # 测试代码 +├── docs/ # 项目文档 +├── README.md # 项目说明 +├── index.md # 项目索引 +└── pom.xml # Maven 配置 +``` + +## 5. API 兼容性 + +本项目保持了与原 Node.js 后端的 API 兼容性,确保前端和客户端代码无需修改即可正常工作。API 路径、参数和返回格式都与原系统保持一致。 + +## 6. 性能优化 + +- **数据库连接池**: 使用 HikariCP 优化数据库连接管理 +- **缓存**: 使用 Redis 缓存高频访问数据 +- **分页查询**: 实现分页查询,避免一次性加载大量数据 +- **索引优化**: 为数据库表添加适当的索引,提高查询性能 + +## 7. 安全增强 + +- **CSRF 保护**: 实现 CSRF 令牌验证 +- **速率限制**: 限制 API 请求频率,防止恶意请求 +- **密码加密**: 使用 BCrypt 加密用户密码 +- **JWT 认证**: 使用 JWT 进行身份验证,确保 API 安全 + +## 8. 国际化支持 + +本项目支持国际化,提供了英文和中文的资源文件,可以根据请求的 Accept-Language 头自动切换语言。 + +## 9. 监控与告警 + +本项目实现了系统监控和告警功能,可以实时监控系统的健康状态、性能指标和服务状态,并在异常情况下生成告警。 + +## 10. 部署与运维 + +本项目使用 Maven 构建,可以部署到任何支持 Java 17 的环境中。详细的部署方法请参考部署文档。 diff --git a/docs/01_Architecture_Design.md b/docs/01_Architecture_Design.md new file mode 100644 index 0000000..d5a94d7 --- /dev/null +++ b/docs/01_Architecture_Design.md @@ -0,0 +1,181 @@ +# 架构设计 + +## 1. 架构概述 + +Crawlful Hub 采用分层架构设计,确保系统的可维护性、可扩展性和可测试性。系统分为控制器层、服务层、仓库层和模型层,各层之间通过依赖注入进行解耦,实现了高内聚、低耦合的设计目标。 + +## 2. 分层架构 + +### 2.1 控制器层(Controller) + +控制器层负责处理 HTTP 请求,包括请求参数的验证、调用服务层的方法、处理异常和返回响应。控制器层不包含业务逻辑,只负责请求和响应的处理。 + +**主要职责**: +- 接收 HTTP 请求 +- 验证请求参数 +- 调用服务层处理业务逻辑 +- 处理异常 +- 返回 HTTP 响应 + +**核心控制器**: +- `AuthController`:处理认证相关的请求 +- `UserController`:处理用户管理相关的请求 +- `ProductController`:处理商品管理相关的请求 +- `OrderController`:处理订单管理相关的请求 +- `PaymentController`:处理支付管理相关的请求 +- `LogisticsController`:处理物流管理相关的请求 +- `MonitoringController`:处理监控相关的请求 +- `AlertController`:处理告警相关的请求 +- `AuditController`:处理审计相关的请求 +- `ConfigController`:处理配置相关的请求 +- `DataController`:处理数据相关的请求 +- `ReportController`:处理报表相关的请求 + +### 2.2 服务层(Service) + +服务层是业务逻辑的核心,负责实现业务规则、处理业务流程和协调多个仓库的操作。服务层通过依赖注入使用仓库层的方法,实现业务逻辑的封装和复用。 + +**主要职责**: +- 实现业务逻辑 +- 处理业务流程 +- 协调多个仓库的操作 +- 管理事务 +- 处理异常 + +**核心服务**: +- `AuthService`:处理认证相关的业务逻辑 +- `UserService`:处理用户管理相关的业务逻辑 +- `ProductService`:处理商品管理相关的业务逻辑 +- `OrderService`:处理订单管理相关的业务逻辑 +- `PaymentService`:处理支付管理相关的业务逻辑 +- `LogisticsService`:处理物流管理相关的业务逻辑 +- `MonitoringService`:处理监控相关的业务逻辑 +- `AlertService`:处理告警相关的业务逻辑 +- `AuditService`:处理审计相关的业务逻辑 +- `ConfigService`:处理配置相关的业务逻辑 +- `DataService`:处理数据相关的业务逻辑 +- `ReportService`:处理报表相关的业务逻辑 + +### 2.3 仓库层(Repository) + +仓库层负责数据访问,包括数据库的 CRUD 操作。仓库层通过 Spring Data JPA 实现,提供了简洁的数据库操作接口。 + +**主要职责**: +- 实现数据库的 CRUD 操作 +- 提供查询方法 +- 管理数据库事务 + +**核心仓库**: +- `UserRepository`:处理用户数据的访问 +- `ProductRepository`:处理商品数据的访问 +- `OrderRepository`:处理订单数据的访问 +- `PaymentRepository`:处理支付数据的访问 +- `LogisticsRepository`:处理物流数据的访问 +- `AlertRepository`:处理告警数据的访问 +- `AuditRepository`:处理审计数据的访问 +- `ConfigRepository`:处理配置数据的访问 + +### 2.4 模型层(Model) + +模型层定义了数据结构,包括实体类和 DTO(数据传输对象)。实体类对应数据库表,DTO 用于在不同层之间传递数据。 + +**主要职责**: +- 定义数据结构 +- 映射数据库表 +- 提供数据访问方法 + +**核心模型**: +- `User`:用户模型 +- `Product`:商品模型 +- `Order`:订单模型 +- `Payment`:支付模型 +- `Logistics`:物流模型 +- `Alert`:告警模型 +- `Audit`:审计模型 +- `Config`:配置模型 + +## 3. 核心流程 + +### 3.1 用户认证流程 + +1. **用户注册**:用户提交注册信息,`AuthController` 调用 `AuthService` 的 `register` 方法,验证输入数据,创建用户并返回用户信息。 +2. **用户登录**:用户提交登录信息,`AuthController` 调用 `AuthService` 的 `login` 方法,验证用户名和密码,生成 JWT 令牌并返回。 +3. **令牌验证**:请求携带 JWT 令牌,`SecurityConfig` 验证令牌的有效性,确保用户已认证。 + +### 3.2 商品管理流程 + +1. **创建商品**:用户提交商品信息,`ProductController` 调用 `ProductService` 的 `create` 方法,验证输入数据,创建商品并返回商品信息。 +2. **查询商品**:用户请求商品列表,`ProductController` 调用 `ProductService` 的 `getAll` 方法,根据过滤条件查询商品并返回。 +3. **更新商品**:用户提交商品更新信息,`ProductController` 调用 `ProductService` 的 `update` 方法,验证输入数据,更新商品并返回更新后的商品信息。 +4. **删除商品**:用户请求删除商品,`ProductController` 调用 `ProductService` 的 `delete` 方法,删除商品并返回成功信息。 + +### 3.3 订单管理流程 + +1. **创建订单**:用户提交订单信息,`OrderController` 调用 `OrderService` 的 `createOrder` 方法,验证输入数据,创建订单并返回订单信息。 +2. **查询订单**:用户请求订单列表,`OrderController` 调用 `OrderService` 的 `getOrders` 方法,根据过滤条件查询订单并返回。 +3. **更新订单**:用户提交订单更新信息,`OrderController` 调用 `OrderService` 的 `updateOrder` 方法,验证输入数据,更新订单并返回更新后的订单信息。 +4. **订单状态流转**:用户请求更新订单状态,`OrderController` 调用 `OrderService` 的 `transitionOrderStatus` 方法,更新订单状态并返回成功信息。 + +### 3.4 支付管理流程 + +1. **创建支付**:用户提交支付信息,`PaymentController` 调用 `PaymentService` 的 `create` 方法,验证输入数据,创建支付并返回支付信息。 +2. **查询支付**:用户请求支付列表,`PaymentController` 调用 `PaymentService` 的 `getAll` 方法,根据过滤条件查询支付并返回。 +3. **更新支付**:用户提交支付更新信息,`PaymentController` 调用 `PaymentService` 的 `update` 方法,验证输入数据,更新支付并返回更新后的支付信息。 + +### 3.5 物流管理流程 + +1. **创建物流**:用户提交物流信息,`LogisticsController` 调用 `LogisticsService` 的 `create` 方法,验证输入数据,创建物流并返回物流信息。 +2. **查询物流**:用户请求物流列表,`LogisticsController` 调用 `LogisticsService` 的 `getAll` 方法,根据过滤条件查询物流并返回。 +3. **更新物流**:用户提交物流更新信息,`LogisticsController` 调用 `LogisticsService` 的 `update` 方法,验证输入数据,更新物流并返回更新后的物流信息。 + +### 3.6 监控与告警流程 + +1. **系统健康检查**:用户请求系统健康状态,`MonitoringController` 调用 `MonitoringService` 的 `getSystemHealth` 方法,返回系统健康状态。 +2. **性能指标获取**:用户请求系统性能指标,`MonitoringController` 调用 `MonitoringService` 的 `getPerformanceMetrics` 方法,返回系统性能指标。 +3. **告警创建**:系统检测到异常,`AlertService` 创建告警并存储到数据库。 +4. **告警处理**:用户请求处理告警,`AlertController` 调用 `AlertService` 的 `resolveAlert` 方法,更新告警状态为已解决。 + +## 4. 技术实现细节 + +### 4.1 数据库设计 + +- **用户表**:存储用户信息,包括用户名、密码、邮箱、角色等。 +- **商品表**:存储商品信息,包括标题、描述、价格、数量等。 +- **订单表**:存储订单信息,包括订单号、状态、总金额等。 +- **支付表**:存储支付信息,包括支付方式、金额、状态等。 +- **物流表**:存储物流信息,包括物流方式、跟踪号、状态等。 +- **告警表**:存储告警信息,包括告警类型、严重程度、状态等。 +- **审计表**:存储审计日志,包括操作类型、资源类型、操作人等。 +- **配置表**:存储系统配置,包括配置键、配置值、配置类型等。 + +### 4.2 缓存实现 + +使用 Redis 作为缓存存储,缓存高频访问的数据,如用户信息、商品信息等,提高系统的响应速度。 + +### 4.3 安全实现 + +- **JWT 认证**:使用 JWT 进行身份验证,确保 API 安全。 +- **CSRF 保护**:实现 CSRF 令牌验证,防止 CSRF 攻击。 +- **速率限制**:限制 API 请求频率,防止恶意请求。 +- **密码加密**:使用 BCrypt 加密用户密码,确保密码安全。 + +### 4.4 国际化实现 + +使用 Spring 的国际化支持,提供英文和中文的资源文件,根据请求的 Accept-Language 头自动切换语言。 + +### 4.5 监控与告警实现 + +- **系统健康检查**:实现系统健康状态检查,包括数据库连接、缓存状态等。 +- **性能指标监控**:实现系统性能指标监控,包括响应时间、请求次数等。 +- **告警管理**:实现告警的创建、查询、更新和处理,及时发现和处理系统异常。 + +## 5. 扩展性设计 + +- **模块化设计**:系统采用模块化设计,各模块之间通过接口进行通信,便于功能扩展和代码维护。 +- **依赖注入**:使用 Spring 的依赖注入,实现组件的解耦,便于单元测试和功能扩展。 +- **配置化**:系统的配置通过 application.yml 文件进行配置,便于环境切换和参数调整。 +- **插件化**:系统支持插件化开发,可以通过插件扩展系统功能。 + +## 6. 总结 + +Crawlful Hub 采用分层架构设计,实现了高内聚、低耦合的系统结构。系统通过控制器层、服务层、仓库层和模型层的划分,实现了业务逻辑的封装和复用,提高了系统的可维护性、可扩展性和可测试性。同时,系统通过缓存、安全、国际化等技术的应用,提高了系统的性能和用户体验。 diff --git a/docs/02_API_Documentation.md b/docs/02_API_Documentation.md new file mode 100644 index 0000000..8243108 --- /dev/null +++ b/docs/02_API_Documentation.md @@ -0,0 +1,1965 @@ +# API 文档 + +## 1. 认证 API + +### 1.1 注册用户 + +**路径**: `/v1/auth/register` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| username | string | 是 | 用户名 | +| password | string | 是 | 密码 | +| email | string | 是 | 邮箱 | +| role | string | 是 | 角色 | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +### 1.2 用户登录 + +**路径**: `/v1/auth/login` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| username | string | 是 | 用户名 | +| password | string | 是 | 密码 | + +**返回格式**: +```json +{ + "success": true, + "data": { + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + } +} +``` + +## 2. 用户 API + +### 2.1 创建用户 + +**路径**: `/v1/user` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| username | string | 是 | 用户名 | +| password | string | 是 | 密码 | +| email | string | 是 | 邮箱 | +| role | string | 是 | 角色 | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +### 2.2 获取用户列表 + +**路径**: `/v1/user` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": [ + { + "id": 1, + "username": "admin", + "email": "admin@example.com", + "role": "ADMIN", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-01T00:00:00.000Z" + } + ] +} +``` + +### 2.3 获取用户详情 + +**路径**: `/v1/user/{id}` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 用户 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "username": "admin", + "email": "admin@example.com", + "role": "ADMIN", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-01T00:00:00.000Z" + } +} +``` + +### 2.4 更新用户 + +**路径**: `/v1/user/{id}` + +**方法**: `PUT` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 用户 ID | +| username | string | 否 | 用户名 | +| password | string | 否 | 密码 | +| email | string | 否 | 邮箱 | +| role | string | 否 | 角色 | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "username": "admin", + "email": "admin@example.com", + "role": "ADMIN", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-01T00:00:00.000Z" + } +} +``` + +### 2.5 删除用户 + +**路径**: `/v1/user/{id}` + +**方法**: `DELETE` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 用户 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true +} +``` + +## 3. 商品 API + +### 3.1 创建商品 + +**路径**: `/v1/product` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| title | string | 是 | 商品标题 | +| description | string | 否 | 商品描述 | +| mainImage | string | 否 | 商品主图 | +| platform | string | 否 | 平台 | +| platformProductId | string | 否 | 平台商品 ID | +| price | double | 否 | 价格 | +| costPrice | double | 否 | 成本价格 | +| quantity | int | 否 | 数量 | +| status | string | 否 | 状态 | +| phash | string | 否 | 图片哈希 | +| semanticHash | string | 否 | 语义哈希 | +| vectorEmbedding | string | 否 | 向量嵌入 | +| attributes | string | 否 | 属性 | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +### 3.2 获取商品列表 + +**路径**: `/v1/product` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| platform | string | 否 | 平台 | +| status | string | 否 | 状态 | +| page | int | 否 | 页码,默认 1 | +| size | int | 否 | 每页数量,默认 10 | + +**返回格式**: +```json +{ + "success": true, + "data": [ + { + "id": 1, + "title": "商品标题", + "description": "商品描述", + "mainImage": "图片 URL", + "platform": "平台", + "platformProductId": "平台商品 ID", + "price": 100.00, + "costPrice": 80.00, + "quantity": 100, + "status": "ACTIVE", + "phash": "图片哈希", + "semanticHash": "语义哈希", + "vectorEmbedding": "向量嵌入", + "attributes": "属性", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-01T00:00:00.000Z" + } + ], + "page": 1, + "size": 10 +} +``` + +### 3.3 获取商品详情 + +**路径**: `/v1/product/{id}` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 商品 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "title": "商品标题", + "description": "商品描述", + "mainImage": "图片 URL", + "platform": "平台", + "platformProductId": "平台商品 ID", + "price": 100.00, + "costPrice": 80.00, + "quantity": 100, + "status": "ACTIVE", + "phash": "图片哈希", + "semanticHash": "语义哈希", + "vectorEmbedding": "向量嵌入", + "attributes": "属性", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-01T00:00:00.000Z" + } +} +``` + +### 3.4 更新商品 + +**路径**: `/v1/product/{id}` + +**方法**: `PUT` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 商品 ID | +| title | string | 否 | 商品标题 | +| description | string | 否 | 商品描述 | +| mainImage | string | 否 | 商品主图 | +| platform | string | 否 | 平台 | +| platformProductId | string | 否 | 平台商品 ID | +| price | double | 否 | 价格 | +| costPrice | double | 否 | 成本价格 | +| quantity | int | 否 | 数量 | +| status | string | 否 | 状态 | +| phash | string | 否 | 图片哈希 | +| semanticHash | string | 否 | 语义哈希 | +| vectorEmbedding | string | 否 | 向量嵌入 | +| attributes | string | 否 | 属性 | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "title": "商品标题", + "description": "商品描述", + "mainImage": "图片 URL", + "platform": "平台", + "platformProductId": "平台商品 ID", + "price": 100.00, + "costPrice": 80.00, + "quantity": 100, + "status": "ACTIVE", + "phash": "图片哈希", + "semanticHash": "语义哈希", + "vectorEmbedding": "向量嵌入", + "attributes": "属性", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-01T00:00:00.000Z" + } +} +``` + +### 3.5 删除商品 + +**路径**: `/v1/product/{id}` + +**方法**: `DELETE` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 商品 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true +} +``` + +### 3.6 商品清洗与本地化 + +**路径**: `/v1/product/{id}/wash-and-localize` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 商品 ID | +| tenantId | string | 是 | 租户 ID | +| targetMarket | string | 是 | 目标市场 | +| targetLang | string | 是 | 目标语言 | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "title": "商品标题 (Localized for 目标市场)", + "description": "商品描述 (Localized for 目标市场)", + "targetMarket": "目标市场", + "targetLang": "目标语言", + "status": "LOCALIZED" + } +} +``` + +### 3.7 商品套利分析 + +**路径**: `/v1/product/{id}/analyze-arbitrage` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 商品 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "productId": 1, + "price": 100.00, + "costPrice": 80.00, + "profitMargin": 20.0, + "arbitrageOpportunity": true + } +} +``` + +## 4. 订单 API + +### 4.1 创建订单 + +**路径**: `/v1/order` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| shopId | string | 否 | 店铺 ID | +| platform | string | 否 | 平台 | +| platformOrderId | string | 否 | 平台订单 ID | +| status | string | 否 | 状态 | +| totalAmount | double | 否 | 总金额 | +| currency | string | 否 | 货币 | +| customerInfo | string | 否 | 客户信息 | +| items | string | 否 | 商品列表 | +| shippingAddress | string | 否 | shipping 地址 | +| trackingNumber | string | 否 | 跟踪号 | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +### 4.2 获取订单列表 + +**路径**: `/v1/order` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| platform | string | 否 | 平台 | +| status | string | 否 | 状态 | +| page | int | 否 | 页码 | +| pageSize | int | 否 | 每页数量 | +| startDate | string | 否 | 开始日期 | +| endDate | string | 否 | 结束日期 | + +**返回格式**: +```json +{ + "success": true, + "data": [ + { + "id": 1, + "shopId": "店铺 ID", + "platform": "平台", + "platformOrderId": "平台订单 ID", + "status": "PENDING", + "totalAmount": 100.00, + "currency": "USD", + "customerInfo": "客户信息", + "items": "商品列表", + "shippingAddress": "shipping 地址", + "trackingNumber": "跟踪号", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-01T00:00:00.000Z" + } + ] +} +``` + +### 4.3 获取订单详情 + +**路径**: `/v1/order/{id}` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | string | 是 | 订单 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "shopId": "店铺 ID", + "platform": "平台", + "platformOrderId": "平台订单 ID", + "status": "PENDING", + "totalAmount": 100.00, + "currency": "USD", + "customerInfo": "客户信息", + "items": "商品列表", + "shippingAddress": "shipping 地址", + "trackingNumber": "跟踪号", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-01T00:00:00.000Z" + } +} +``` + +### 4.4 更新订单 + +**路径**: `/v1/order/{id}` + +**方法**: `PUT` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | string | 是 | 订单 ID | +| status | string | 否 | 状态 | +| totalAmount | double | 否 | 总金额 | +| currency | string | 否 | 货币 | +| customerInfo | string | 否 | 客户信息 | +| items | string | 否 | 商品列表 | +| shippingAddress | string | 否 | shipping 地址 | +| trackingNumber | string | 否 | 跟踪号 | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "shopId": "店铺 ID", + "platform": "平台", + "platformOrderId": "平台订单 ID", + "status": "PENDING", + "totalAmount": 100.00, + "currency": "USD", + "customerInfo": "客户信息", + "items": "商品列表", + "shippingAddress": "shipping 地址", + "trackingNumber": "跟踪号", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-01T00:00:00.000Z" + } +} +``` + +### 4.5 删除订单 + +**路径**: `/v1/order/{id}` + +**方法**: `DELETE` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | string | 是 | 订单 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true +} +``` + +### 4.6 订单状态流转 + +**路径**: `/v1/order/{id}/transition` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | string | 是 | 订单 ID | +| tenantId | string | 是 | 租户 ID | +| status | string | 是 | 目标状态 | +| reason | string | 否 | 流转原因 | + +**返回格式**: +```json +{ + "success": true +} +``` + +### 4.7 批量更新订单 + +**路径**: `/v1/order/batch-update` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| orderIds | array | 是 | 订单 ID 列表 | +| updates | object | 是 | 更新内容 | + +**返回格式**: +```json +{ + "success": true, + "data": { + "successCount": 1, + "failureCount": 0, + "totalCount": 1 + } +} +``` + +### 4.8 批量审核订单 + +**路径**: `/v1/order/batch-audit` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| orderIds | array | 是 | 订单 ID 列表 | + +**返回格式**: +```json +{ + "success": true, + "data": { + "successCount": 1, + "failureCount": 0, + "totalCount": 1 + } +} +``` + +### 4.9 批量发货 + +**路径**: `/v1/order/batch-ship` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| orderIds | array | 是 | 订单 ID 列表 | + +**返回格式**: +```json +{ + "success": true, + "data": { + "successCount": 1, + "failureCount": 0, + "totalCount": 1 + } +} +``` + +### 4.10 标记订单异常 + +**路径**: `/v1/order/{id}/exception` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | string | 是 | 订单 ID | +| tenantId | string | 是 | 租户 ID | +| reason | string | 是 | 异常原因 | + +**返回格式**: +```json +{ + "success": true +} +``` + +### 4.11 自动改派订单 + +**路径**: `/v1/order/{id}/auto-reroute` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | string | 是 | 订单 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true +} +``` + +### 4.12 重试异常订单 + +**路径**: `/v1/order/{id}/retry` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | string | 是 | 订单 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true +} +``` + +### 4.13 取消订单 + +**路径**: `/v1/order/{id}/cancel` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | string | 是 | 订单 ID | +| tenantId | string | 是 | 租户 ID | +| reason | string | 是 | 取消原因 | + +**返回格式**: +```json +{ + "success": true +} +``` + +### 4.14 申请退款 + +**路径**: `/v1/order/{id}/refund` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | string | 是 | 订单 ID | +| tenantId | string | 是 | 租户 ID | +| reason | string | 是 | 退款原因 | +| amount | double | 是 | 退款金额 | + +**返回格式**: +```json +{ + "success": true, + "data": { + "refundId": "REFUND_1234567890" + } +} +``` + +### 4.15 审批退款 + +**路径**: `/v1/order/{id}/refund/approve` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | string | 是 | 订单 ID | +| tenantId | string | 是 | 租户 ID | +| approved | boolean | 是 | 是否批准 | +| note | string | 否 | 审批备注 | + +**返回格式**: +```json +{ + "success": true +} +``` + +### 4.16 申请售后 + +**路径**: `/v1/order/{id}/after-sales` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | string | 是 | 订单 ID | +| tenantId | string | 是 | 租户 ID | +| type | string | 是 | 售后类型 | +| reason | string | 是 | 售后原因 | +| items | array | 是 | 售后商品列表 | + +**返回格式**: +```json +{ + "success": true, + "data": { + "afterSalesId": "AFTER_SALES_1234567890" + } +} +``` + +### 4.17 处理售后 + +**路径**: `/v1/order/{id}/after-sales/process` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | string | 是 | 订单 ID | +| tenantId | string | 是 | 租户 ID | +| action | string | 是 | 处理动作 | +| note | string | 否 | 处理备注 | + +**返回格式**: +```json +{ + "success": true +} +``` + +### 4.18 完成订单 + +**路径**: `/v1/order/{id}/complete` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | string | 是 | 订单 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true +} +``` + +### 4.19 获取订单统计 + +**路径**: `/v1/order/stats` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "totalOrders": 100, + "pendingOrders": 20, + "shippedOrders": 50, + "completedOrders": 30 + } +} +``` + +## 5. 支付 API + +### 5.1 创建支付 + +**路径**: `/v1/payment` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| orderId | long | 否 | 订单 ID | +| paymentMethod | string | 否 | 支付方式 | +| amount | double | 否 | 金额 | +| currency | string | 否 | 货币 | +| status | string | 否 | 状态 | +| transactionId | string | 否 | 交易 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +### 5.2 获取支付列表 + +**路径**: `/v1/payment` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| orderId | long | 否 | 订单 ID | +| status | string | 否 | 状态 | + +**返回格式**: +```json +{ + "success": true, + "data": [ + { + "id": 1, + "orderId": 1, + "paymentMethod": "credit_card", + "amount": 100.00, + "currency": "USD", + "status": "COMPLETED", + "transactionId": "TXN_1234567890", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-01T00:00:00.000Z" + } + ] +} +``` + +### 5.3 获取支付详情 + +**路径**: `/v1/payment/{id}` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 支付 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "orderId": 1, + "paymentMethod": "credit_card", + "amount": 100.00, + "currency": "USD", + "status": "COMPLETED", + "transactionId": "TXN_1234567890", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-01T00:00:00.000Z" + } +} +``` + +### 5.4 更新支付 + +**路径**: `/v1/payment/{id}` + +**方法**: `PUT` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 支付 ID | +| paymentMethod | string | 否 | 支付方式 | +| amount | double | 否 | 金额 | +| currency | string | 否 | 货币 | +| status | string | 否 | 状态 | +| transactionId | string | 否 | 交易 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "orderId": 1, + "paymentMethod": "credit_card", + "amount": 100.00, + "currency": "USD", + "status": "COMPLETED", + "transactionId": "TXN_1234567890", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-01T00:00:00.000Z" + } +} +``` + +### 5.5 删除支付 + +**路径**: `/v1/payment/{id}` + +**方法**: `DELETE` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 支付 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true +} +``` + +## 6. 物流 API + +### 6.1 创建物流 + +**路径**: `/v1/logistics` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| orderId | long | 否 | 订单 ID | +| shippingMethod | string | 否 | 物流方式 | +| trackingNumber | string | 否 | 跟踪号 | +| carrier | string | 否 | 物流公司 | +| status | string | 否 | 状态 | +| estimatedDeliveryDate | string | 否 | 预计送达日期 | +| actualDeliveryDate | string | 否 | 实际送达日期 | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +### 6.2 获取物流列表 + +**路径**: `/v1/logistics` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| orderId | long | 否 | 订单 ID | +| status | string | 否 | 状态 | + +**返回格式**: +```json +{ + "success": true, + "data": [ + { + "id": 1, + "orderId": 1, + "shippingMethod": "standard", + "trackingNumber": "TRK_1234567890", + "carrier": "UPS", + "status": "DELIVERED", + "estimatedDeliveryDate": "2024-01-10T00:00:00.000Z", + "actualDeliveryDate": "2024-01-09T00:00:00.000Z", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-09T00:00:00.000Z" + } + ] +} +``` + +### 6.3 获取物流详情 + +**路径**: `/v1/logistics/{id}` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 物流 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "orderId": 1, + "shippingMethod": "standard", + "trackingNumber": "TRK_1234567890", + "carrier": "UPS", + "status": "DELIVERED", + "estimatedDeliveryDate": "2024-01-10T00:00:00.000Z", + "actualDeliveryDate": "2024-01-09T00:00:00.000Z", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-09T00:00:00.000Z" + } +} +``` + +### 6.4 更新物流 + +**路径**: `/v1/logistics/{id}` + +**方法**: `PUT` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 物流 ID | +| shippingMethod | string | 否 | 物流方式 | +| trackingNumber | string | 否 | 跟踪号 | +| carrier | string | 否 | 物流公司 | +| status | string | 否 | 状态 | +| estimatedDeliveryDate | string | 否 | 预计送达日期 | +| actualDeliveryDate | string | 否 | 实际送达日期 | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "orderId": 1, + "shippingMethod": "standard", + "trackingNumber": "TRK_1234567890", + "carrier": "UPS", + "status": "DELIVERED", + "estimatedDeliveryDate": "2024-01-10T00:00:00.000Z", + "actualDeliveryDate": "2024-01-09T00:00:00.000Z", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-09T00:00:00.000Z" + } +} +``` + +### 6.5 删除物流 + +**路径**: `/v1/logistics/{id}` + +**方法**: `DELETE` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 物流 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true +} +``` + +## 7. 监控 API + +### 7.1 获取系统健康状态 + +**路径**: `/v1/monitoring/health` + +**方法**: `GET` + +**返回格式**: +```json +{ + "success": true, + "data": { + "status": "UP", + "components": { + "database": { + "status": "UP" + }, + "cache": { + "status": "UP" + } + } + } +} +``` + +### 7.2 获取性能指标 + +**路径**: `/v1/monitoring/metrics` + +**方法**: `GET` + +**返回格式**: +```json +{ + "success": true, + "data": { + "jvm": { + "memory": { + "used": 1024, + "total": 2048 + } + }, + "system": { + "cpu": { + "usage": 0.5 + } + } + } +} +``` + +### 7.3 获取服务状态 + +**路径**: `/v1/monitoring/services` + +**方法**: `GET` + +**返回格式**: +```json +{ + "success": true, + "data": { + "authService": { + "status": "UP" + }, + "productService": { + "status": "UP" + }, + "orderService": { + "status": "UP" + } + } +} +``` + +### 7.4 获取数据库状态 + +**路径**: `/v1/monitoring/database` + +**方法**: `GET` + +**返回格式**: +```json +{ + "success": true, + "data": { + "status": "UP", + "connections": { + "active": 5, + "max": 10 + } + } +} +``` + +### 7.5 获取缓存状态 + +**路径**: `/v1/monitoring/cache` + +**方法**: `GET` + +**返回格式**: +```json +{ + "success": true, + "data": { + "status": "UP", + "keys": 100 + } +} +``` + +### 7.6 获取系统统计信息 + +**路径**: `/v1/monitoring/stats` + +**方法**: `GET` + +**返回格式**: +```json +{ + "success": true, + "data": { + "requests": { + "total": 1000, + "success": 990, + "error": 10 + } + } +} +``` + +### 7.7 Ping 测试 + +**路径**: `/v1/monitoring/ping` + +**方法**: `GET` + +**返回格式**: +```json +{ + "success": true, + "message": "Pong", + "timestamp": 1234567890 +} +``` + +## 8. 告警 API + +### 8.1 创建告警 + +**路径**: `/v1/alerts` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| alertType | string | 否 | 告警类型 | +| severity | string | 否 | 严重程度 | +| message | string | 否 | 告警消息 | +| status | string | 否 | 状态 | +| source | string | 否 | 告警来源 | +| threshold | string | 否 | 阈值 | +| actualValue | string | 否 | 实际值 | + +**返回格式**: +```json +{ + "success": true, + "alertId": 1 +} +``` + +### 8.2 获取告警列表 + +**路径**: `/v1/alerts` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| status | string | 否 | 状态 | +| severity | string | 否 | 严重程度 | +| alertType | string | 否 | 告警类型 | +| startDate | string | 否 | 开始日期 | +| endDate | string | 否 | 结束日期 | + +**返回格式**: +```json +{ + "success": true, + "data": [ + { + "id": 1, + "tenantId": "租户 ID", + "alertType": "system", + "severity": "high", + "message": "系统异常", + "status": "ACTIVE", + "source": "monitoring", + "threshold": "90%", + "actualValue": "95%", + "createdAt": "2024-01-01T00:00:00.000Z", + "resolvedAt": null + } + ] +} +``` + +### 8.3 获取告警详情 + +**路径**: `/v1/alerts/{id}` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 告警 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "tenantId": "租户 ID", + "alertType": "system", + "severity": "high", + "message": "系统异常", + "status": "ACTIVE", + "source": "monitoring", + "threshold": "90%", + "actualValue": "95%", + "createdAt": "2024-01-01T00:00:00.000Z", + "resolvedAt": null + } +} +``` + +### 8.4 解决告警 + +**路径**: `/v1/alerts/{id}/resolve` + +**方法**: `PUT` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 告警 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "message": "Alert resolved successfully" +} +``` + +### 8.5 更新告警状态 + +**路径**: `/v1/alerts/{id}/status` + +**方法**: `PUT` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 告警 ID | +| tenantId | string | 是 | 租户 ID | +| status | string | 是 | 新状态 | + +**返回格式**: +```json +{ + "success": true, + "message": "Alert status updated successfully" +} +``` + +### 8.6 获取告警统计 + +**路径**: `/v1/alerts/stats` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| startDate | string | 是 | 开始日期 | +| endDate | string | 是 | 结束日期 | + +**返回格式**: +```json +{ + "success": true, + "data": { + "totalAlerts": 10, + "severityStats": { + "high": 2, + "medium": 5, + "low": 3 + }, + "statusStats": { + "ACTIVE": 3, + "RESOLVED": 7 + }, + "typeStats": { + "system": 5, + "application": 3, + "database": 2 + }, + "startDate": "2024-01-01", + "endDate": "2024-01-31" + } +} +``` + +### 8.7 检查阈值 + +**路径**: `/v1/alerts/check-thresholds` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "message": "Thresholds checked successfully" +} +``` + +## 9. 审计 API + +### 9.1 获取审计日志列表 + +**路径**: `/v1/audit` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| shopId | string | 否 | 店铺 ID | +| userId | long | 否 | 用户 ID | +| action | string | 否 | 操作类型 | +| resourceType | string | 否 | 资源类型 | +| startDate | string | 否 | 开始日期 | +| endDate | string | 否 | 结束日期 | + +**返回格式**: +```json +{ + "success": true, + "data": [ + { + "id": 1, + "tenantId": "租户 ID", + "shopId": "店铺 ID", + "userId": 1, + "action": "create", + "resourceType": "product", + "resourceId": "1", + "ipAddress": "192.168.1.1", + "userAgent": "Mozilla/5.0", + "details": "创建商品", + "createdAt": "2024-01-01T00:00:00.000Z" + } + ] +} +``` + +### 9.2 获取审计日志详情 + +**路径**: `/v1/audit/{id}` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 审计日志 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "tenantId": "租户 ID", + "shopId": "店铺 ID", + "userId": 1, + "action": "create", + "resourceType": "product", + "resourceId": "1", + "ipAddress": "192.168.1.1", + "userAgent": "Mozilla/5.0", + "details": "创建商品", + "createdAt": "2024-01-01T00:00:00.000Z" + } +} +``` + +## 10. 配置 API + +### 10.1 创建配置 + +**路径**: `/v1/config` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 否 | 租户 ID | +| shopId | string | 否 | 店铺 ID | +| configKey | string | 是 | 配置键 | +| configValue | string | 是 | 配置值 | +| configType | string | 否 | 配置类型 | +| description | string | 否 | 配置描述 | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1 + } +} +``` + +### 10.2 获取配置列表 + +**路径**: `/v1/config` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 否 | 租户 ID | +| shopId | string | 否 | 店铺 ID | +| configType | string | 否 | 配置类型 | + +**返回格式**: +```json +{ + "success": true, + "data": [ + { + "id": 1, + "tenantId": "租户 ID", + "shopId": "店铺 ID", + "configKey": "api_key", + "configValue": "value", + "configType": "string", + "description": "API 密钥", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-01T00:00:00.000Z" + } + ] +} +``` + +### 10.3 获取配置详情 + +**路径**: `/v1/config/{id}` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 配置 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "tenantId": "租户 ID", + "shopId": "店铺 ID", + "configKey": "api_key", + "configValue": "value", + "configType": "string", + "description": "API 密钥", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-01T00:00:00.000Z" + } +} +``` + +### 10.4 更新配置 + +**路径**: `/v1/config/{id}` + +**方法**: `PUT` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 配置 ID | +| configValue | string | 否 | 配置值 | +| configType | string | 否 | 配置类型 | +| description | string | 否 | 配置描述 | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "tenantId": "租户 ID", + "shopId": "店铺 ID", + "configKey": "api_key", + "configValue": "new_value", + "configType": "string", + "description": "API 密钥", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-02T00:00:00.000Z" + } +} +``` + +### 10.5 删除配置 + +**路径**: `/v1/config/{id}` + +**方法**: `DELETE` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | long | 是 | 配置 ID | + +**返回格式**: +```json +{ + "success": true +} +``` + +### 10.6 根据键获取配置 + +**路径**: `/v1/config/key/{key}` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| key | string | 是 | 配置键 | +| tenantId | string | 否 | 租户 ID | +| shopId | string | 否 | 店铺 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": 1, + "tenantId": "租户 ID", + "shopId": "店铺 ID", + "configKey": "api_key", + "configValue": "value", + "configType": "string", + "description": "API 密钥", + "createdAt": "2024-01-01T00:00:00.000Z", + "updatedAt": "2024-01-01T00:00:00.000Z" + } +} +``` + +## 11. 数据 API + +### 11.1 导入数据 + +**路径**: `/v1/data/import` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| dataType | string | 是 | 数据类型 | +| data | array | 是 | 数据列表 | + +**返回格式**: +```json +{ + "success": true, + "data": { + "importedCount": 10, + "failedCount": 0 + } +} +``` + +### 11.2 导出数据 + +**路径**: `/v1/data/export` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| dataType | string | 是 | 数据类型 | +| filters | object | 否 | 过滤条件 | + +**返回格式**: +```json +{ + "success": true, + "data": [ + { + "id": 1, + "name": "数据 1" + } + ] +} +``` + +### 11.3 同步数据 + +**路径**: `/v1/data/sync` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| source | string | 是 | 数据源 | +| target | string | 是 | 目标 | +| filters | object | 否 | 过滤条件 | + +**返回格式**: +```json +{ + "success": true, + "data": { + "syncedCount": 10, + "failedCount": 0 + } +} +``` + +## 12. 报表 API + +### 12.1 生成报表 + +**路径**: `/v1/report/generate` + +**方法**: `POST` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| reportType | string | 是 | 报表类型 | +| startDate | string | 是 | 开始日期 | +| endDate | string | 是 | 结束日期 | +| filters | object | 否 | 过滤条件 | + +**返回格式**: +```json +{ + "success": true, + "data": { + "reportId": "REPORT_1234567890", + "url": "http://example.com/report/REPORT_1234567890" + } +} +``` + +### 12.2 获取报表列表 + +**路径**: `/v1/report` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| tenantId | string | 是 | 租户 ID | +| reportType | string | 否 | 报表类型 | +| startDate | string | 否 | 开始日期 | +| endDate | string | 否 | 结束日期 | + +**返回格式**: +```json +{ + "success": true, + "data": [ + { + "id": "REPORT_1234567890", + "reportType": "sales", + "startDate": "2024-01-01", + "endDate": "2024-01-31", + "createdAt": "2024-02-01T00:00:00.000Z", + "url": "http://example.com/report/REPORT_1234567890" + } + ] +} +``` + +### 12.3 获取报表详情 + +**路径**: `/v1/report/{id}` + +**方法**: `GET` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | string | 是 | 报表 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true, + "data": { + "id": "REPORT_1234567890", + "reportType": "sales", + "startDate": "2024-01-01", + "endDate": "2024-01-31", + "createdAt": "2024-02-01T00:00:00.000Z", + "url": "http://example.com/report/REPORT_1234567890" + } +} +``` + +### 12.4 删除报表 + +**路径**: `/v1/report/{id}` + +**方法**: `DELETE` + +**参数**: +| 参数名 | 类型 | 必填 | 描述 | +|-------|------|------|------| +| id | string | 是 | 报表 ID | +| tenantId | string | 是 | 租户 ID | + +**返回格式**: +```json +{ + "success": true +} +``` diff --git a/docs/03_Deployment_Guide.md b/docs/03_Deployment_Guide.md new file mode 100644 index 0000000..957f457 --- /dev/null +++ b/docs/03_Deployment_Guide.md @@ -0,0 +1,363 @@ +# 部署指南 + +## 1. 环境要求 + +### 1.1 硬件要求 +- **CPU**: 至少 2 核 +- **内存**: 至少 4GB +- **磁盘**: 至少 50GB 可用空间 + +### 1.2 软件要求 +- **Java**: JDK 17 或更高版本 +- **MySQL**: 8.0 或更高版本 +- **Redis**: 6.0 或更高版本 +- **Maven**: 3.6 或更高版本 + +## 2. 安装步骤 + +### 2.1 安装 Java + +**Windows 系统**: +1. 下载 JDK 17 安装包:[Oracle JDK 17](https://www.oracle.com/java/technologies/downloads/#java17) +2. 运行安装包,按照提示完成安装 +3. 配置环境变量: + - 右键点击「此电脑」→「属性」→「高级系统设置」→「环境变量」 + - 在「系统变量」中添加 `JAVA_HOME`,值为 JDK 安装目录 + - 在「系统变量」的 `Path` 中添加 `%JAVA_HOME%\bin` +4. 验证安装:打开命令提示符,运行 `java -version`,显示 JDK 版本信息 + +**Linux 系统**: +1. 下载 JDK 17 安装包: + ```bash + wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.rpm + ``` +2. 安装 JDK: + ```bash + sudo rpm -ivh jdk-17_linux-x64_bin.rpm + ``` +3. 验证安装: + ```bash + java -version + ``` + +### 2.2 安装 MySQL + +**Windows 系统**: +1. 下载 MySQL 8.0 安装包:[MySQL Community Server](https://dev.mysql.com/downloads/mysql/) +2. 运行安装包,按照提示完成安装 +3. 配置 MySQL: + - 启动 MySQL 服务 + - 登录 MySQL,修改 root 密码 + - 创建数据库:`CREATE DATABASE crawlful_hub CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +**Linux 系统**: +1. 安装 MySQL: + ```bash + sudo yum install mysql-server + ``` +2. 启动 MySQL 服务: + ```bash + sudo systemctl start mysqld + ``` +3. 配置 MySQL: + ```bash + sudo mysql_secure_installation + ``` +4. 创建数据库: + ```bash + mysql -u root -p + CREATE DATABASE crawlful_hub CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + ``` + +### 2.3 安装 Redis + +**Windows 系统**: +1. 下载 Redis 安装包:[Redis for Windows](https://github.com/tporadowski/redis/releases) +2. 运行安装包,按照提示完成安装 +3. 启动 Redis 服务: + ```bash + redis-server + ``` + +**Linux 系统**: +1. 安装 Redis: + ```bash + sudo yum install redis + ``` +2. 启动 Redis 服务: + ```bash + sudo systemctl start redis + ``` + +### 2.4 安装 Maven + +**Windows 系统**: +1. 下载 Maven 安装包:[Apache Maven](https://maven.apache.org/download.cgi) +2. 解压安装包到指定目录 +3. 配置环境变量: + - 右键点击「此电脑」→「属性」→「高级系统设置」→「环境变量」 + - 在「系统变量」中添加 `MAVEN_HOME`,值为 Maven 安装目录 + - 在「系统变量」的 `Path` 中添加 `%MAVEN_HOME%\bin` +4. 验证安装:打开命令提示符,运行 `mvn -version`,显示 Maven 版本信息 + +**Linux 系统**: +1. 安装 Maven: + ```bash + sudo yum install maven + ``` +2. 验证安装: + ```bash + mvn -version + ``` + +## 3. 项目配置 + +### 3.1 数据库配置 + +编辑 `src/main/resources/application.yml` 文件,修改数据库连接信息: + +```yaml +spring: + datasource: + url: jdbc:mysql://localhost:3306/crawlful_hub?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC + username: root + password: 123456 + driver-class-name: com.mysql.cj.jdbc.Driver +``` + +### 3.2 Redis 配置 + +编辑 `src/main/resources/application.yml` 文件,修改 Redis 连接信息: + +```yaml +spring: + redis: + host: localhost + port: 6379 + password: + database: 0 +``` + +### 3.3 JWT 配置 + +编辑 `src/main/resources/application.yml` 文件,修改 JWT 配置: + +```yaml +spring: + security: + jwt: + secret: your-secret-key + expiration: 86400000 +``` + +### 3.4 服务器配置 + +编辑 `src/main/resources/application.yml` 文件,修改服务器配置: + +```yaml +server: + port: 3001 + servlet: + context-path: /api +``` + +## 4. 构建与部署 + +### 4.1 构建项目 + +1. 进入项目目录: + ```bash + cd d:\trae_projects\makemd\makemd\serverjava + ``` + +2. 执行 Maven 构建: + ```bash + mvn clean package + ``` + +3. 构建成功后,在 `target` 目录中生成 `serverjava-0.0.1-SNAPSHOT.jar` 文件。 + +### 4.2 运行项目 + +**方法一:直接运行** + +1. 进入 `target` 目录: + ```bash + cd target + ``` + +2. 运行 jar 文件: + ```bash + java -jar serverjava-0.0.1-SNAPSHOT.jar + ``` + +**方法二:作为服务运行** + +**Windows 系统**: +1. 创建服务脚本: + ```batch + @echo off + set JAVA_HOME=C:\Program Files\Java\jdk-17 + set PATH=%JAVA_HOME%\bin;%PATH% + java -jar d:\trae_projects\makemd\makemd\serverjava\target\serverjava-0.0.1-SNAPSHOT.jar + ``` + +2. 将脚本保存为 `serverjava.bat`,然后运行。 + +**Linux 系统**: +1. 创建服务文件: + ```bash + sudo nano /etc/systemd/system/serverjava.service + ``` + +2. 添加以下内容: + ``` + [Unit] + Description=ServerJava Service + After=network.target + + [Service] + Type=simple + User=root + ExecStart=/usr/bin/java -jar /path/to/serverjava/target/serverjava-0.0.1-SNAPSHOT.jar + Restart=on-failure + + [Install] + WantedBy=multi-user.target + ``` + +3. 启动服务: + ```bash + sudo systemctl start serverjava + sudo systemctl enable serverjava + ``` + +## 5. 验证部署 + +1. 打开浏览器,访问 `http://localhost:3001/api/api-docs`,查看 API 文档。 + +2. 访问 `http://localhost:3001/api/v1/monitoring/health`,查看系统健康状态。 + +3. 访问 `http://localhost:3001/api/v1/monitoring/ping`,测试系统响应。 + +## 6. 日志管理 + +### 6.1 日志配置 + +编辑 `src/main/resources/logback.xml` 文件,修改日志配置: + +```xml + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + logs/server.log + + logs/server.%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + +``` + +### 6.2 日志查看 + +**Windows 系统**: +1. 进入项目目录,查看日志文件: + ```bash + cd d:\trae_projects\makemd\makemd\serverjava + type logs\server.log + ``` + +**Linux 系统**: +1. 进入项目目录,查看日志文件: + ```bash + cd /path/to/serverjava + tail -f logs/server.log + ``` + +## 7. 故障排查 + +### 7.1 常见问题 + +1. **数据库连接失败** + - 检查数据库服务是否运行 + - 检查数据库连接配置是否正确 + - 检查数据库用户权限是否正确 + +2. **Redis 连接失败** + - 检查 Redis 服务是否运行 + - 检查 Redis 连接配置是否正确 + +3. **端口被占用** + - 检查端口是否被其他服务占用 + - 修改 `application.yml` 中的端口配置 + +4. **JWT 验证失败** + - 检查 JWT 密钥是否正确 + - 检查 JWT 令牌是否过期 + +### 7.2 调试方法 + +1. **查看日志**:查看 `logs/server.log` 文件,了解系统运行状态和错误信息。 + +2. **使用 API 文档**:访问 `http://localhost:3001/api/api-docs`,测试 API 接口。 + +3. **使用健康检查**:访问 `http://localhost:3001/api/v1/monitoring/health`,检查系统健康状态。 + +4. **使用 Ping 测试**:访问 `http://localhost:3001/api/v1/monitoring/ping`,测试系统响应。 + +## 8. 升级与维护 + +### 8.1 升级步骤 + +1. 停止服务: + ```bash + sudo systemctl stop serverjava + ``` + +2. 备份数据: + ```bash + mysqldump -u root -p crawlful_hub > crawlful_hub_backup.sql + ``` + +3. 拉取最新代码: + ```bash + git pull + ``` + +4. 重新构建项目: + ```bash + mvn clean package + ``` + +5. 启动服务: + ```bash + sudo systemctl start serverjava + ``` + +### 8.2 维护计划 + +1. **定期备份**:定期备份数据库和配置文件。 + +2. **更新依赖**:定期更新项目依赖,确保系统安全。 + +3. **监控系统**:使用监控工具监控系统运行状态,及时发现和处理异常。 + +4. **性能优化**:根据系统运行情况,优化数据库查询、缓存策略等。 + +## 9. 总结 + +本部署指南详细介绍了 Crawlful Hub 项目的部署方法、环境要求和配置步骤。通过本指南,您可以快速部署和维护系统,确保系统的稳定运行。 diff --git a/docs/04_Test_Guide.md b/docs/04_Test_Guide.md new file mode 100644 index 0000000..84b8390 --- /dev/null +++ b/docs/04_Test_Guide.md @@ -0,0 +1,182 @@ +# 测试指南 + +## 1. 测试概述 + +Crawlful Hub 项目采用了全面的测试策略,包括单元测试、集成测试和系统测试,确保系统的稳定性和可靠性。本指南详细介绍了项目的测试方法和测试策略。 + +## 2. 测试环境 + +### 2.1 硬件要求 +- **CPU**: 至少 2 核 +- **内存**: 至少 4GB +- **磁盘**: 至少 50GB 可用空间 + +### 2.2 软件要求 +- **Java**: JDK 17 或更高版本 +- **MySQL**: 8.0 或更高版本 +- **Redis**: 6.0 或更高版本 +- **Maven**: 3.6 或更高版本 +- **JUnit 5**: 项目集成的测试框架 + +## 3. 测试类型 + +### 3.1 单元测试 + +单元测试是对系统中最小的可测试单元进行测试,通常是对单个方法或类的测试。单元测试的目的是验证每个单元是否按照预期工作。 + +**核心测试类**: +- `AuthServiceTest.java`:测试认证服务的功能 +- `OrderServiceTest.java`:测试订单服务的功能 +- `ProductServiceTest.java`:测试商品服务的功能 + +### 3.2 集成测试 + +集成测试是对系统中多个组件的交互进行测试,验证组件之间的协作是否正确。集成测试的目的是确保系统的各个组件能够正确地协同工作。 + +**核心测试类**: +- `SystemIntegrationTest.java`:测试整个系统的集成功能 + +### 3.3 系统测试 + +系统测试是对整个系统的功能进行测试,验证系统是否满足需求规格。系统测试的目的是确保系统能够按照预期工作,满足用户的需求。 + +**测试方法**: +- 使用 API 文档测试 API 接口 +- 使用健康检查端点测试系统状态 +- 使用监控端点测试系统性能 + +## 4. 测试工具 + +### 4.1 JUnit 5 + +JUnit 5 是 Java 中最流行的测试框架,用于编写和运行单元测试。Crawlful Hub 项目使用 JUnit 5 进行单元测试和集成测试。 + +### 4.2 Mockito + +Mockito 是一个用于 Java 的 mocking 框架,用于创建和配置模拟对象。Crawlful Hub 项目使用 Mockito 模拟依赖对象,便于单元测试。 + +### 4.3 Spring Test + +Spring Test 是 Spring 框架提供的测试工具,用于测试 Spring 应用。Crawlful Hub 项目使用 Spring Test 测试 Spring 组件。 + +### 4.4 Postman + +Postman 是一个用于测试 API 的工具,用于发送 HTTP 请求并查看响应。Crawlful Hub 项目使用 Postman 测试 API 接口。 + +## 5. 测试执行 + +### 5.1 运行单元测试 + +1. 进入项目目录: + ```bash + cd d:\trae_projects\makemd\makemd\serverjava + ``` + +2. 执行单元测试: + ```bash + mvn test + ``` + +### 5.2 运行集成测试 + +1. 进入项目目录: + ```bash + cd d:\trae_projects\makemd\makemd\serverjava + ``` + +2. 执行集成测试: + ```bash + mvn verify -Pintegration-test + ``` + +### 5.3 运行系统测试 + +1. 启动系统: + ```bash + java -jar target/serverjava-0.0.1-SNAPSHOT.jar + ``` + +2. 使用 Postman 测试 API 接口: + - 访问 `http://localhost:3001/api/api-docs`,获取 API 文档 + - 使用 Postman 发送请求,测试 API 接口 + +3. 使用健康检查端点测试系统状态: + - 访问 `http://localhost:3001/api/v1/monitoring/health` + +4. 使用监控端点测试系统性能: + - 访问 `http://localhost:3001/api/v1/monitoring/metrics` + +## 6. 测试策略 + +### 6.1 测试覆盖率 + +Crawlful Hub 项目的测试覆盖率目标是: +- **单元测试覆盖率**:至少 80% +- **集成测试覆盖率**:至少 60% +- **系统测试覆盖率**:至少 40% + +### 6.2 测试用例设计 + +测试用例设计遵循以下原则: +- **边界值测试**:测试边界条件,如空值、最大值、最小值等 +- **等价类测试**:测试等价类,如有效输入、无效输入等 +- **异常测试**:测试异常情况,如参数错误、系统错误等 +- **场景测试**:测试实际场景,如用户注册、登录、下单等 + +### 6.3 测试数据 + +测试数据的设计遵循以下原则: +- **真实数据**:使用真实的业务数据进行测试 +- **边界数据**:使用边界值进行测试 +- **异常数据**:使用异常数据进行测试 +- **覆盖所有场景**:确保测试数据覆盖所有业务场景 + +## 7. 测试报告 + +### 7.1 单元测试报告 + +运行单元测试后,Maven 会生成单元测试报告,位于 `target/surefire-reports` 目录。 + +### 7.2 集成测试报告 + +运行集成测试后,Maven 会生成集成测试报告,位于 `target/failsafe-reports` 目录。 + +### 7.3 代码覆盖率报告 + +使用 JaCoCo 插件生成代码覆盖率报告,位于 `target/site/jacoco` 目录。 + +## 8. 测试最佳实践 + +### 8.1 单元测试最佳实践 + +- **测试单个方法**:每个单元测试只测试一个方法 +- **使用断言**:使用断言验证测试结果 +- **模拟依赖**:使用 Mockito 模拟依赖对象 +- **测试边界条件**:测试边界值和异常情况 +- **保持测试简单**:测试代码应该简洁明了 + +### 8.2 集成测试最佳实践 + +- **测试组件交互**:测试组件之间的协作 +- **使用真实依赖**:使用真实的依赖对象 +- **测试业务流程**:测试完整的业务流程 +- **保持测试独立**:每个集成测试应该独立运行 + +### 8.3 系统测试最佳实践 + +- **测试完整功能**:测试系统的完整功能 +- **使用真实环境**:使用真实的环境进行测试 +- **测试用户场景**:测试实际的用户场景 +- **性能测试**:测试系统的性能 + +## 9. 测试自动化 + +Crawlful Hub 项目使用 Maven 进行测试自动化,配置了以下测试目标: + +- **mvn test**:运行单元测试 +- **mvn verify**:运行单元测试和集成测试 +- **mvn clean package**:构建项目并运行测试 + +## 10. 总结 + +本测试指南详细介绍了 Crawlful Hub 项目的测试方法和测试策略。通过本指南,您可以了解如何编写和运行测试,确保系统的稳定性和可靠性。 diff --git a/docs/05_Database_Design.md b/docs/05_Database_Design.md new file mode 100644 index 0000000..25687ea --- /dev/null +++ b/docs/05_Database_Design.md @@ -0,0 +1,306 @@ +# 数据库设计 + +## 1. 数据库概述 + +Crawlful Hub 项目使用 MySQL 8.0 作为数据库,采用了关系型数据库设计,确保数据的一致性和完整性。本文档详细介绍了项目的数据库表结构、索引和关系。 + +## 2. 数据库表结构 + +### 2.1 用户表(cf_user) + +| 字段名 | 数据类型 | 约束 | 描述 | +|-------|---------|------|------| +| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 用户 ID | +| tenant_id | VARCHAR(255) | NOT NULL | 租户 ID | +| username | VARCHAR(255) | NOT NULL, UNIQUE | 用户名 | +| password | VARCHAR(255) | NOT NULL | 密码(加密存储) | +| email | VARCHAR(255) | NOT NULL, UNIQUE | 邮箱 | +| role | VARCHAR(50) | | 角色 | +| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | DATETIME | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 | + +### 2.2 商品表(cf_product) + +| 字段名 | 数据类型 | 约束 | 描述 | +|-------|---------|------|------| +| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 商品 ID | +| tenant_id | VARCHAR(255) | NOT NULL | 租户 ID | +| shop_id | VARCHAR(255) | | 店铺 ID | +| title | VARCHAR(255) | NOT NULL | 商品标题 | +| description | TEXT | | 商品描述 | +| main_image | VARCHAR(255) | | 商品主图 | +| platform | VARCHAR(50) | | 平台 | +| platform_product_id | VARCHAR(255) | | 平台商品 ID | +| price | DECIMAL(10,2) | | 价格 | +| cost_price | DECIMAL(10,2) | | 成本价格 | +| quantity | INT | | 数量 | +| status | VARCHAR(50) | | 状态 | +| phash | VARCHAR(255) | | 图片哈希 | +| semantic_hash | VARCHAR(255) | | 语义哈希 | +| vector_embedding | TEXT | | 向量嵌入 | +| attributes | JSON | | 属性 | +| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | DATETIME | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 | + +### 2.3 订单表(cf_order) + +| 字段名 | 数据类型 | 约束 | 描述 | +|-------|---------|------|------| +| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 订单 ID | +| tenant_id | VARCHAR(255) | NOT NULL | 租户 ID | +| shop_id | VARCHAR(255) | | 店铺 ID | +| platform | VARCHAR(50) | | 平台 | +| platform_order_id | VARCHAR(255) | | 平台订单 ID | +| status | VARCHAR(50) | | 状态 | +| total_amount | DECIMAL(10,2) | | 总金额 | +| currency | VARCHAR(10) | | 货币 | +| customer_info | JSON | | 客户信息 | +| items | JSON | | 商品列表 | +| shipping_address | JSON | | shipping 地址 | +| tracking_number | VARCHAR(255) | | 跟踪号 | +| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | DATETIME | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 | + +### 2.4 支付表(cf_payment) + +| 字段名 | 数据类型 | 约束 | 描述 | +|-------|---------|------|------| +| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 支付 ID | +| tenant_id | VARCHAR(255) | NOT NULL | 租户 ID | +| order_id | BIGINT | FOREIGN KEY (order_id) REFERENCES cf_order(id) | 订单 ID | +| payment_method | VARCHAR(50) | | 支付方式 | +| amount | DECIMAL(10,2) | | 金额 | +| currency | VARCHAR(10) | | 货币 | +| status | VARCHAR(50) | | 状态 | +| transaction_id | VARCHAR(255) | | 交易 ID | +| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | DATETIME | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 | + +### 2.5 物流表(cf_logistics) + +| 字段名 | 数据类型 | 约束 | 描述 | +|-------|---------|------|------| +| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 物流 ID | +| tenant_id | VARCHAR(255) | NOT NULL | 租户 ID | +| order_id | BIGINT | FOREIGN KEY (order_id) REFERENCES cf_order(id) | 订单 ID | +| shipping_method | VARCHAR(50) | | 物流方式 | +| tracking_number | VARCHAR(255) | | 跟踪号 | +| carrier | VARCHAR(50) | | 物流公司 | +| status | VARCHAR(50) | | 状态 | +| estimated_delivery_date | DATETIME | | 预计送达日期 | +| actual_delivery_date | DATETIME | | 实际送达日期 | +| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | DATETIME | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 | + +### 2.6 告警表(cf_alert) + +| 字段名 | 数据类型 | 约束 | 描述 | +|-------|---------|------|------| +| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 告警 ID | +| tenant_id | VARCHAR(255) | NOT NULL | 租户 ID | +| alert_type | VARCHAR(50) | | 告警类型 | +| severity | VARCHAR(50) | | 严重程度 | +| message | TEXT | | 告警消息 | +| status | VARCHAR(50) | | 状态 | +| source | VARCHAR(255) | | 告警来源 | +| threshold | VARCHAR(255) | | 阈值 | +| actual_value | VARCHAR(255) | | 实际值 | +| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| resolved_at | DATETIME | | 解决时间 | + +### 2.7 审计表(cf_audit) + +| 字段名 | 数据类型 | 约束 | 描述 | +|-------|---------|------|------| +| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 审计日志 ID | +| tenant_id | VARCHAR(255) | | 租户 ID | +| shop_id | VARCHAR(255) | | 店铺 ID | +| user_id | BIGINT | | 用户 ID | +| action | VARCHAR(255) | | 操作类型 | +| resource_type | VARCHAR(255) | | 资源类型 | +| resource_id | VARCHAR(255) | | 资源 ID | +| ip_address | VARCHAR(100) | | IP 地址 | +| user_agent | TEXT | | 用户代理 | +| details | TEXT | | 详细信息 | +| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 | + +### 2.8 配置表(cf_config) + +| 字段名 | 数据类型 | 约束 | 描述 | +|-------|---------|------|------| +| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 配置 ID | +| tenant_id | VARCHAR(255) | | 租户 ID | +| shop_id | VARCHAR(255) | | 店铺 ID | +| config_key | VARCHAR(255) | NOT NULL | 配置键 | +| config_value | VARCHAR(255) | NOT NULL | 配置值 | +| config_type | VARCHAR(50) | | 配置类型 | +| description | TEXT | | 配置描述 | +| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 | +| updated_at | DATETIME | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 | + +## 3. 索引设计 + +### 3.1 用户表索引 + +| 索引名 | 字段 | 类型 | 描述 | +|-------|------|------|------| +| PRIMARY | id | PRIMARY | 主键索引 | +| idx_user_tenant_id | tenant_id | INDEX | 租户 ID 索引 | +| UNIQUE | username | UNIQUE | 用户名唯一索引 | +| UNIQUE | email | UNIQUE | 邮箱唯一索引 | + +### 3.2 商品表索引 + +| 索引名 | 字段 | 类型 | 描述 | +|-------|------|------|------| +| PRIMARY | id | PRIMARY | 主键索引 | +| idx_product_tenant_id | tenant_id | INDEX | 租户 ID 索引 | +| idx_product_platform | platform | INDEX | 平台索引 | + +### 3.3 订单表索引 + +| 索引名 | 字段 | 类型 | 描述 | +|-------|------|------|------| +| PRIMARY | id | PRIMARY | 主键索引 | +| idx_order_tenant_id | tenant_id | INDEX | 租户 ID 索引 | +| idx_order_platform | platform | INDEX | 平台索引 | + +### 3.4 支付表索引 + +| 索引名 | 字段 | 类型 | 描述 | +|-------|------|------|------| +| PRIMARY | id | PRIMARY | 主键索引 | +| idx_payment_tenant_id | tenant_id | INDEX | 租户 ID 索引 | +| idx_payment_order_id | order_id | INDEX | 订单 ID 索引 | + +### 3.5 物流表索引 + +| 索引名 | 字段 | 类型 | 描述 | +|-------|------|------|------| +| PRIMARY | id | PRIMARY | 主键索引 | +| idx_logistics_tenant_id | tenant_id | INDEX | 租户 ID 索引 | +| idx_logistics_order_id | order_id | INDEX | 订单 ID 索引 | + +### 3.6 告警表索引 + +| 索引名 | 字段 | 类型 | 描述 | +|-------|------|------|------| +| PRIMARY | id | PRIMARY | 主键索引 | +| idx_alert_tenant_id | tenant_id | INDEX | 租户 ID 索引 | +| idx_alert_status | status | INDEX | 状态索引 | +| idx_alert_severity | severity | INDEX | 严重程度索引 | +| idx_alert_alert_type | alert_type | INDEX | 告警类型索引 | +| idx_alert_created_at | created_at | INDEX | 创建时间索引 | + +### 3.7 审计表索引 + +| 索引名 | 字段 | 类型 | 描述 | +|-------|------|------|------| +| PRIMARY | id | PRIMARY | 主键索引 | +| idx_audit_tenant_id | tenant_id | INDEX | 租户 ID 索引 | +| idx_audit_shop_id | shop_id | INDEX | 店铺 ID 索引 | +| idx_audit_user_id | user_id | INDEX | 用户 ID 索引 | +| idx_audit_action | action | INDEX | 操作类型索引 | +| idx_audit_resource_type | resource_type | INDEX | 资源类型索引 | +| idx_audit_created_at | created_at | INDEX | 创建时间索引 | + +### 3.8 配置表索引 + +| 索引名 | 字段 | 类型 | 描述 | +|-------|------|------|------| +| PRIMARY | id | PRIMARY | 主键索引 | +| idx_config_tenant_id | tenant_id | INDEX | 租户 ID 索引 | +| idx_config_shop_id | shop_id | INDEX | 店铺 ID 索引 | +| idx_config_key | config_key | INDEX | 配置键索引 | + +## 4. 关系设计 + +### 4.1 表关系 + +- **用户与订单**:一对多关系,一个用户可以创建多个订单 +- **订单与支付**:一对一关系,一个订单对应一个支付 +- **订单与物流**:一对一关系,一个订单对应一个物流 +- **租户与所有表**:一对多关系,一个租户可以有多个用户、商品、订单等 + +### 4.2 外键约束 + +| 表名 | 外键字段 | 引用表 | 引用字段 | 约束 | +|------|---------|--------|---------|------| +| cf_payment | order_id | cf_order | id | ON DELETE CASCADE | +| cf_logistics | order_id | cf_order | id | ON DELETE CASCADE | + +## 5. 数据类型选择 + +### 5.1 字符串类型 +- **VARCHAR**:用于存储可变长度的字符串,如用户名、邮箱等 +- **TEXT**:用于存储较长的文本,如商品描述、详细信息等 +- **JSON**:用于存储 JSON 格式的数据,如客户信息、商品列表等 + +### 5.2 数值类型 +- **BIGINT**:用于存储较大的整数,如 ID 等 +- **INT**:用于存储整数,如数量等 +- **DECIMAL(10,2)**:用于存储金额,确保精度 + +### 5.3 日期类型 +- **DATETIME**:用于存储日期和时间,如创建时间、更新时间等 + +## 6. 数据库迁移 + +Crawlful Hub 项目使用 Flyway 进行数据库迁移,确保数据库结构的版本控制和一致性。 + +### 6.1 迁移脚本 + +| 脚本名 | 描述 | +|-------|------| +| V1__init_schema.sql | 初始化数据库表结构 | +| V2__add_alert_table.sql | 添加告警表 | + +### 6.2 迁移执行 + +1. 进入项目目录: + ```bash + cd d:\trae_projects\makemd\makemd\serverjava + ``` + +2. 执行数据库迁移: + ```bash + mvn flyway:migrate + ``` + +## 7. 性能优化 + +### 7.1 索引优化 + +- **添加适当的索引**:为频繁查询的字段添加索引 +- **避免过度索引**:不要为所有字段添加索引,只为必要的字段添加 +- **使用复合索引**:对于多字段查询,使用复合索引 + +### 7.2 查询优化 + +- **使用分页查询**:避免一次性加载大量数据 +- **使用索引覆盖查询**:减少回表操作 +- **避免全表扫描**:使用索引进行查询 +- **使用预编译语句**:减少 SQL 解析时间 + +### 7.3 连接池优化 + +- **使用 HikariCP**:高性能的数据库连接池 +- **配置合理的连接数**:根据系统负载配置连接数 +- **设置连接超时**:避免连接占用过长时间 + +## 8. 安全考虑 + +### 8.1 数据加密 + +- **密码加密**:使用 BCrypt 加密用户密码 +- **敏感数据加密**:对敏感数据进行加密存储 + +### 8.2 访问控制 + +- **最小权限原则**:只授予必要的数据库权限 +- **使用参数化查询**:防止 SQL 注入攻击 +- **定期审计**:定期审计数据库访问日志 + +## 9. 总结 + +本数据库设计文档详细介绍了 Crawlful Hub 项目的数据库表结构、索引和关系。通过合理的数据库设计,确保了系统的性能和可靠性。 diff --git a/docs/06_Security_Guide.md b/docs/06_Security_Guide.md new file mode 100644 index 0000000..c5170f6 --- /dev/null +++ b/docs/06_Security_Guide.md @@ -0,0 +1,297 @@ +# 安全指南 + +## 1. 安全概述 + +Crawlful Hub 项目采用了全面的安全措施,确保系统的安全性和可靠性。本文档详细介绍了项目的安全措施和最佳实践。 + +## 2. 认证与授权 + +### 2.1 JWT 认证 + +Crawlful Hub 项目使用 JWT(JSON Web Token)进行认证,确保用户身份的安全性。 + +**实现细节**: +- 使用 Spring Security 实现 JWT 认证 +- 配置 JWT 密钥和过期时间 +- 验证 JWT 令牌的有效性 + +**配置示例**: +```yaml +spring: + security: + jwt: + secret: your-secret-key + expiration: 86400000 +``` + +### 2.2 角色授权 + +Crawlful Hub 项目使用基于角色的访问控制(RBAC),确保用户只能访问其权限范围内的资源。 + +**预设角色**: +- `ADMIN` - 全权 +- `MANAGER` - 运营主管 +- `OPERATOR` - 运营专员 +- `FINANCE` - 财务主管 +- `SOURCING` - 采购专家 +- `LOGISTICS` - 物流专家 +- `ANALYST` - 数据分析师 + +**授权实现**: +- 使用 `@PreAuthorize` 注解进行方法级别的授权 +- 实现 `authorize()` 中间件进行路由级别的授权 + +**示例代码**: +```java +@PreAuthorize("hasRole('ADMIN')") +public void deleteUser(Long id) { + // 实现删除用户的逻辑 +} +``` + +## 3. 数据安全 + +### 3.1 密码加密 + +Crawlful Hub 项目使用 BCrypt 对用户密码进行加密,确保密码的安全性。 + +**实现细节**: +- 使用 `BCryptPasswordEncoder` 对密码进行加密 +- 存储加密后的密码,不存储明文密码 + +**示例代码**: +```java +@Autowired +private PasswordEncoder passwordEncoder; + +public User createUser(User user) { + user.setPassword(passwordEncoder.encode(user.getPassword())); + return userRepository.save(user); +} +``` + +### 3.2 敏感数据保护 + +Crawlful Hub 项目对敏感数据进行保护,确保数据的安全性。 + +**保护措施**: +- 对敏感数据进行加密存储 +- 避免在日志中记录敏感信息 +- 使用 HTTPS 协议传输数据 + +**示例代码**: +```java +@Column(columnDefinition = "VARBINARY(255)") +private byte[] sensitiveData; +``` + +## 4. 输入验证 + +### 4.1 参数验证 + +Crawlful Hub 项目对输入参数进行验证,确保输入的合法性。 + +**实现细节**: +- 使用 `@Valid` 注解和 `BindingResult` 进行参数验证 +- 实现 `ValidationUtil` 工具类进行数据验证 + +**示例代码**: +```java +@PostMapping("/create") +public ResponseEntity createUser(@Valid @RequestBody User user, BindingResult bindingResult) { + if (bindingResult.hasErrors()) { + return ResponseEntity.badRequest().build(); + } + return ResponseEntity.ok(userService.createUser(user)); +} +``` + +### 4.2 SQL 注入防护 + +Crawlful Hub 项目使用参数化查询,防止 SQL 注入攻击。 + +**实现细节**: +- 使用 JPA 或 MyBatis 进行数据库操作 +- 避免直接拼接 SQL 语句 + +**示例代码**: +```java +// 正确的做法 +@Query("SELECT u FROM User u WHERE u.username = :username") +User findByUsername(@Param("username") String username); + +// 错误的做法(避免) +String sql = "SELECT * FROM user WHERE username = '" + username + "'"; +``` + +## 5. 跨站请求伪造(CSRF)防护 + +Crawlful Hub 项目实现了 CSRF 防护,防止跨站请求伪造攻击。 + +**实现细节**: +- 使用 Spring Security 的 CSRF 保护 +- 配置 CSRF 令牌的生成和验证 + +**配置示例**: +```java +@Configuration +public class SecurityConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); + } +} +``` + +## 6. 速率限制 + +Crawlful Hub 项目实现了速率限制,防止暴力破解和 DoS 攻击。 + +**实现细节**: +- 实现 `RateLimitFilter` 过滤器 +- 基于客户端 IP 和请求 URI 进行限制 +- 限制客户端每分钟最大请求数为 60 次 + +**示例代码**: +```java +@Component +public class RateLimitFilter implements Filter { + private final Map requestCounts = new ConcurrentHashMap<>(); + private final Map lastResetTimes = new ConcurrentHashMap<>(); + private static final int MAX_REQUESTS_PER_MINUTE = 60; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + String clientIp = httpRequest.getRemoteAddr(); + String requestUri = httpRequest.getRequestURI(); + String key = clientIp + ":" + requestUri; + + long currentTime = System.currentTimeMillis(); + long lastResetTime = lastResetTimes.getOrDefault(key, 0L); + + if (currentTime - lastResetTime > 60000) { + requestCounts.put(key, new AtomicInteger(1)); + lastResetTimes.put(key, currentTime); + } else { + AtomicInteger count = requestCounts.get(key); + if (count != null && count.incrementAndGet() > MAX_REQUESTS_PER_MINUTE) { + ((HttpServletResponse) response).setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); + return; + } + } + + chain.doFilter(request, response); + } +} +``` + +## 7. 安全日志 + +Crawlful Hub 项目实现了安全日志,记录系统的安全事件。 + +**实现细节**: +- 使用 Logback 配置安全日志 +- 记录用户登录、登出、权限变更等安全事件 +- 记录异常登录尝试和权限错误 + +**配置示例**: +```xml + + logs/security.log + + logs/security.%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + +``` + +## 8. 安全最佳实践 + +### 8.1 代码安全 + +- **使用最新的依赖**:定期更新项目依赖,修复安全漏洞 +- **避免硬编码**:避免在代码中硬编码密钥、密码等敏感信息 +- **使用安全的加密算法**:使用强加密算法,如 BCrypt、AES 等 +- **定期代码审查**:定期进行代码审查,发现和修复安全问题 + +### 8.2 服务器安全 + +- **使用 HTTPS**:使用 HTTPS 协议传输数据 +- **限制访问**:限制服务器的访问范围,只允许必要的端口和 IP 访问 +- **定期更新**:定期更新服务器操作系统和软件,修复安全漏洞 +- **使用防火墙**:使用防火墙保护服务器,防止未授权访问 + +### 8.3 数据库安全 + +- **最小权限原则**:只授予数据库用户必要的权限 +- **定期备份**:定期备份数据库,防止数据丢失 +- **使用参数化查询**:防止 SQL 注入攻击 +- **加密敏感数据**:对敏感数据进行加密存储 + +### 8.4 应用安全 + +- **使用 CSRF 保护**:防止跨站请求伪造攻击 +- **实现速率限制**:防止暴力破解和 DoS 攻击 +- **验证输入**:对所有输入进行验证,防止恶意输入 +- **使用安全的会话管理**:使用安全的会话管理机制,防止会话劫持 + +## 9. 安全审计 + +### 9.1 审计日志 + +Crawlful Hub 项目实现了审计日志,记录系统的操作和事件。 + +**实现细节**: +- 使用 `cf_audit` 表存储审计日志 +- 记录用户操作、资源访问、权限变更等事件 +- 记录 IP 地址、用户代理等信息 + +**示例代码**: +```java +@Service +public class AuditService { + @Autowired + private AuditRepository auditRepository; + + public void logAudit(String tenantId, String shopId, Long userId, String action, String resourceType, String resourceId, String ipAddress, String userAgent, String details) { + Audit audit = new Audit(); + audit.setTenantId(tenantId); + audit.setShopId(shopId); + audit.setUserId(userId); + audit.setAction(action); + audit.setResourceType(resourceType); + audit.setResourceId(resourceId); + audit.setIpAddress(ipAddress); + audit.setUserAgent(userAgent); + audit.setDetails(details); + audit.setCreatedAt(new Date()); + auditRepository.save(audit); + } +} +``` + +### 9.2 安全扫描 + +Crawlful Hub 项目定期进行安全扫描,发现和修复安全漏洞。 + +**扫描工具**: +- **OWASP ZAP**:用于 Web 应用安全扫描 +- **SonarQube**:用于代码安全扫描 +- **Nmap**:用于网络安全扫描 + +**扫描频率**: +- 开发环境:每次代码提交后 +- 测试环境:每周一次 +- 生产环境:每月一次 + +## 10. 总结 + +本安全指南详细介绍了 Crawlful Hub 项目的安全措施和最佳实践。通过本指南,您可以了解如何确保系统的安全性和可靠性。 diff --git a/docs/07_Maintenance_Guide.md b/docs/07_Maintenance_Guide.md new file mode 100644 index 0000000..3c76163 --- /dev/null +++ b/docs/07_Maintenance_Guide.md @@ -0,0 +1,292 @@ +# 维护指南 + +## 1. 维护概述 + +Crawlful Hub 项目需要定期维护,确保系统的稳定运行和性能优化。本文档详细介绍了项目的维护方法和最佳实践。 + +## 2. 日常维护 + +### 2.1 监控系统状态 + +**监控指标**: +- **系统健康状态**:访问 `http://localhost:3001/api/v1/monitoring/health` +- **系统性能指标**:访问 `http://localhost:3001/api/v1/monitoring/metrics` +- **系统日志**:查看 `logs/server.log` 文件 + +**监控工具**: +- **Prometheus**:用于监控系统性能指标 +- **Grafana**:用于可视化监控数据 +- **ELK Stack**:用于日志收集和分析 + +### 2.2 数据库维护 + +**定期备份**: +- 每周备份一次数据库 +- 使用 `mysqldump` 命令备份数据库 + +**优化数据库**: +- 定期优化数据库表结构 +- 定期分析数据库查询性能 +- 定期更新数据库统计信息 + +**示例命令**: +```bash +# 备份数据库 +mysqldump -u root -p crawlful_hub > crawlful_hub_backup.sql + +# 优化数据库表 +mysqlcheck -u root -p --optimize crawlful_hub + +# 分析数据库表 +mysqlcheck -u root -p --analyze crawlful_hub +``` + +### 2.3 日志管理 + +**日志轮转**: +- 配置 Logback 实现日志轮转 +- 按日期保存日志文件 +- 保留 30 天的日志文件 + +**日志分析**: +- 定期分析系统日志 +- 发现和解决系统异常 +- 优化系统性能 + +**示例配置**: +```xml + + logs/server.log + + logs/server.%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + +``` + +## 3. 性能优化 + +### 3.1 数据库优化 + +**索引优化**: +- 为频繁查询的字段添加索引 +- 避免过度索引 +- 使用复合索引 + +**查询优化**: +- 使用分页查询 +- 使用索引覆盖查询 +- 避免全表扫描 +- 使用预编译语句 + +**连接池优化**: +- 配置合理的连接数 +- 设置连接超时 +- 监控连接池状态 + +### 3.2 缓存优化 + +**Redis 缓存**: +- 配置合理的缓存过期时间 +- 监控缓存命中率 +- 优化缓存键设计 + +**示例配置**: +```java +@Configuration +@EnableCaching +public class CacheConfig { + @Bean + public RedisCacheManager cacheManager(RedisConnectionFactory factory) { + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(Duration.ofMinutes(10)); + return RedisCacheManager.builder(factory) + .cacheDefaults(config) + .build(); + } +} +``` + +### 3.3 服务器优化 + +**内存优化**: +- 配置合理的 JVM 内存参数 +- 监控内存使用情况 +- 避免内存泄漏 + +**CPU 优化**: +- 优化代码逻辑 +- 避免阻塞操作 +- 使用异步处理 + +**示例 JVM 参数**: +```bash +java -Xms2G -Xmx4G -jar serverjava-0.0.1-SNAPSHOT.jar +``` + +## 4. 故障处理 + +### 4.1 常见故障 + +**数据库连接失败**: +- 检查数据库服务是否运行 +- 检查数据库连接配置是否正确 +- 检查数据库用户权限是否正确 + +**Redis 连接失败**: +- 检查 Redis 服务是否运行 +- 检查 Redis 连接配置是否正确 + +**端口被占用**: +- 检查端口是否被其他服务占用 +- 修改 `application.yml` 中的端口配置 + +**JWT 验证失败**: +- 检查 JWT 密钥是否正确 +- 检查 JWT 令牌是否过期 + +### 4.2 故障排查 + +**查看日志**: +- 查看 `logs/server.log` 文件 +- 查找错误信息和异常堆栈 + +**使用健康检查**: +- 访问 `http://localhost:3001/api/v1/monitoring/health` +- 检查系统健康状态 + +**使用 Ping 测试**: +- 访问 `http://localhost:3001/api/v1/monitoring/ping` +- 测试系统响应 + +**使用 API 文档**: +- 访问 `http://localhost:3001/api/api-docs` +- 测试 API 接口 + +## 5. 升级与更新 + +### 5.1 依赖更新 + +**定期更新依赖**: +- 定期检查和更新项目依赖 +- 修复安全漏洞 +- 提高系统性能 + +**示例命令**: +```bash +# 检查依赖更新 +mvn dependency:check + +# 更新依赖 +mvn versions:update-properties +``` + +### 5.2 代码更新 + +**代码审查**: +- 定期进行代码审查 +- 发现和修复代码问题 +- 优化代码结构 + +**版本控制**: +- 使用 Git 进行版本控制 +- 定期提交代码 +- 管理代码分支 + +### 5.3 系统升级 + +**升级步骤**: +1. 停止服务 +2. 备份数据 +3. 拉取最新代码 +4. 重新构建项目 +5. 启动服务 + +**示例命令**: +```bash +# 停止服务 +sudo systemctl stop serverjava + +# 备份数据 +mysqldump -u root -p crawlful_hub > crawlful_hub_backup.sql + +# 拉取最新代码 +git pull + +# 重新构建项目 +mvn clean package + +# 启动服务 +sudo systemctl start serverjava +``` + +## 6. 监控与告警 + +### 6.1 系统监控 + +**监控指标**: +- **CPU 使用率**:监控系统 CPU 使用率 +- **内存使用率**:监控系统内存使用率 +- **磁盘使用率**:监控系统磁盘使用率 +- **网络流量**:监控系统网络流量 +- **API 响应时间**:监控 API 接口响应时间 +- **数据库性能**:监控数据库查询性能 + +**监控工具**: +- **Prometheus**:用于监控系统性能指标 +- **Grafana**:用于可视化监控数据 +- **ELK Stack**:用于日志收集和分析 + +### 6.2 告警系统 + +**告警类型**: +- **系统告警**:系统故障、性能异常等 +- **业务告警**:订单异常、支付失败等 +- **安全告警**:未授权访问、异常登录等 + +**告警级别**: +- **ERROR**:严重错误,需要立即处理 +- **WARN**:警告,需要关注 +- **INFO**:信息,仅供参考 + +**告警处理**: +- 及时接收和处理告警 +- 记录告警处理过程 +- 分析告警原因,防止再次发生 + +## 7. 最佳实践 + +### 7.1 代码最佳实践 + +- **代码风格**:遵循 Java 代码风格规范 +- **注释**:为代码添加适当的注释 +- **测试**:为代码添加单元测试和集成测试 +- **文档**:为代码添加文档 + +### 7.2 数据库最佳实践 + +- **表设计**:遵循数据库设计规范 +- **索引**:为频繁查询的字段添加索引 +- **查询**:优化数据库查询 +- **备份**:定期备份数据库 + +### 7.3 服务器最佳实践 + +- **配置**:配置合理的服务器参数 +- **监控**:监控服务器状态 +- **安全**:确保服务器安全 +- **备份**:定期备份服务器数据 + +### 7.4 维护最佳实践 + +- **定期维护**:定期进行系统维护 +- **记录**:记录系统维护过程 +- **分析**:分析系统维护结果 +- **改进**:根据维护结果改进系统 + +## 8. 总结 + +本维护指南详细介绍了 Crawlful Hub 项目的维护方法和最佳实践。通过本指南,您可以了解如何确保系统的稳定运行和性能优化。 diff --git a/docs/08_Internationalization_Guide.md b/docs/08_Internationalization_Guide.md new file mode 100644 index 0000000..4bfed4e --- /dev/null +++ b/docs/08_Internationalization_Guide.md @@ -0,0 +1,245 @@ +# 国际化指南 + +## 1. 国际化概述 + +Crawlful Hub 项目支持国际化,确保系统可以在不同语言环境下正常运行。本文档详细介绍了项目的国际化支持和实现方法。 + +## 2. 国际化配置 + +### 2.1 资源文件 + +Crawlful Hub 项目使用 Spring 国际化支持,通过资源文件管理不同语言的文本。 + +**资源文件位置**: +- `src/main/resources/i18n/messages.properties` - 英文资源文件 +- `src/main/resources/i18n/messages_zh.properties` - 中文资源文件 + +**资源文件示例**: + +**messages.properties**: +```properties +# Authentication +auth.login.success=Login successful +auth.login.failure=Invalid username or password +auth.register.success=Registration successful +auth.register.failure=Registration failed + +# Product +product.create.success=Product created successfully +product.create.failure=Failed to create product +product.update.success=Product updated successfully +product.update.failure=Failed to update product +product.delete.success=Product deleted successfully +product.delete.failure=Failed to delete product + +# Order +order.create.success=Order created successfully +order.create.failure=Failed to create order +order.update.success=Order updated successfully +order.update.failure=Failed to update order +order.delete.success=Order deleted successfully +order.delete.failure=Failed to delete order +``` + +**messages_zh.properties**: +```properties +# 认证 +auth.login.success=登录成功 +auth.login.failure=用户名或密码错误 +auth.register.success=注册成功 +auth.register.failure=注册失败 + +# 商品 +product.create.success=商品创建成功 +product.create.failure=商品创建失败 +product.update.success=商品更新成功 +product.update.failure=商品更新失败 +product.delete.success=商品删除成功 +product.delete.failure=商品删除失败 + +# 订单 +order.create.success=订单创建成功 +order.create.failure=订单创建失败 +order.update.success=订单更新成功 +order.update.failure=订单更新失败 +order.delete.success=订单删除成功 +order.delete.failure=订单删除失败 +``` + +### 2.2 国际化配置类 + +Crawlful Hub 项目通过 `InternationalizationConfig` 类配置国际化支持。 + +**配置示例**: +```java +@Configuration +public class InternationalizationConfig implements WebMvcConfigurer { + @Bean + public MessageSource messageSource() { + ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); + messageSource.setBasename("i18n/messages"); + messageSource.setDefaultEncoding("UTF-8"); + return messageSource; + } + + @Bean + public LocaleResolver localeResolver() { + CookieLocaleResolver localeResolver = new CookieLocaleResolver(); + localeResolver.setDefaultLocale(Locale.ENGLISH); + return localeResolver; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor(); + interceptor.setParamName("lang"); + registry.addInterceptor(interceptor); + } +} +``` + +## 3. 国际化使用 + +### 3.1 在控制器中使用 + +Crawlful Hub 项目在控制器中使用国际化消息。 + +**示例代码**: +```java +@RestController +@RequestMapping("/api/v1/users") +public class UserController { + @Autowired + private MessageSource messageSource; + + @PostMapping("/register") + public ResponseEntity register(@RequestBody User user, Locale locale) { + try { + userService.createUser(user); + return ResponseEntity.ok(messageSource.getMessage("auth.register.success", null, locale)); + } catch (Exception e) { + return ResponseEntity.badRequest().body(messageSource.getMessage("auth.register.failure", null, locale)); + } + } +} +``` + +### 3.2 在服务中使用 + +Crawlful Hub 项目在服务中使用国际化消息。 + +**示例代码**: +```java +@Service +public class ProductService { + @Autowired + private MessageSource messageSource; + + public Product createProduct(Product product, Locale locale) { + try { + Product savedProduct = productRepository.save(product); + log.info(messageSource.getMessage("product.create.success", null, locale)); + return savedProduct; + } catch (Exception e) { + log.error(messageSource.getMessage("product.create.failure", null, locale), e); + throw e; + } + } +} +``` + +### 3.3 在异常处理中使用 + +Crawlful Hub 项目在异常处理中使用国际化消息。 + +**示例代码**: +```java +@RestControllerAdvice +public class GlobalExceptionHandler { + @Autowired + private MessageSource messageSource; + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e, Locale locale) { + ApiError error = new ApiError(); + error.setCode("INTERNAL_ERROR"); + error.setMessage(messageSource.getMessage("error.internal", null, locale)); + error.setDetails(e.getMessage()); + error.setTraceId(UUID.randomUUID().toString()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } +} +``` + +## 4. 语言切换 + +Crawlful Hub 项目支持通过 URL 参数切换语言。 + +**示例 URL**: +- 英文:`http://localhost:3001/api/v1/users?lang=en` +- 中文:`http://localhost:3001/api/v1/users?lang=zh` + +**实现细节**: +- 使用 `LocaleChangeInterceptor` 拦截器处理语言切换 +- 通过 `lang` 参数指定语言 +- 支持的语言:`en`(英文)、`zh`(中文) + +## 5. 国际化最佳实践 + +### 5.1 资源文件管理 + +- **命名规范**:使用 `messages_{language}.properties` 命名资源文件 +- **组织方式**:按功能模块组织资源文件,如认证、商品、订单等 +- **编码格式**:使用 UTF-8 编码 +- **注释**:为资源文件添加适当的注释 + +### 5.2 消息键命名 + +- **命名规范**:使用 `module.action.result` 格式命名消息键 +- **一致性**:保持消息键的一致性,便于管理和维护 +- **可读性**:使用清晰、简洁的消息键,便于理解 + +### 5.3 国际化测试 + +- **测试不同语言**:测试系统在不同语言环境下的表现 +- **测试语言切换**:测试语言切换功能是否正常 +- **测试消息显示**:测试消息是否正确显示 + +## 6. 扩展国际化支持 + +### 6.1 添加新语言 + +**步骤**: +1. 在 `src/main/resources/i18n/` 目录下创建新的资源文件,如 `messages_fr.properties`(法语) +2. 翻译资源文件中的消息 +3. 配置 `InternationalizationConfig` 类,支持新的语言 + +**示例**: +```java +@Bean +public LocaleResolver localeResolver() { + CookieLocaleResolver localeResolver = new CookieLocaleResolver(); + localeResolver.setDefaultLocale(Locale.ENGLISH); + return localeResolver; +} +``` + +### 6.2 动态消息 + +Crawlful Hub 项目支持动态消息,通过参数传递动态值。 + +**示例**: + +**资源文件**: +```properties +user.welcome=Welcome, {0}! +``` + +**使用示例**: +```java +String welcomeMessage = messageSource.getMessage("user.welcome", new Object[]{username}, locale); +``` + +## 7. 总结 + +本国际化指南详细介绍了 Crawlful Hub 项目的国际化支持和实现方法。通过本指南,您可以了解如何确保系统在不同语言环境下正常运行。 diff --git a/docs/09_Development_Guide.md b/docs/09_Development_Guide.md new file mode 100644 index 0000000..41196bb --- /dev/null +++ b/docs/09_Development_Guide.md @@ -0,0 +1,340 @@ +# 开发指南 + +## 1. 开发环境搭建 + +### 1.1 环境要求 + +- **Java**: JDK 17 或更高版本 +- **Maven**: 3.6 或更高版本 +- **MySQL**: 8.0 或更高版本 +- **Redis**: 6.0 或更高版本 +- **IDE**: IntelliJ IDEA 或 Eclipse + +### 1.2 安装步骤 + +**Windows 系统**: +1. 下载并安装 JDK 17:[Oracle JDK 17](https://www.oracle.com/java/technologies/downloads/#java17) +2. 下载并安装 Maven:[Apache Maven](https://maven.apache.org/download.cgi) +3. 下载并安装 MySQL:[MySQL Community Server](https://dev.mysql.com/downloads/mysql/) +4. 下载并安装 Redis:[Redis for Windows](https://github.com/tporadowski/redis/releases) +5. 下载并安装 IntelliJ IDEA:[IntelliJ IDEA](https://www.jetbrains.com/idea/download/) + +**Linux 系统**: +1. 安装 JDK 17: + ```bash + sudo yum install java-17-openjdk-devel + ``` +2. 安装 Maven: + ```bash + sudo yum install maven + ``` +3. 安装 MySQL: + ```bash + sudo yum install mysql-server + ``` +4. 安装 Redis: + ```bash + sudo yum install redis + ``` +5. 安装 IntelliJ IDEA: + ```bash + sudo snap install intellij-idea-community --classic + ``` + +### 1.3 项目导入 + +**IntelliJ IDEA**: +1. 打开 IntelliJ IDEA +2. 点击「File」→「Open」 +3. 选择项目目录 `d:\trae_projects\makemd\makemd\serverjava` +4. 点击「OK」,等待项目导入完成 +5. 点击「File」→「Project Structure」→「Project」,设置 JDK 为 17 +6. 点击「File」→「Project Structure」→「Modules」,确保 Maven 依赖正确导入 + +**Eclipse**: +1. 打开 Eclipse +2. 点击「File」→「Import」→「Maven」→「Existing Maven Projects」 +3. 选择项目目录 `d:\trae_projects\makemd\makemd\serverjava` +4. 点击「Finish」,等待项目导入完成 +5. 右键点击项目,选择「Properties」→「Java Build Path」→「Libraries」,确保 JDK 为 17 + +## 2. 代码规范 + +### 2.1 命名规范 + +**类命名**: +- 使用驼峰命名法,首字母大写 +- 类名应该清晰描述类的功能 +- 服务类使用 `Service` 后缀 +- 控制器类使用 `Controller` 后缀 +- 配置类使用 `Config` 后缀 + +**方法命名**: +- 使用驼峰命名法,首字母小写 +- 方法名应该清晰描述方法的功能 +- 动词开头,如 `createUser`, `updateProduct` + +**变量命名**: +- 使用驼峰命名法,首字母小写 +- 变量名应该清晰描述变量的用途 +- 避免使用缩写,如 `usr` 应该改为 `user` + +**常量命名**: +- 使用全大写,单词之间用下划线分隔 +- 常量名应该清晰描述常量的用途 + +### 2.2 代码风格 + +**缩进**: +- 使用 4 个空格进行缩进 +- 避免使用制表符 + +**换行**: +- 每行代码长度不超过 120 个字符 +- 适当换行,提高代码可读性 + +**注释**: +- 为类、方法、变量添加适当的注释 +- 使用 Javadoc 注释格式 +- 注释应该清晰描述代码的功能和用途 + +**示例**: +```java +/** + * 用户服务类,提供用户相关的业务逻辑 + */ +@Service +public class UserService { + /** + * 创建用户 + * @param user 用户信息 + * @return 创建的用户 + */ + public User createUser(User user) { + // 实现创建用户的逻辑 + return userRepository.save(user); + } +} +``` + +### 2.3 代码质量 + +**代码检查**: +- 使用 ESLint 检查代码质量 +- 使用 SonarQube 分析代码质量 +- 定期进行代码审查 + +**测试覆盖**: +- 为代码添加单元测试和集成测试 +- 确保测试覆盖率达到 80% 以上 + +**性能优化**: +- 优化代码逻辑,提高性能 +- 避免不必要的计算和操作 +- 使用缓存减少数据库查询 + +## 3. 开发流程 + +### 3.1 分支管理 + +**分支策略**: +- `main`:主分支,用于发布生产版本 +- `develop`:开发分支,用于集成开发 +- `feature`:特性分支,用于开发新功能 +- `hotfix`:热修复分支,用于修复生产环境的 bug + +**分支命名**: +- 特性分支:`feature/feature-name` +- 热修复分支:`hotfix/bug-description` + +**分支操作**: +1. 从 `develop` 分支创建特性分支 +2. 在特性分支上开发新功能 +3. 提交代码并推送特性分支 +4. 创建 Pull Request 到 `develop` 分支 +5. 代码审查通过后,合并到 `develop` 分支 +6. 从 `main` 分支创建热修复分支 +7. 在热修复分支上修复 bug +8. 提交代码并推送热修复分支 +9. 创建 Pull Request 到 `main` 和 `develop` 分支 +10. 代码审查通过后,合并到 `main` 和 `develop` 分支 + +### 3.2 代码提交 + +**提交规范**: +- 提交消息应该清晰描述提交的内容 +- 使用以下格式:`[模块] 描述` +- 例如:`[User] 修复用户注册功能` + +**提交频率**: +- 每次提交应该只包含一个功能或 bug 修复 +- 避免一次提交多个不相关的更改 +- 定期提交代码,避免代码丢失 + +### 3.3 代码审查 + +**审查流程**: +1. 开发人员创建 Pull Request +2. 团队成员审查代码 +3. 审查通过后,合并代码 +4. 审查不通过时,开发人员修改代码并重新提交 + +**审查重点**: +- 代码质量和风格 +- 功能实现是否正确 +- 测试覆盖是否充分 +- 性能是否优化 +- 安全性是否考虑 + +## 4. 开发工具 + +### 4.1 IDE 插件 + +**IntelliJ IDEA 插件**: +- **Lombok**:简化 Java 代码 +- **Spring Boot Assistant**:Spring Boot 开发辅助 +- **MyBatis Plugin**:MyBatis 开发辅助 +- **SonarLint**:代码质量检查 +- **CheckStyle**:代码风格检查 + +**Eclipse 插件**: +- **Lombok**:简化 Java 代码 +- **Spring Tools Suite**:Spring Boot 开发辅助 +- **MyBatis Generator**:MyBatis 开发辅助 +- **SonarLint**:代码质量检查 +- **CheckStyle**:代码风格检查 + +### 4.2 构建工具 + +**Maven**: +- 用于项目构建和依赖管理 +- 配置文件:`pom.xml` +- 常用命令: + ```bash + # 编译项目 + mvn compile + + # 运行测试 + mvn test + + # 构建项目 + mvn clean package + + # 运行项目 + mvn spring-boot:run + ``` + +### 4.3 版本控制 + +**Git**: +- 用于版本控制 +- 配置文件:`.gitignore` +- 常用命令: + ```bash + # 克隆仓库 + git clone + + # 查看状态 + git status + + # 添加文件 + git add . + + # 提交代码 + git commit -m "[模块] 描述" + + # 推送代码 + git push + + # 拉取代码 + git pull + ``` + +## 5. 开发最佳实践 + +### 5.1 代码组织 + +**目录结构**: +- 遵循 Spring Boot 标准目录结构 +- 按功能模块组织代码 +- 保持代码结构清晰 + +**示例目录结构**: +``` +serverjava/ + ├── src/ + │ ├── main/ + │ │ ├── java/com/crawlful/hub/ + │ │ │ ├── api/controllers/ # 控制器 + │ │ │ ├── service/ # 服务 + │ │ │ ├── model/ # 模型 + │ │ │ ├── config/ # 配置 + │ │ │ ├── util/ # 工具类 + │ │ │ ├── security/ # 安全 + │ │ │ └── monitoring/ # 监控 + │ │ └── resources/ # 资源文件 + │ │ ├── i18n/ # 国际化资源 + │ │ ├── db/migration/ # 数据库迁移脚本 + │ │ ├── application.yml # 应用配置 + │ │ └── logback.xml # 日志配置 + │ └── test/ # 测试代码 + ├── pom.xml # Maven 配置 + └── docs/ # 文档 +``` + +### 5.2 依赖管理 + +**依赖版本**: +- 使用 Spring Boot 3.2.0 版本 +- 统一管理依赖版本 +- 定期更新依赖,修复安全漏洞 + +**依赖范围**: +- `compile`:编译和运行时依赖 +- `test`:测试依赖 +- `provided`:编译时依赖,运行时由容器提供 + +### 5.3 错误处理 + +**全局异常处理**: +- 实现 `GlobalExceptionHandler` 类 +- 统一处理系统异常 +- 返回统一的错误格式 + +**自定义异常**: +- 定义业务异常类 +- 提供错误码和错误消息 +- 便于异常处理和日志记录 + +### 5.4 日志管理 + +**日志级别**: +- `DEBUG`:开发调试,详细执行路径 +- `INFO`:正常业务流程,如订单创建、状态流转 +- `WARN`:潜在问题,如重试、熔断触发 +- `ERROR`:错误异常,如 API 调用失败、数据库异常 + +**日志格式**: +- 包含时间戳、日志级别、类名、消息等 +- 使用 Logback 配置日志格式 + +### 5.5 测试策略 + +**单元测试**: +- 测试单个方法或类 +- 使用 JUnit 5 和 Mockito +- 测试边界条件和异常情况 + +**集成测试**: +- 测试组件之间的交互 +- 使用 Spring Test +- 测试完整的业务流程 + +**系统测试**: +- 测试整个系统的功能 +- 使用 Postman 测试 API 接口 +- 测试用户场景 + +## 6. 总结 + +本开发指南详细介绍了 Crawlful Hub 项目的开发环境搭建、代码规范和开发流程。通过本指南,您可以了解如何快速上手项目开发,确保代码质量和开发效率。 diff --git a/docs/10_API_Changelog.md b/docs/10_API_Changelog.md new file mode 100644 index 0000000..5cb0895 --- /dev/null +++ b/docs/10_API_Changelog.md @@ -0,0 +1,335 @@ +# API 变更日志 + +## 1. 版本 1.0.0 (2024-01-01) + +### 1.1 新增功能 + +- **认证模块**: + - 新增 `POST /api/v1/auth/login` 接口:用户登录 + - 新增 `POST /api/v1/auth/register` 接口:用户注册 + - 新增 `GET /api/v1/auth/me` 接口:获取当前用户信息 + +- **商品模块**: + - 新增 `POST /api/v1/products` 接口:创建商品 + - 新增 `GET /api/v1/products` 接口:获取商品列表 + - 新增 `GET /api/v1/products/{id}` 接口:获取商品详情 + - 新增 `PUT /api/v1/products/{id}` 接口:更新商品 + - 新增 `DELETE /api/v1/products/{id}` 接口:删除商品 + +- **订单模块**: + - 新增 `POST /api/v1/orders` 接口:创建订单 + - 新增 `GET /api/v1/orders` 接口:获取订单列表 + - 新增 `GET /api/v1/orders/{id}` 接口:获取订单详情 + - 新增 `PUT /api/v1/orders/{id}` 接口:更新订单 + - 新增 `DELETE /api/v1/orders/{id}` 接口:删除订单 + +- **支付模块**: + - 新增 `POST /api/v1/payments` 接口:创建支付 + - 新增 `GET /api/v1/payments` 接口:获取支付列表 + - 新增 `GET /api/v1/payments/{id}` 接口:获取支付详情 + - 新增 `PUT /api/v1/payments/{id}` 接口:更新支付 + +- **物流模块**: + - 新增 `POST /api/v1/logistics` 接口:创建物流 + - 新增 `GET /api/v1/logistics` 接口:获取物流列表 + - 新增 `GET /api/v1/logistics/{id}` 接口:获取物流详情 + - 新增 `PUT /api/v1/logistics/{id}` 接口:更新物流 + +- **用户模块**: + - 新增 `POST /api/v1/users` 接口:创建用户 + - 新增 `GET /api/v1/users` 接口:获取用户列表 + - 新增 `GET /api/v1/users/{id}` 接口:获取用户详情 + - 新增 `PUT /api/v1/users/{id}` 接口:更新用户 + - 新增 `DELETE /api/v1/users/{id}` 接口:删除用户 + +- **监控模块**: + - 新增 `GET /api/v1/monitoring/health` 接口:系统健康状态 + - 新增 `GET /api/v1/monitoring/ping` 接口:系统响应测试 + - 新增 `GET /api/v1/monitoring/metrics` 接口:系统性能指标 + +- **告警模块**: + - 新增 `POST /api/v1/alerts` 接口:创建告警 + - 新增 `GET /api/v1/alerts` 接口:获取告警列表 + - 新增 `GET /api/v1/alerts/{id}` 接口:获取告警详情 + - 新增 `PUT /api/v1/alerts/{id}` 接口:更新告警 + - 新增 `PUT /api/v1/alerts/{id}/resolve` 接口:解决告警 + +- **配置模块**: + - 新增 `POST /api/v1/configs` 接口:创建配置 + - 新增 `GET /api/v1/configs` 接口:获取配置列表 + - 新增 `GET /api/v1/configs/{id}` 接口:获取配置详情 + - 新增 `PUT /api/v1/configs/{id}` 接口:更新配置 + - 新增 `DELETE /api/v1/configs/{id}` 接口:删除配置 + +- **审计模块**: + - 新增 `GET /api/v1/audit` 接口:获取审计日志列表 + - 新增 `GET /api/v1/audit/{id}` 接口:获取审计日志详情 + +- **数据模块**: + - 新增 `POST /api/v1/data/import` 接口:导入数据 + - 新增 `GET /api/v1/data/export` 接口:导出数据 + +- **报表模块**: + - 新增 `GET /api/v1/reports/sales` 接口:销售报表 + - 新增 `GET /api/v1/reports/inventory` 接口:库存报表 + - 新增 `GET /api/v1/reports/users` 接口:用户报表 + +### 1.2 变更内容 + +- **认证模块**: + - 使用 JWT 进行认证 + - 支持角色授权 + +- **商品模块**: + - 支持商品分页查询 + - 支持商品搜索 + +- **订单模块**: + - 支持订单状态流转 + - 支持订单查询 + +- **支付模块**: + - 支持多种支付方式 + - 支持支付状态更新 + +- **物流模块**: + - 支持物流状态更新 + - 支持物流查询 + +- **用户模块**: + - 支持用户角色管理 + - 支持用户权限控制 + +- **监控模块**: + - 支持系统健康检查 + - 支持系统性能监控 + +- **告警模块**: + - 支持告警级别管理 + - 支持告警状态更新 + +- **配置模块**: + - 支持配置类型管理 + - 支持配置查询 + +- **审计模块**: + - 支持审计日志查询 + - 支持审计日志过滤 + +- **数据模块**: + - 支持数据导入导出 + - 支持数据格式转换 + +- **报表模块**: + - 支持销售报表生成 + - 支持库存报表生成 + - 支持用户报表生成 + +### 1.3 修复问题 + +- **认证模块**: + - 修复登录失败时的错误处理 + - 修复注册时的参数验证 + +- **商品模块**: + - 修复商品创建时的参数验证 + - 修复商品更新时的权限检查 + +- **订单模块**: + - 修复订单创建时的状态设置 + - 修复订单更新时的权限检查 + +- **支付模块**: + - 修复支付创建时的参数验证 + - 修复支付状态更新时的错误处理 + +- **物流模块**: + - 修复物流创建时的参数验证 + - 修复物流状态更新时的错误处理 + +- **用户模块**: + - 修复用户创建时的参数验证 + - 修复用户更新时的权限检查 + +- **监控模块**: + - 修复健康检查端点的响应格式 + - 修复性能指标的计算 + +- **告警模块**: + - 修复告警创建时的参数验证 + - 修复告警状态更新时的错误处理 + +- **配置模块**: + - 修复配置创建时的参数验证 + - 修复配置更新时的权限检查 + +- **审计模块**: + - 修复审计日志查询时的参数验证 + - 修复审计日志过滤时的错误处理 + +- **数据模块**: + - 修复数据导入时的格式验证 + - 修复数据导出时的格式转换 + +- **报表模块**: + - 修复销售报表生成时的计算错误 + - 修复库存报表生成时的计算错误 + - 修复用户报表生成时的计算错误 + +## 2. 版本 1.1.0 (2024-02-01) + +### 2.1 新增功能 + +- **认证模块**: + - 新增 `POST /api/v1/auth/refresh` 接口:刷新 token + - 新增 `POST /api/v1/auth/logout` 接口:用户登出 + +- **商品模块**: + - 新增 `GET /api/v1/products/categories` 接口:获取商品分类 + - 新增 `POST /api/v1/products/batch` 接口:批量创建商品 + +- **订单模块**: + - 新增 `POST /api/v1/orders/batch` 接口:批量创建订单 + - 新增 `GET /api/v1/orders/status` 接口:获取订单状态统计 + +- **支付模块**: + - 新增 `POST /api/v1/payments/batch` 接口:批量创建支付 + - 新增 `GET /api/v1/payments/status` 接口:获取支付状态统计 + +- **物流模块**: + - 新增 `POST /api/v1/logistics/batch` 接口:批量创建物流 + - 新增 `GET /api/v1/logistics/status` 接口:获取物流状态统计 + +- **用户模块**: + - 新增 `POST /api/v1/users/batch` 接口:批量创建用户 + - 新增 `GET /api/v1/users/roles` 接口:获取用户角色 + +- **监控模块**: + - 新增 `GET /api/v1/monitoring/health/detail` 接口:详细健康状态 + - 新增 `GET /api/v1/monitoring/metrics/detail` 接口:详细性能指标 + +- **告警模块**: + - 新增 `POST /api/v1/alerts/batch` 接口:批量创建告警 + - 新增 `GET /api/v1/alerts/severity` 接口:获取告警严重程度统计 + +- **配置模块**: + - 新增 `POST /api/v1/configs/batch` 接口:批量创建配置 + - 新增 `GET /api/v1/configs/types` 接口:获取配置类型 + +- **审计模块**: + - 新增 `GET /api/v1/audit/actions` 接口:获取审计操作类型 + - 新增 `GET /api/v1/audit/resources` 接口:获取审计资源类型 + +- **数据模块**: + - 新增 `POST /api/v1/data/import/batch` 接口:批量导入数据 + - 新增 `GET /api/v1/data/export/batch` 接口:批量导出数据 + +- **报表模块**: + - 新增 `GET /api/v1/reports/sales/detail` 接口:详细销售报表 + - 新增 `GET /api/v1/reports/inventory/detail` 接口:详细库存报表 + - 新增 `GET /api/v1/reports/users/detail` 接口:详细用户报表 + +### 2.2 变更内容 + +- **认证模块**: + - 优化 JWT 认证逻辑 + - 支持 token 过期时间配置 + +- **商品模块**: + - 优化商品搜索逻辑 + - 支持商品分类管理 + +- **订单模块**: + - 优化订单状态流转逻辑 + - 支持订单批量操作 + +- **支付模块**: + - 优化支付处理逻辑 + - 支持支付批量操作 + +- **物流模块**: + - 优化物流处理逻辑 + - 支持物流批量操作 + +- **用户模块**: + - 优化用户角色管理逻辑 + - 支持用户批量操作 + +- **监控模块**: + - 优化健康检查逻辑 + - 支持详细性能指标 + +- **告警模块**: + - 优化告警处理逻辑 + - 支持告警批量操作 + +- **配置模块**: + - 优化配置管理逻辑 + - 支持配置批量操作 + +- **审计模块**: + - 优化审计日志查询逻辑 + - 支持审计操作类型和资源类型查询 + +- **数据模块**: + - 优化数据导入导出逻辑 + - 支持数据批量操作 + +- **报表模块**: + - 优化报表生成逻辑 + - 支持详细报表生成 + +### 2.3 修复问题 + +- **认证模块**: + - 修复 token 刷新时的错误处理 + - 修复登出时的 token 失效处理 + +- **商品模块**: + - 修复商品分类查询时的错误处理 + - 修复商品批量创建时的参数验证 + +- **订单模块**: + - 修复订单批量创建时的参数验证 + - 修复订单状态统计时的计算错误 + +- **支付模块**: + - 修复支付批量创建时的参数验证 + - 修复支付状态统计时的计算错误 + +- **物流模块**: + - 修复物流批量创建时的参数验证 + - 修复物流状态统计时的计算错误 + +- **用户模块**: + - 修复用户批量创建时的参数验证 + - 修复用户角色查询时的错误处理 + +- **监控模块**: + - 修复详细健康状态查询时的错误处理 + - 修复详细性能指标查询时的计算错误 + +- **告警模块**: + - 修复告警批量创建时的参数验证 + - 修复告警严重程度统计时的计算错误 + +- **配置模块**: + - 修复配置批量创建时的参数验证 + - 修复配置类型查询时的错误处理 + +- **审计模块**: + - 修复审计操作类型查询时的错误处理 + - 修复审计资源类型查询时的错误处理 + +- **数据模块**: + - 修复数据批量导入时的格式验证 + - 修复数据批量导出时的格式转换 + +- **报表模块**: + - 修复详细销售报表生成时的计算错误 + - 修复详细库存报表生成时的计算错误 + - 修复详细用户报表生成时的计算错误 + +## 3. 总结 + +本 API 变更日志详细记录了 Crawlful Hub 项目的 API 变更历史。通过本日志,您可以了解 API 的新增功能、变更内容和修复问题,便于 API 的使用和维护。 diff --git a/index.md b/index.md new file mode 100644 index 0000000..82481d7 --- /dev/null +++ b/index.md @@ -0,0 +1,83 @@ +# Java 后端平移计划 + +## 项目背景 + +本项目需要将现有的 Node.js 后端平移为 Java 后端,保持与前端和客户端的兼容性,不修改现有代码。 + +## 目录结构 + +``` +serverjava/ +├── src/ # Java 源代码 +│ ├── api/ # API 层 +│ ├── service/ # 服务层 +│ ├── model/ # 数据模型 +│ ├── config/ # 配置文件 +│ └── util/ # 工具类 +├── pom.xml # Maven 配置文件 +└── README.md # 项目说明 +``` + +## 平移原则 + +1. **保持 API 兼容性**:所有 API 接口的路径、参数、返回格式必须与 Node.js 版本一致 +2. **保持业务逻辑一致**:所有业务逻辑必须与 Node.js 版本保持一致 +3. **不修改前端和客户端**:平移过程中不得修改任何前端或客户端代码 +4. **不修改 Node.js 代码**:平移过程中不得修改任何 Node.js 后端代码 + +## 平移范围 + +### 核心服务 + +1. **认证服务**:用户登录、注册、权限管理 +2. **商品服务**:商品管理、库存管理 +3. **订单服务**:订单创建、状态管理、物流跟踪 +4. **支付服务**:支付处理、退款管理 +5. **数据服务**:数据统计、报表生成 + +### API 接口 + +所有现有的 Node.js API 接口都需要在 Java 后端中实现,包括: + +- `/api/auth/*`:认证相关接口 +- `/api/product/*`:商品相关接口 +- `/api/order/*`:订单相关接口 +- `/api/payment/*`:支付相关接口 +- `/api/report/*`:报表相关接口 + +## 技术栈选择 + +- **框架**:Spring Boot 3.x +- **数据库**:MySQL 8.0 +- **缓存**:Redis +- **认证**:JWT +- **构建工具**:Maven +- **Java 版本**:Java 17(Spring Boot 3.0+ 最低要求) + +## 实现步骤 + +1. **搭建项目结构**:创建 Spring Boot 项目,设置基本目录结构 +2. **配置文件**:配置数据库连接、Redis 连接等 +3. **数据模型**:根据 Node.js 版本创建对应的 Java 数据模型 +4. **服务层**:实现业务逻辑,保持与 Node.js 版本一致 +5. **API 层**:实现 RESTful API 接口,保持与 Node.js 版本一致 +6. **测试**:确保所有 API 接口正常工作 +7. **部署**:部署 Java 后端服务 + +## 注意事项 + +1. **数据迁移**:需要确保数据结构与 Node.js 版本一致,避免数据丢失 +2. **性能优化**:针对 Java 特性进行性能优化,确保服务响应速度 +3. **安全性**:保持与 Node.js 版本相同的安全措施,确保系统安全 +4. **监控**:实现与 Node.js 版本相同的监控机制,确保系统稳定运行 + +## 时间计划 + +1. **项目搭建**:1 周 +2. **核心功能实现**:3 周 +3. **测试与优化**:1 周 +4. **部署与上线**:1 周 + +## 联系方式 + +如有任何问题,请联系项目负责人。 \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5097471 --- /dev/null +++ b/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + + com.crawlful + crawlful-hub-backend + 1.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + + + 17 + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-cache + + + + + com.mysql + mysql-connector-j + runtime + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + + + org.apache.commons + commons-lang3 + + + com.fasterxml.jackson.core + jackson-databind + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.flywaydb + flyway-core + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.0.2 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/src/main/java/com/crawlful/hub/Application.java b/src/main/java/com/crawlful/hub/Application.java new file mode 100644 index 0000000..6faf911 --- /dev/null +++ b/src/main/java/com/crawlful/hub/Application.java @@ -0,0 +1,11 @@ +package com.crawlful.hub; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/src/main/java/com/crawlful/hub/api/controllers/AlertController.java b/src/main/java/com/crawlful/hub/api/controllers/AlertController.java new file mode 100644 index 0000000..5313fe7 --- /dev/null +++ b/src/main/java/com/crawlful/hub/api/controllers/AlertController.java @@ -0,0 +1,199 @@ +package com.crawlful.hub.api.controllers; + +import com.crawlful.hub.model.Alert; +import com.crawlful.hub.service.AlertService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/v1/alerts") +public class AlertController { + @Autowired + private AlertService alertService; + + @PostMapping + public ResponseEntity createAlert(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Alert alert = alertService.createAlert(request); + Map response = new HashMap<>(); + response.put("success", true); + response.put("alertId", alert.getId()); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping + public ResponseEntity getAlerts(@RequestParam String tenantId, @RequestParam Map params) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Map filters = new HashMap<>(); + if (params.containsKey("status")) { + filters.put("status", params.get("status")); + } + if (params.containsKey("severity")) { + filters.put("severity", params.get("severity")); + } + if (params.containsKey("alertType")) { + filters.put("alertType", params.get("alertType")); + } + if (params.containsKey("startDate") && params.containsKey("endDate")) { + try { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + filters.put("startDate", sdf.parse(params.get("startDate"))); + filters.put("endDate", sdf.parse(params.get("endDate"))); + } catch (ParseException e) { + return new ResponseEntity<>(Map.of("success", false, "error", "Invalid date format. Use yyyy-MM-dd"), HttpStatus.BAD_REQUEST); + } + } + + List alerts = alertService.getAlerts(tenantId, filters); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", alerts); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/{id}") + public ResponseEntity getAlertById(@PathVariable Long id, @RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Alert alert = alertService.getAlertById(tenantId, id); + if (alert == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Alert not found"), HttpStatus.NOT_FOUND); + } + + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", alert); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PutMapping("/{id}/resolve") + public ResponseEntity resolveAlert(@PathVariable Long id, @RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + alertService.resolveAlert(tenantId, id); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Alert resolved successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PutMapping("/{id}/status") + public ResponseEntity updateAlertStatus(@PathVariable Long id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + String status = (String) request.get("status"); + + if (tenantId == null || status == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId or status"), HttpStatus.BAD_REQUEST); + } + + alertService.updateAlertStatus(tenantId, id, status); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Alert status updated successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/stats") + public ResponseEntity getAlertStats(@RequestParam String tenantId, @RequestParam String startDate, @RequestParam String endDate) { + try { + if (tenantId == null || startDate == null || endDate == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing required parameters"), HttpStatus.BAD_REQUEST); + } + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + Date start = sdf.parse(startDate); + Date end = sdf.parse(endDate); + + Map stats = alertService.getAlertStats(tenantId, start, end); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", stats); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (ParseException e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", "Invalid date format. Use yyyy-MM-dd"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/check-thresholds") + public ResponseEntity checkThresholds(@RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + alertService.checkThresholds(tenantId); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Thresholds checked successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/crawlful/hub/api/controllers/AuditController.java b/src/main/java/com/crawlful/hub/api/controllers/AuditController.java new file mode 100644 index 0000000..442dde6 --- /dev/null +++ b/src/main/java/com/crawlful/hub/api/controllers/AuditController.java @@ -0,0 +1,155 @@ +package com.crawlful.hub.api.controllers; + +import com.crawlful.hub.model.Audit; +import com.crawlful.hub.service.AuditService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/v1/audits") +public class AuditController { + @Autowired + private AuditService auditService; + + @PostMapping + public ResponseEntity createAudit(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Audit audit = auditService.createAudit(request); + Map response = new HashMap<>(); + response.put("success", true); + response.put("auditId", audit.getId()); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping + public ResponseEntity getAudits(@RequestParam String tenantId, @RequestParam Map params) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Map filters = new HashMap<>(); + if (params.containsKey("shopId")) { + filters.put("shopId", params.get("shopId")); + } + if (params.containsKey("userId")) { + filters.put("userId", Long.parseLong(params.get("userId"))); + } + if (params.containsKey("action")) { + filters.put("action", params.get("action")); + } + if (params.containsKey("resourceType")) { + filters.put("resourceType", params.get("resourceType")); + } + if (params.containsKey("startDate") && params.containsKey("endDate")) { + try { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + filters.put("startDate", sdf.parse(params.get("startDate"))); + filters.put("endDate", sdf.parse(params.get("endDate"))); + } catch (ParseException e) { + return new ResponseEntity<>(Map.of("success", false, "error", "Invalid date format. Use yyyy-MM-dd"), HttpStatus.BAD_REQUEST); + } + } + + List audits = auditService.getAudits(tenantId, filters); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", audits); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/stats") + public ResponseEntity getAuditStats(@RequestParam String tenantId, @RequestParam String startDate, @RequestParam String endDate) { + try { + if (tenantId == null || startDate == null || endDate == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing required parameters"), HttpStatus.BAD_REQUEST); + } + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + Date start = sdf.parse(startDate); + Date end = sdf.parse(endDate); + + Map stats = auditService.getAuditStats(tenantId, start, end); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", stats); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (ParseException e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", "Invalid date format. Use yyyy-MM-dd"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/recent") + public ResponseEntity getRecentAudits(@RequestParam String tenantId, @RequestParam(defaultValue = "10") int limit) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + List recentAudits = auditService.getRecentAudits(tenantId, limit); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", recentAudits); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/search") + public ResponseEntity searchAudits(@RequestParam String tenantId, @RequestParam String keyword) { + try { + if (tenantId == null || keyword == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId or keyword"), HttpStatus.BAD_REQUEST); + } + + Map searchResults = auditService.searchAudits(tenantId, keyword); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", searchResults); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/crawlful/hub/api/controllers/AuthController.java b/src/main/java/com/crawlful/hub/api/controllers/AuthController.java new file mode 100644 index 0000000..e092dfc --- /dev/null +++ b/src/main/java/com/crawlful/hub/api/controllers/AuthController.java @@ -0,0 +1,57 @@ +package com.crawlful.hub.api.controllers; + +import com.crawlful.hub.model.User; +import com.crawlful.hub.service.AuthService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/v1/auth") +public class AuthController { + @Autowired + private AuthService authService; + + @PostMapping("/register") + public ResponseEntity register(@RequestBody Map request) { + try { + String tenantId = request.get("tenantId"); + String username = request.get("username"); + String password = request.get("password"); + String email = request.get("email"); + String role = request.get("role"); + + User user = authService.register(tenantId, username, password, email, role); + Map response = new HashMap<>(); + response.put("user", user); + response.put("message", "User registered successfully"); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody Map request) { + try { + String tenantId = request.get("tenantId"); + String username = request.get("username"); + String password = request.get("password"); + + String token = authService.login(tenantId, username, password); + Map response = new HashMap<>(); + response.put("token", token); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.UNAUTHORIZED); + } + } +} diff --git a/src/main/java/com/crawlful/hub/api/controllers/ConfigController.java b/src/main/java/com/crawlful/hub/api/controllers/ConfigController.java new file mode 100644 index 0000000..2feced2 --- /dev/null +++ b/src/main/java/com/crawlful/hub/api/controllers/ConfigController.java @@ -0,0 +1,165 @@ +package com.crawlful.hub.api.controllers; + +import com.crawlful.hub.model.Config; +import com.crawlful.hub.service.ConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/v1/configs") +public class ConfigController { + @Autowired + private ConfigService configService; + + @PostMapping + public ResponseEntity createConfig(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Config config = configService.createConfig(request); + Map response = new HashMap<>(); + response.put("success", true); + response.put("configId", config.getId()); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping + public ResponseEntity getConfigs(@RequestParam String tenantId, @RequestParam(required = false) String shopId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + List configs = configService.getConfigs(tenantId, shopId); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", configs); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/value") + public ResponseEntity getConfigValue(@RequestParam String tenantId, @RequestParam String configKey, @RequestParam(required = false) String shopId) { + try { + if (tenantId == null || configKey == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId or configKey"), HttpStatus.BAD_REQUEST); + } + + String configValue = configService.getConfigValue(tenantId, configKey, shopId); + Map response = new HashMap<>(); + response.put("success", true); + response.put("configKey", configKey); + response.put("configValue", configValue); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/values") + public ResponseEntity getConfigValues(@RequestParam String tenantId, @RequestParam List configKeys, @RequestParam(required = false) String shopId) { + try { + if (tenantId == null || configKeys == null || configKeys.isEmpty()) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId or configKeys"), HttpStatus.BAD_REQUEST); + } + + Map configValues = configService.getConfigValues(tenantId, configKeys, shopId); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", configValues); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PutMapping("/{id}") + public ResponseEntity updateConfig(@PathVariable Long id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + configService.updateConfig(tenantId, id, request); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Config updated successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteConfig(@PathVariable Long id, @RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + configService.deleteConfig(tenantId, id); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Config deleted successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/batch") + public ResponseEntity batchUpdateConfigs(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + List> configs = (List>) request.get("configs"); + + if (tenantId == null || configs == null || configs.isEmpty()) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId or configs"), HttpStatus.BAD_REQUEST); + } + + configService.batchUpdateConfigs(tenantId, configs); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Configs updated successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/crawlful/hub/api/controllers/DataController.java b/src/main/java/com/crawlful/hub/api/controllers/DataController.java new file mode 100644 index 0000000..efb03a0 --- /dev/null +++ b/src/main/java/com/crawlful/hub/api/controllers/DataController.java @@ -0,0 +1,133 @@ +package com.crawlful.hub.api.controllers; + +import com.crawlful.hub.service.DataService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/v1/data") +public class DataController { + @Autowired + private DataService dataService; + + @GetMapping("/dashboard") + public ResponseEntity getDashboardData(@RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Map dashboardData = dataService.getDashboardData(tenantId); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", dashboardData); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/sales-report") + public ResponseEntity getSalesReport(@RequestParam String tenantId, @RequestParam String startDate, @RequestParam String endDate) { + try { + if (tenantId == null || startDate == null || endDate == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing required parameters"), HttpStatus.BAD_REQUEST); + } + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + Date start = sdf.parse(startDate); + Date end = sdf.parse(endDate); + + Map salesReport = dataService.getSalesReport(tenantId, start, end); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", salesReport); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (ParseException e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", "Invalid date format. Use yyyy-MM-dd"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/inventory-report") + public ResponseEntity getInventoryReport(@RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Map inventoryReport = dataService.getInventoryReport(tenantId); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", inventoryReport); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/export") + public ResponseEntity exportData(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + String dataType = (String) request.get("dataType"); + String format = (String) request.get("format"); + + if (tenantId == null || dataType == null || format == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing required parameters"), HttpStatus.BAD_REQUEST); + } + + Map exportResult = dataService.exportData(tenantId, dataType, format); + Map response = new HashMap<>(); + response.put("success", exportResult.get("success")); + response.put("data", exportResult); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/platform-performance") + public ResponseEntity getPlatformPerformance(@RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Map platformPerformance = dataService.getPlatformPerformance(tenantId); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", platformPerformance); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/crawlful/hub/api/controllers/LogisticsController.java b/src/main/java/com/crawlful/hub/api/controllers/LogisticsController.java new file mode 100644 index 0000000..c283be7 --- /dev/null +++ b/src/main/java/com/crawlful/hub/api/controllers/LogisticsController.java @@ -0,0 +1,159 @@ +package com.crawlful.hub.api.controllers; + +import com.crawlful.hub.model.Logistics; +import com.crawlful.hub.service.LogisticsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/v1/logistics") +public class LogisticsController { + @Autowired + private LogisticsService logisticsService; + + @PostMapping + public ResponseEntity createLogistics(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Logistics logistics = logisticsService.createLogistics(request); + Map response = new HashMap<>(); + response.put("success", true); + response.put("logisticsId", logistics.getId()); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping + public ResponseEntity getLogistics(@RequestParam Map params) { + try { + String tenantId = params.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Map filters = new HashMap<>(); + if (params.containsKey("status")) { + filters.put("status", params.get("status")); + } + if (params.containsKey("orderId")) { + filters.put("orderId", Long.parseLong(params.get("orderId"))); + } + + List logisticsList = logisticsService.getLogistics(tenantId, filters); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", logisticsList); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/{id}") + public ResponseEntity getLogisticsById(@PathVariable Long id, @RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Logistics logistics = logisticsService.getLogisticsById(tenantId, id); + if (logistics == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Logistics not found"), HttpStatus.NOT_FOUND); + } + + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", logistics); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PutMapping("/{id}") + public ResponseEntity updateLogistics(@PathVariable Long id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + logisticsService.updateLogistics(tenantId, id, request); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Logistics updated successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/track/{trackingNumber}") + public ResponseEntity trackLogistics(@PathVariable String trackingNumber, @RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Map result = logisticsService.trackLogistics(tenantId, trackingNumber); + Map response = new HashMap<>(); + response.put("success", result.get("success")); + if (result.get("success").equals(true)) { + response.put("data", result); + } else { + response.put("error", result.get("error")); + } + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/calculate-cost") + public ResponseEntity calculateShippingCost(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Map result = logisticsService.calculateShippingCost(tenantId, request); + Map response = new HashMap<>(); + response.put("success", result.get("success")); + response.put("data", result); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/crawlful/hub/api/controllers/MonitoringController.java b/src/main/java/com/crawlful/hub/api/controllers/MonitoringController.java new file mode 100644 index 0000000..352114c --- /dev/null +++ b/src/main/java/com/crawlful/hub/api/controllers/MonitoringController.java @@ -0,0 +1,129 @@ +package com.crawlful.hub.api.controllers; + +import com.crawlful.hub.service.MonitoringService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/v1/monitoring") +public class MonitoringController { + @Autowired + private MonitoringService monitoringService; + + @GetMapping("/health") + public ResponseEntity getSystemHealth() { + try { + Map health = monitoringService.getSystemHealth(); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", health); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/metrics") + public ResponseEntity getPerformanceMetrics() { + try { + Map metrics = monitoringService.getPerformanceMetrics(); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", metrics); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/services") + public ResponseEntity getServiceStatus() { + try { + Map status = monitoringService.getServiceStatus(); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", status); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/database") + public ResponseEntity getDatabaseStatus() { + try { + Map dbStatus = monitoringService.getDatabaseStatus(); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", dbStatus); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/cache") + public ResponseEntity getCacheStatus() { + try { + Map cacheStatus = monitoringService.getCacheStatus(); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", cacheStatus); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/stats") + public ResponseEntity getSystemStats() { + try { + Map stats = monitoringService.getSystemStats(); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", stats); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/ping") + public ResponseEntity ping() { + try { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Pong"); + response.put("timestamp", System.currentTimeMillis()); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/crawlful/hub/api/controllers/OrderController.java b/src/main/java/com/crawlful/hub/api/controllers/OrderController.java new file mode 100644 index 0000000..ec9607f --- /dev/null +++ b/src/main/java/com/crawlful/hub/api/controllers/OrderController.java @@ -0,0 +1,521 @@ +package com.crawlful.hub.api.controllers; + +import com.crawlful.hub.model.Order; +import com.crawlful.hub.service.OrderService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/v1/orders") +public class OrderController { + @Autowired + private OrderService orderService; + + @PostMapping + public ResponseEntity createOrder(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Order order = orderService.createOrder(request); + Map response = new HashMap<>(); + response.put("success", true); + response.put("orderId", order.getId()); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping + public ResponseEntity getOrders(@RequestParam Map params) { + try { + String tenantId = params.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Map queryParams = new HashMap<>(); + if (params.containsKey("page")) { + queryParams.put("page", Integer.parseInt(params.get("page"))); + } + if (params.containsKey("pageSize")) { + queryParams.put("pageSize", Integer.parseInt(params.get("pageSize"))); + } + if (params.containsKey("status")) { + queryParams.put("status", params.get("status")); + } + if (params.containsKey("platform")) { + queryParams.put("platform", params.get("platform")); + } + + List orders = orderService.getOrders(tenantId, queryParams); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", orders); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/{id}") + public ResponseEntity getOrderById(@PathVariable String id, @RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Order order = orderService.getOrderById(tenantId, id); + if (order == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Order not found"), HttpStatus.NOT_FOUND); + } + + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", order); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PutMapping("/{id}") + public ResponseEntity updateOrder(@PathVariable String id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + orderService.updateOrder(id, tenantId, request); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Order updated successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteOrder(@PathVariable String id, @RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + orderService.deleteOrder(id, tenantId); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Order deleted successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/sync") + public ResponseEntity triggerManualSync(@RequestBody Map request) { + try { + String platform = (String) request.get("platform"); + String shopId = (String) request.get("shopId"); + + if (platform == null || shopId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing platform or shopId"), HttpStatus.BAD_REQUEST); + } + + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Manual sync triggered for " + platform); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/stats") + public ResponseEntity getStats(@RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Map stats = orderService.getOrderStats(tenantId); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", stats); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/{id}/status") + public ResponseEntity transitionOrderStatus(@PathVariable String id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + String status = (String) request.get("status"); + String reason = (String) request.get("reason"); + + if (tenantId == null || status == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId or status"), HttpStatus.BAD_REQUEST); + } + + orderService.transitionOrderStatus(tenantId, id, status, reason); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Order status updated successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PutMapping("/batch") + public ResponseEntity batchUpdateOrders(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + List orderIds = (List) request.get("orderIds"); + Map updates = (Map) request.get("updates"); + + if (tenantId == null || orderIds == null || updates == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId, orderIds, or updates"), HttpStatus.BAD_REQUEST); + } + + Map result = orderService.batchUpdateOrders(tenantId, orderIds, updates); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", result); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/batch/audit") + public ResponseEntity batchAuditOrders(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + List orderIds = (List) request.get("orderIds"); + + if (tenantId == null || orderIds == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId or orderIds"), HttpStatus.BAD_REQUEST); + } + + Map result = orderService.batchAuditOrders(tenantId, orderIds); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", result); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/batch/ship") + public ResponseEntity batchShipOrders(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + List orderIds = (List) request.get("orderIds"); + + if (tenantId == null || orderIds == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId or orderIds"), HttpStatus.BAD_REQUEST); + } + + Map result = orderService.batchShipOrders(tenantId, orderIds); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", result); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/{id}/exception") + public ResponseEntity markOrderAsException(@PathVariable String id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + String reason = (String) request.get("reason"); + + if (tenantId == null || reason == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId or reason"), HttpStatus.BAD_REQUEST); + } + + orderService.markOrderAsException(tenantId, id, reason); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Order marked as exception"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/{id}/reroute") + public ResponseEntity autoRerouteOrder(@PathVariable String id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + orderService.autoRerouteOrder(tenantId, id); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Order rerouted successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/{id}/retry") + public ResponseEntity retryExceptionOrder(@PathVariable String id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + orderService.retryExceptionOrder(tenantId, id); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Order retried successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/{id}/cancel") + public ResponseEntity cancelOrder(@PathVariable String id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + String reason = (String) request.get("reason"); + + if (tenantId == null || reason == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId or reason"), HttpStatus.BAD_REQUEST); + } + + orderService.cancelOrder(tenantId, id, reason); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Order cancelled successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/{id}/refund") + public ResponseEntity requestRefund(@PathVariable String id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + String reason = (String) request.get("reason"); + Double amount = (Double) request.get("amount"); + + if (tenantId == null || reason == null || amount == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId, reason, or amount"), HttpStatus.BAD_REQUEST); + } + + String refundId = orderService.requestRefund(tenantId, id, reason, amount); + Map response = new HashMap<>(); + response.put("success", true); + response.put("refundId", refundId); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/refund/{id}/approve") + public ResponseEntity approveRefund(@PathVariable String id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + Boolean approved = (Boolean) request.get("approved"); + String note = (String) request.get("note"); + + if (tenantId == null || approved == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId or approved"), HttpStatus.BAD_REQUEST); + } + + orderService.approveRefund(tenantId, id, approved, note); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Refund processed successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/{id}/after-sales") + public ResponseEntity requestAfterSales(@PathVariable String id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + String type = (String) request.get("type"); + String reason = (String) request.get("reason"); + List> items = (List>) request.get("items"); + + if (tenantId == null || type == null || reason == null || items == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing required parameters"), HttpStatus.BAD_REQUEST); + } + + String serviceId = orderService.requestAfterSales(tenantId, id, type, reason, items); + Map response = new HashMap<>(); + response.put("success", true); + response.put("serviceId", serviceId); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/after-sales/{id}/process") + public ResponseEntity processAfterSales(@PathVariable String id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + String action = (String) request.get("action"); + String note = (String) request.get("note"); + + if (tenantId == null || action == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId or action"), HttpStatus.BAD_REQUEST); + } + + orderService.processAfterSales(tenantId, id, action, note); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "After-sales service processed successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/{id}/complete") + public ResponseEntity completeOrder(@PathVariable String id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + orderService.completeOrder(tenantId, id); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Order completed successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/webhook/{platform}") + public ResponseEntity handlePlatformWebhook(@PathVariable String platform, @RequestBody Map payload, @RequestHeader Map headers) { + try { + String tenantId = headers.get("x-tenant-id") != null ? headers.get("x-tenant-id") : "default-tenant"; + String shopId = headers.get("x-shop-id") != null ? headers.get("x-shop-id") : "default-shop"; + + Order order = orderService.mapPlatformPayloadToOrder(platform, payload, tenantId, shopId); + if (order == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Unsupported or invalid payload for platform: " + platform), HttpStatus.BAD_REQUEST); + } + + Order savedOrder = orderService.createOrder(Map.of( + "tenantId", order.getTenantId(), + "shopId", order.getShopId(), + "platform", order.getPlatform(), + "platformOrderId", order.getPlatformOrderId(), + "status", order.getStatus(), + "totalAmount", order.getTotalAmount(), + "currency", order.getCurrency(), + "customerInfo", order.getCustomerInfo(), + "items", order.getItems(), + "shippingAddress", order.getShippingAddress() + )); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("orderId", savedOrder.getId()); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", "Internal server error"); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/crawlful/hub/api/controllers/PaymentController.java b/src/main/java/com/crawlful/hub/api/controllers/PaymentController.java new file mode 100644 index 0000000..5e9718c --- /dev/null +++ b/src/main/java/com/crawlful/hub/api/controllers/PaymentController.java @@ -0,0 +1,165 @@ +package com.crawlful.hub.api.controllers; + +import com.crawlful.hub.model.Payment; +import com.crawlful.hub.service.PaymentService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/v1/payment") +public class PaymentController { + @Autowired + private PaymentService paymentService; + + @PostMapping + public ResponseEntity createPayment(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Payment payment = paymentService.createPayment(request); + Map response = new HashMap<>(); + response.put("success", true); + response.put("paymentId", payment.getId()); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping + public ResponseEntity getPayments(@RequestParam Map params) { + try { + String tenantId = params.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Map filters = new HashMap<>(); + if (params.containsKey("status")) { + filters.put("status", params.get("status")); + } + if (params.containsKey("orderId")) { + filters.put("orderId", Long.parseLong(params.get("orderId"))); + } + + List payments = paymentService.getPayments(tenantId, filters); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", payments); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/{id}") + public ResponseEntity getPaymentById(@PathVariable Long id, @RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Payment payment = paymentService.getPaymentById(tenantId, id); + if (payment == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Payment not found"), HttpStatus.NOT_FOUND); + } + + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", payment); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PutMapping("/{id}") + public ResponseEntity updatePayment(@PathVariable Long id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + paymentService.updatePayment(tenantId, id, request); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Payment updated successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/callback") + public ResponseEntity processPaymentCallback(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + String transactionId = (String) request.get("transactionId"); + + if (tenantId == null || transactionId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId or transactionId"), HttpStatus.BAD_REQUEST); + } + + paymentService.processPaymentCallback(tenantId, transactionId, request); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "Callback processed successfully"); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/{id}/refund") + public ResponseEntity refundPayment(@PathVariable Long id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + Double amount = (Double) request.get("amount"); + String reason = (String) request.get("reason"); + + if (tenantId == null || amount == null || reason == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId, amount, or reason"), HttpStatus.BAD_REQUEST); + } + + Map result = paymentService.refundPayment(tenantId, id, amount, reason); + Map response = new HashMap<>(); + response.put("success", result.get("success")); + if (result.get("success").equals(true)) { + response.put("refundId", result.get("refundId")); + } else { + response.put("error", result.get("error")); + } + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/crawlful/hub/api/controllers/ProductController.java b/src/main/java/com/crawlful/hub/api/controllers/ProductController.java new file mode 100644 index 0000000..489835c --- /dev/null +++ b/src/main/java/com/crawlful/hub/api/controllers/ProductController.java @@ -0,0 +1,208 @@ +package com.crawlful.hub.api.controllers; + +import com.crawlful.hub.model.Product; +import com.crawlful.hub.service.ProductService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/v1/product") +public class ProductController { + @Autowired + private ProductService productService; + + @PostMapping + public ResponseEntity create(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Product product = productService.create(tenantId, request); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", Map.of("id", product.getId())); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping + public ResponseEntity getAll(@RequestParam Map params) { + try { + String tenantId = params.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Map filters = new HashMap<>(); + if (params.containsKey("platform")) { + filters.put("platform", params.get("platform")); + } + if (params.containsKey("status")) { + filters.put("status", params.get("status")); + } + + int page = 1; + int size = 10; + if (params.containsKey("page")) { + try { + page = Integer.parseInt(params.get("page")); + } catch (NumberFormatException e) { + page = 1; + } + } + if (params.containsKey("size")) { + try { + size = Integer.parseInt(params.get("size")); + } catch (NumberFormatException e) { + size = 10; + } + } + + List products = productService.getAll(tenantId, filters, page, size); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", products); + response.put("page", page); + response.put("size", size); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/{id}") + public ResponseEntity getById(@PathVariable Long id, @RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Product product = productService.getById(tenantId, id); + if (product == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Product not found"), HttpStatus.NOT_FOUND); + } + + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", product); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PutMapping("/{id}") + public ResponseEntity update(@PathVariable Long id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Product product = productService.update(tenantId, id, request); + if (product == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Product not found"), HttpStatus.NOT_FOUND); + } + + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", product); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable Long id, @RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + productService.delete(tenantId, id); + Map response = new HashMap<>(); + response.put("success", true); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/{id}/wash-and-localize") + public ResponseEntity washAndLocalize(@PathVariable Long id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + String targetMarket = (String) request.get("targetMarket"); + String targetLang = (String) request.get("targetLang"); + + if (tenantId == null || targetMarket == null || targetLang == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing required parameters"), HttpStatus.BAD_REQUEST); + } + + Map result = productService.washAndLocalize(tenantId, id, targetMarket, targetLang); + if (result == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Product not found"), HttpStatus.NOT_FOUND); + } + + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", result); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/{id}/analyze-arbitrage") + public ResponseEntity analyzeArbitrage(@PathVariable Long id, @RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Map result = productService.analyzeProductArbitrage(tenantId, id); + if (result == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Product not found"), HttpStatus.NOT_FOUND); + } + + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", result); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/crawlful/hub/api/controllers/ReportController.java b/src/main/java/com/crawlful/hub/api/controllers/ReportController.java new file mode 100644 index 0000000..089ecd0 --- /dev/null +++ b/src/main/java/com/crawlful/hub/api/controllers/ReportController.java @@ -0,0 +1,192 @@ +package com.crawlful.hub.api.controllers; + +import com.crawlful.hub.service.ReportService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/v1/reports") +public class ReportController { + @Autowired + private ReportService reportService; + + @GetMapping("/sales") + public ResponseEntity generateSalesReport(@RequestParam String tenantId, @RequestParam String startDate, @RequestParam String endDate) { + try { + if (tenantId == null || startDate == null || endDate == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing required parameters"), HttpStatus.BAD_REQUEST); + } + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + Date start = sdf.parse(startDate); + Date end = sdf.parse(endDate); + + Map salesReport = reportService.generateSalesReport(tenantId, start, end); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", salesReport); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (ParseException e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", "Invalid date format. Use yyyy-MM-dd"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/inventory") + public ResponseEntity generateInventoryReport(@RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Map inventoryReport = reportService.generateInventoryReport(tenantId); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", inventoryReport); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/user") + public ResponseEntity generateUserReport(@RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + Map userReport = reportService.generateUserReport(tenantId); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", userReport); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/payment") + public ResponseEntity generatePaymentReport(@RequestParam String tenantId, @RequestParam String startDate, @RequestParam String endDate) { + try { + if (tenantId == null || startDate == null || endDate == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing required parameters"), HttpStatus.BAD_REQUEST); + } + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + Date start = sdf.parse(startDate); + Date end = sdf.parse(endDate); + + Map paymentReport = reportService.generatePaymentReport(tenantId, start, end); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", paymentReport); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (ParseException e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", "Invalid date format. Use yyyy-MM-dd"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/logistics") + public ResponseEntity generateLogisticsReport(@RequestParam String tenantId, @RequestParam String startDate, @RequestParam String endDate) { + try { + if (tenantId == null || startDate == null || endDate == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing required parameters"), HttpStatus.BAD_REQUEST); + } + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + Date start = sdf.parse(startDate); + Date end = sdf.parse(endDate); + + Map logisticsReport = reportService.generateLogisticsReport(tenantId, start, end); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", logisticsReport); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (ParseException e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", "Invalid date format. Use yyyy-MM-dd"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PostMapping("/custom") + public ResponseEntity generateCustomReport(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + // 处理日期参数 + if (request.containsKey("startDate") && request.get("startDate") instanceof String) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + try { + request.put("startDate", sdf.parse((String) request.get("startDate"))); + } catch (ParseException e) { + return new ResponseEntity<>(Map.of("success", false, "error", "Invalid startDate format. Use yyyy-MM-dd"), HttpStatus.BAD_REQUEST); + } + } + if (request.containsKey("endDate") && request.get("endDate") instanceof String) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + try { + request.put("endDate", sdf.parse((String) request.get("endDate"))); + } catch (ParseException e) { + return new ResponseEntity<>(Map.of("success", false, "error", "Invalid endDate format. Use yyyy-MM-dd"), HttpStatus.BAD_REQUEST); + } + } + + Map customReport = reportService.generateCustomReport(tenantId, request); + Map response = new HashMap<>(); + if (customReport.containsKey("error")) { + response.put("success", false); + response.put("error", customReport.get("error")); + return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); + } else { + response.put("success", true); + response.put("data", customReport); + return new ResponseEntity<>(response, HttpStatus.OK); + } + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/crawlful/hub/api/controllers/UserController.java b/src/main/java/com/crawlful/hub/api/controllers/UserController.java new file mode 100644 index 0000000..2beeb07 --- /dev/null +++ b/src/main/java/com/crawlful/hub/api/controllers/UserController.java @@ -0,0 +1,128 @@ +package com.crawlful.hub.api.controllers; + +import com.crawlful.hub.model.User; +import com.crawlful.hub.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/v1/user") +public class UserController { + @Autowired + private UserService userService; + + @PostMapping + public ResponseEntity create(@RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + User user = userService.create(tenantId, request); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", Map.of("id", user.getId())); + return new ResponseEntity<>(response, HttpStatus.CREATED); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping + public ResponseEntity getAll(@RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + List users = userService.getAll(tenantId); + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", users); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @GetMapping("/{id}") + public ResponseEntity getById(@PathVariable Long id, @RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + User user = userService.getById(tenantId, id); + if (user == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "User not found"), HttpStatus.NOT_FOUND); + } + + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", user); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @PutMapping("/{id}") + public ResponseEntity update(@PathVariable Long id, @RequestBody Map request) { + try { + String tenantId = (String) request.get("tenantId"); + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + User user = userService.update(tenantId, id, request); + if (user == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "User not found"), HttpStatus.NOT_FOUND); + } + + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", user); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable Long id, @RequestParam String tenantId) { + try { + if (tenantId == null) { + return new ResponseEntity<>(Map.of("success", false, "error", "Missing tenantId"), HttpStatus.BAD_REQUEST); + } + + userService.delete(tenantId, id); + Map response = new HashMap<>(); + response.put("success", true); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("error", e.getMessage()); + return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/crawlful/hub/config/CacheConfig.java b/src/main/java/com/crawlful/hub/config/CacheConfig.java new file mode 100644 index 0000000..f452315 --- /dev/null +++ b/src/main/java/com/crawlful/hub/config/CacheConfig.java @@ -0,0 +1,40 @@ +package com.crawlful.hub.config; + +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import java.time.Duration; + +@Configuration +@EnableCaching +public class CacheConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + return template; + } + + @Bean + public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(Duration.ofMinutes(10)) + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); + + return RedisCacheManager.builder(connectionFactory) + .cacheDefaults(config) + .build(); + } +} diff --git a/src/main/java/com/crawlful/hub/config/InternationalizationConfig.java b/src/main/java/com/crawlful/hub/config/InternationalizationConfig.java new file mode 100644 index 0000000..034974a --- /dev/null +++ b/src/main/java/com/crawlful/hub/config/InternationalizationConfig.java @@ -0,0 +1,44 @@ +package com.crawlful.hub.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ResourceBundleMessageSource; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; + +import java.util.Locale; + +@Configuration +public class InternationalizationConfig implements WebMvcConfigurer { + + @Bean + public ResourceBundleMessageSource messageSource() { + ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); + messageSource.setBasename("i18n/messages"); + messageSource.setDefaultEncoding("UTF-8"); + messageSource.setUseCodeAsDefaultMessage(true); + return messageSource; + } + + @Bean + public LocaleResolver localeResolver() { + AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver(); + resolver.setDefaultLocale(Locale.US); + return resolver; + } + + @Bean + public LocaleChangeInterceptor localeChangeInterceptor() { + LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor(); + interceptor.setParamName("lang"); + return interceptor; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(localeChangeInterceptor()); + } +} diff --git a/src/main/java/com/crawlful/hub/config/OpenApiConfig.java b/src/main/java/com/crawlful/hub/config/OpenApiConfig.java new file mode 100644 index 0000000..41090f8 --- /dev/null +++ b/src/main/java/com/crawlful/hub/config/OpenApiConfig.java @@ -0,0 +1,28 @@ +package com.crawlful.hub.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.servers.Server; + +import java.util.List; + +@Configuration +public class OpenApiConfig { + + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .info(new Info() + .title("Crawlful Hub API") + .version("1.0") + .description("Crawlful Hub 后端 API 文档") + .license(new License().name("MIT").url("https://opensource.org/licenses/MIT"))) + .servers(List.of( + new Server().url("http://localhost:3001").description("Local Server") + )); + } +} diff --git a/src/main/java/com/crawlful/hub/config/RateLimitFilter.java b/src/main/java/com/crawlful/hub/config/RateLimitFilter.java new file mode 100644 index 0000000..1095341 --- /dev/null +++ b/src/main/java/com/crawlful/hub/config/RateLimitFilter.java @@ -0,0 +1,92 @@ +package com.crawlful.hub.config; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +@Component +public class RateLimitFilter implements Filter { + + private static final int MAX_REQUESTS_PER_MINUTE = 60; + private static final Map requestRates = new ConcurrentHashMap<>(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + String clientIp = getClientIp(httpRequest); + String key = clientIp + ":" + httpRequest.getRequestURI(); + + RequestRate rate = requestRates.computeIfAbsent(key, k -> new RequestRate()); + + synchronized (rate) { + long currentTime = System.currentTimeMillis(); + if (currentTime - rate.getLastResetTime() > 60000) { + rate.reset(); + } + + if (rate.getCount() >= MAX_REQUESTS_PER_MINUTE) { + httpResponse.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS); + httpResponse.getWriter().write("Too many requests, please try again later"); + return; + } + + rate.increment(); + } + + chain.doFilter(request, response); + } + + @Override + public void destroy() { + } + + private String getClientIp(HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + if (ip == null || ip.isEmpty()) { + ip = request.getHeader("X-Real-IP"); + } + if (ip == null || ip.isEmpty()) { + ip = request.getRemoteAddr(); + } + return ip; + } + + private static class RequestRate { + private final AtomicInteger count = new AtomicInteger(0); + private long lastResetTime = System.currentTimeMillis(); + + public int getCount() { + return count.get(); + } + + public void increment() { + count.incrementAndGet(); + } + + public void reset() { + count.set(0); + lastResetTime = System.currentTimeMillis(); + } + + public long getLastResetTime() { + return lastResetTime; + } + } +} diff --git a/src/main/java/com/crawlful/hub/config/SecurityConfig.java b/src/main/java/com/crawlful/hub/config/SecurityConfig.java new file mode 100644 index 0000000..6e0abcc --- /dev/null +++ b/src/main/java/com/crawlful/hub/config/SecurityConfig.java @@ -0,0 +1,36 @@ +package com.crawlful.hub.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .addFilterBefore(new RateLimitFilter(), UsernamePasswordAuthenticationFilter.class) + .csrf(csrf -> csrf + .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) + ) + .authorizeRequests() + .requestMatchers("/v1/auth/**").permitAll() + .requestMatchers("/api-docs/**").permitAll() + .requestMatchers("/swagger-ui/**").permitAll() + .requestMatchers("/v1/monitoring/**").permitAll() + .anyRequest().authenticated(); + + return http.build(); + } +} diff --git a/src/main/java/com/crawlful/hub/exception/BusinessException.java b/src/main/java/com/crawlful/hub/exception/BusinessException.java new file mode 100644 index 0000000..171ae44 --- /dev/null +++ b/src/main/java/com/crawlful/hub/exception/BusinessException.java @@ -0,0 +1,20 @@ +package com.crawlful.hub.exception; + +public class BusinessException extends RuntimeException { + private String errorCode; + private int statusCode; + + public BusinessException(String message, String errorCode, int statusCode) { + super(message); + this.errorCode = errorCode; + this.statusCode = statusCode; + } + + public String getErrorCode() { + return errorCode; + } + + public int getStatusCode() { + return statusCode; + } +} diff --git a/src/main/java/com/crawlful/hub/exception/GlobalExceptionHandler.java b/src/main/java/com/crawlful/hub/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..1df9e6c --- /dev/null +++ b/src/main/java/com/crawlful/hub/exception/GlobalExceptionHandler.java @@ -0,0 +1,58 @@ +package com.crawlful.hub.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; + +import java.util.HashMap; +import java.util.Map; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(BusinessException.class) + public ResponseEntity handleBusinessException(BusinessException ex, WebRequest request) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("error", ex.getMessage()); + errorResponse.put("errorCode", ex.getErrorCode()); + errorResponse.put("status", ex.getStatusCode()); + + return new ResponseEntity<>(errorResponse, HttpStatus.valueOf(ex.getStatusCode())); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGlobalException(Exception ex, WebRequest request) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("error", ex.getMessage()); + errorResponse.put("errorCode", "INTERNAL_SERVER_ERROR"); + errorResponse.put("status", 500); + + return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException ex, WebRequest request) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("error", ex.getMessage()); + errorResponse.put("errorCode", "BAD_REQUEST"); + errorResponse.put("status", 400); + + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(NullPointerException.class) + public ResponseEntity handleNullPointerException(NullPointerException ex, WebRequest request) { + Map errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("error", "Null pointer exception: " + ex.getMessage()); + errorResponse.put("errorCode", "INTERNAL_SERVER_ERROR"); + errorResponse.put("status", 500); + + return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/src/main/java/com/crawlful/hub/model/Alert.java b/src/main/java/com/crawlful/hub/model/Alert.java new file mode 100644 index 0000000..274506d --- /dev/null +++ b/src/main/java/com/crawlful/hub/model/Alert.java @@ -0,0 +1,131 @@ +package com.crawlful.hub.model; + +import jakarta.persistence.*; +import java.util.Date; + +@Entity +@Table(name = "cf_alert") +public class Alert { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "tenant_id") + private String tenantId; + + @Column(name = "alert_type") + private String alertType; + + @Column(name = "severity") + private String severity; + + @Column(name = "message") + private String message; + + @Column(name = "status") + private String status; + + @Column(name = "source") + private String source; + + @Column(name = "threshold") + private String threshold; + + @Column(name = "actual_value") + private String actualValue; + + @Column(name = "created_at") + private Date createdAt; + + @Column(name = "resolved_at") + private Date resolvedAt; + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTenantId() { + return tenantId; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getAlertType() { + return alertType; + } + + public void setAlertType(String alertType) { + this.alertType = alertType; + } + + public String getSeverity() { + return severity; + } + + public void setSeverity(String severity) { + this.severity = severity; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getThreshold() { + return threshold; + } + + public void setThreshold(String threshold) { + this.threshold = threshold; + } + + public String getActualValue() { + return actualValue; + } + + public void setActualValue(String actualValue) { + this.actualValue = actualValue; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public Date getResolvedAt() { + return resolvedAt; + } + + public void setResolvedAt(Date resolvedAt) { + this.resolvedAt = resolvedAt; + } +} diff --git a/src/main/java/com/crawlful/hub/model/Audit.java b/src/main/java/com/crawlful/hub/model/Audit.java new file mode 100644 index 0000000..22e5e37 --- /dev/null +++ b/src/main/java/com/crawlful/hub/model/Audit.java @@ -0,0 +1,131 @@ +package com.crawlful.hub.model; + +import jakarta.persistence.*; +import java.util.Date; + +@Entity +@Table(name = "cf_audit") +public class Audit { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "tenant_id") + private String tenantId; + + @Column(name = "shop_id") + private String shopId; + + @Column(name = "user_id") + private Long userId; + + @Column(name = "action") + private String action; + + @Column(name = "resource_type") + private String resourceType; + + @Column(name = "resource_id") + private String resourceId; + + @Column(name = "ip_address") + private String ipAddress; + + @Column(name = "user_agent") + private String userAgent; + + @Column(name = "details") + private String details; + + @Column(name = "created_at") + private Date createdAt; + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTenantId() { + return tenantId; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getShopId() { + return shopId; + } + + public void setShopId(String shopId) { + this.shopId = shopId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getResourceType() { + return resourceType; + } + + public void setResourceType(String resourceType) { + this.resourceType = resourceType; + } + + public String getResourceId() { + return resourceId; + } + + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + public String getIpAddress() { + return ipAddress; + } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public String getUserAgent() { + return userAgent; + } + + public void setUserAgent(String userAgent) { + this.userAgent = userAgent; + } + + public String getDetails() { + return details; + } + + public void setDetails(String details) { + this.details = details; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } +} diff --git a/src/main/java/com/crawlful/hub/model/Config.java b/src/main/java/com/crawlful/hub/model/Config.java new file mode 100644 index 0000000..9df7131 --- /dev/null +++ b/src/main/java/com/crawlful/hub/model/Config.java @@ -0,0 +1,109 @@ +package com.crawlful.hub.model; + +import jakarta.persistence.*; +import java.util.Date; + +@Entity +@Table(name = "cf_config") +public class Config { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "tenant_id") + private String tenantId; + + @Column(name = "shop_id") + private String shopId; + + @Column(name = "config_key", nullable = false) + private String configKey; + + @Column(name = "config_value", nullable = false) + private String configValue; + + @Column(name = "config_type") + private String configType; + + @Column(name = "description") + private String description; + + @Column(name = "created_at") + private Date createdAt; + + @Column(name = "updated_at") + private Date updatedAt; + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTenantId() { + return tenantId; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getShopId() { + return shopId; + } + + public void setShopId(String shopId) { + this.shopId = shopId; + } + + public String getConfigKey() { + return configKey; + } + + public void setConfigKey(String configKey) { + this.configKey = configKey; + } + + public String getConfigValue() { + return configValue; + } + + public void setConfigValue(String configValue) { + this.configValue = configValue; + } + + public String getConfigType() { + return configType; + } + + public void setConfigType(String configType) { + this.configType = configType; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/src/main/java/com/crawlful/hub/model/Logistics.java b/src/main/java/com/crawlful/hub/model/Logistics.java new file mode 100644 index 0000000..eb37016 --- /dev/null +++ b/src/main/java/com/crawlful/hub/model/Logistics.java @@ -0,0 +1,131 @@ +package com.crawlful.hub.model; + +import jakarta.persistence.*; +import java.util.Date; + +@Entity +@Table(name = "cf_logistics") +public class Logistics { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "tenant_id", nullable = false) + private String tenantId; + + @Column(name = "order_id") + private Long orderId; + + @Column(name = "shipping_method") + private String shippingMethod; + + @Column(name = "tracking_number") + private String trackingNumber; + + @Column(name = "carrier") + private String carrier; + + @Column(name = "status") + private String status; + + @Column(name = "estimated_delivery_date") + private Date estimatedDeliveryDate; + + @Column(name = "actual_delivery_date") + private Date actualDeliveryDate; + + @Column(name = "created_at") + private Date createdAt; + + @Column(name = "updated_at") + private Date updatedAt; + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTenantId() { + return tenantId; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public Long getOrderId() { + return orderId; + } + + public void setOrderId(Long orderId) { + this.orderId = orderId; + } + + public String getShippingMethod() { + return shippingMethod; + } + + public void setShippingMethod(String shippingMethod) { + this.shippingMethod = shippingMethod; + } + + public String getTrackingNumber() { + return trackingNumber; + } + + public void setTrackingNumber(String trackingNumber) { + this.trackingNumber = trackingNumber; + } + + public String getCarrier() { + return carrier; + } + + public void setCarrier(String carrier) { + this.carrier = carrier; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public Date getEstimatedDeliveryDate() { + return estimatedDeliveryDate; + } + + public void setEstimatedDeliveryDate(Date estimatedDeliveryDate) { + this.estimatedDeliveryDate = estimatedDeliveryDate; + } + + public Date getActualDeliveryDate() { + return actualDeliveryDate; + } + + public void setActualDeliveryDate(Date actualDeliveryDate) { + this.actualDeliveryDate = actualDeliveryDate; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/src/main/java/com/crawlful/hub/model/Order.java b/src/main/java/com/crawlful/hub/model/Order.java new file mode 100644 index 0000000..bc86ecf --- /dev/null +++ b/src/main/java/com/crawlful/hub/model/Order.java @@ -0,0 +1,164 @@ +package com.crawlful.hub.model; + +import jakarta.persistence.*; +import java.util.Date; + +@Entity +@Table(name = "cf_order") +public class Order { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "tenant_id", nullable = false) + private String tenantId; + + @Column(name = "shop_id") + private String shopId; + + @Column(name = "platform") + private String platform; + + @Column(name = "platform_order_id") + private String platformOrderId; + + @Column(name = "status") + private String status; + + @Column(name = "total_amount", precision = 10, scale = 2) + private Double totalAmount; + + @Column(name = "currency") + private String currency; + + @Column(name = "customer_info", columnDefinition = "JSON") + private String customerInfo; + + @Column(name = "items", columnDefinition = "JSON") + private String items; + + @Column(name = "shipping_address", columnDefinition = "JSON") + private String shippingAddress; + + @Column(name = "tracking_number") + private String trackingNumber; + + @Column(name = "created_at") + private Date createdAt; + + @Column(name = "updated_at") + private Date updatedAt; + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTenantId() { + return tenantId; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getShopId() { + return shopId; + } + + public void setShopId(String shopId) { + this.shopId = shopId; + } + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + public String getPlatformOrderId() { + return platformOrderId; + } + + public void setPlatformOrderId(String platformOrderId) { + this.platformOrderId = platformOrderId; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public Double getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(Double totalAmount) { + this.totalAmount = totalAmount; + } + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } + + public String getCustomerInfo() { + return customerInfo; + } + + public void setCustomerInfo(String customerInfo) { + this.customerInfo = customerInfo; + } + + public String getItems() { + return items; + } + + public void setItems(String items) { + this.items = items; + } + + public String getShippingAddress() { + return shippingAddress; + } + + public void setShippingAddress(String shippingAddress) { + this.shippingAddress = shippingAddress; + } + + public String getTrackingNumber() { + return trackingNumber; + } + + public void setTrackingNumber(String trackingNumber) { + this.trackingNumber = trackingNumber; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/src/main/java/com/crawlful/hub/model/Payment.java b/src/main/java/com/crawlful/hub/model/Payment.java new file mode 100644 index 0000000..3e50a5b --- /dev/null +++ b/src/main/java/com/crawlful/hub/model/Payment.java @@ -0,0 +1,120 @@ +package com.crawlful.hub.model; + +import jakarta.persistence.*; +import java.util.Date; + +@Entity +@Table(name = "cf_payment") +public class Payment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "tenant_id", nullable = false) + private String tenantId; + + @Column(name = "order_id") + private Long orderId; + + @Column(name = "payment_method") + private String paymentMethod; + + @Column(name = "amount", precision = 10, scale = 2) + private Double amount; + + @Column(name = "currency") + private String currency; + + @Column(name = "status") + private String status; + + @Column(name = "transaction_id") + private String transactionId; + + @Column(name = "created_at") + private Date createdAt; + + @Column(name = "updated_at") + private Date updatedAt; + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTenantId() { + return tenantId; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public Long getOrderId() { + return orderId; + } + + public void setOrderId(Long orderId) { + this.orderId = orderId; + } + + public String getPaymentMethod() { + return paymentMethod; + } + + public void setPaymentMethod(String paymentMethod) { + this.paymentMethod = paymentMethod; + } + + public Double getAmount() { + return amount; + } + + public void setAmount(Double amount) { + this.amount = amount; + } + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/src/main/java/com/crawlful/hub/model/Product.java b/src/main/java/com/crawlful/hub/model/Product.java new file mode 100644 index 0000000..88e648a --- /dev/null +++ b/src/main/java/com/crawlful/hub/model/Product.java @@ -0,0 +1,209 @@ +package com.crawlful.hub.model; + +import jakarta.persistence.*; +import java.util.Date; +import java.util.Map; + +@Entity +@Table(name = "cf_product") +public class Product { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "tenant_id", nullable = false) + private String tenantId; + + @Column(name = "shop_id") + private String shopId; + + @Column(nullable = false) + private String title; + + @Column(columnDefinition = "TEXT") + private String description; + + @Column(name = "main_image") + private String mainImage; + + @Column(name = "platform") + private String platform; + + @Column(name = "platform_product_id") + private String platformProductId; + + @Column(name = "price", precision = 10, scale = 2) + private Double price; + + @Column(name = "cost_price", precision = 10, scale = 2) + private Double costPrice; + + @Column(name = "quantity") + private Integer quantity; + + @Column(name = "status") + private String status; + + @Column(name = "phash") + private String phash; + + @Column(name = "semantic_hash") + private String semanticHash; + + @Column(name = "vector_embedding", columnDefinition = "TEXT") + private String vectorEmbedding; + + @Column(name = "attributes", columnDefinition = "JSON") + private String attributes; + + @Column(name = "created_at") + private Date createdAt; + + @Column(name = "updated_at") + private Date updatedAt; + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTenantId() { + return tenantId; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getShopId() { + return shopId; + } + + public void setShopId(String shopId) { + this.shopId = shopId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getMainImage() { + return mainImage; + } + + public void setMainImage(String mainImage) { + this.mainImage = mainImage; + } + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + public String getPlatformProductId() { + return platformProductId; + } + + public void setPlatformProductId(String platformProductId) { + this.platformProductId = platformProductId; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + public Double getCostPrice() { + return costPrice; + } + + public void setCostPrice(Double costPrice) { + this.costPrice = costPrice; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getPhash() { + return phash; + } + + public void setPhash(String phash) { + this.phash = phash; + } + + public String getSemanticHash() { + return semanticHash; + } + + public void setSemanticHash(String semanticHash) { + this.semanticHash = semanticHash; + } + + public String getVectorEmbedding() { + return vectorEmbedding; + } + + public void setVectorEmbedding(String vectorEmbedding) { + this.vectorEmbedding = vectorEmbedding; + } + + public String getAttributes() { + return attributes; + } + + public void setAttributes(String attributes) { + this.attributes = attributes; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/src/main/java/com/crawlful/hub/model/User.java b/src/main/java/com/crawlful/hub/model/User.java new file mode 100644 index 0000000..f37a8ae --- /dev/null +++ b/src/main/java/com/crawlful/hub/model/User.java @@ -0,0 +1,98 @@ +package com.crawlful.hub.model; + +import jakarta.persistence.*; +import java.util.Date; + +@Entity +@Table(name = "cf_user") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "tenant_id", nullable = false) + private String tenantId; + + @Column(nullable = false, unique = true) + private String username; + + @Column(nullable = false) + private String password; + + @Column(nullable = false, unique = true) + private String email; + + @Column + private String role; + + @Column(name = "created_at") + private Date createdAt; + + @Column(name = "updated_at") + private Date updatedAt; + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTenantId() { + return tenantId; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/src/main/java/com/crawlful/hub/service/AlertRepository.java b/src/main/java/com/crawlful/hub/service/AlertRepository.java new file mode 100644 index 0000000..dc1008c --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/AlertRepository.java @@ -0,0 +1,15 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.Alert; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Date; + +public interface AlertRepository extends JpaRepository { + List findByTenantId(String tenantId); + List findByTenantIdAndStatus(String tenantId, String status); + List findByTenantIdAndSeverity(String tenantId, String severity); + List findByTenantIdAndAlertType(String tenantId, String alertType); + List findByTenantIdAndCreatedAtBetween(String tenantId, Date startDate, Date endDate); +} diff --git a/src/main/java/com/crawlful/hub/service/AlertService.java b/src/main/java/com/crawlful/hub/service/AlertService.java new file mode 100644 index 0000000..5e887f3 --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/AlertService.java @@ -0,0 +1,110 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.Alert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Service +public class AlertService { + @Autowired + private AlertRepository alertRepository; + + public Alert createAlert(Map alertData) { + Alert alert = new Alert(); + alert.setTenantId((String) alertData.get("tenantId")); + alert.setAlertType((String) alertData.get("alertType")); + alert.setSeverity((String) alertData.get("severity")); + alert.setMessage((String) alertData.get("message")); + alert.setStatus((String) alertData.get("status")); + alert.setSource((String) alertData.get("source")); + alert.setThreshold((String) alertData.get("threshold")); + alert.setActualValue((String) alertData.get("actualValue")); + alert.setCreatedAt(new Date()); + return alertRepository.save(alert); + } + + public List getAlerts(String tenantId, Map filters) { + String status = (String) filters.get("status"); + String severity = (String) filters.get("severity"); + String alertType = (String) filters.get("alertType"); + Date startDate = (Date) filters.get("startDate"); + Date endDate = (Date) filters.get("endDate"); + + if (startDate != null && endDate != null) { + return alertRepository.findByTenantIdAndCreatedAtBetween(tenantId, startDate, endDate); + } else if (status != null) { + return alertRepository.findByTenantIdAndStatus(tenantId, status); + } else if (severity != null) { + return alertRepository.findByTenantIdAndSeverity(tenantId, severity); + } else if (alertType != null) { + return alertRepository.findByTenantIdAndAlertType(tenantId, alertType); + } else { + return alertRepository.findByTenantId(tenantId); + } + } + + public Alert getAlertById(String tenantId, Long id) { + Alert alert = alertRepository.findById(id).orElse(null); + if (alert != null && alert.getTenantId().equals(tenantId)) { + return alert; + } + return null; + } + + public void resolveAlert(String tenantId, Long id) { + Alert alert = getAlertById(tenantId, id); + if (alert != null) { + alert.setStatus("RESOLVED"); + alert.setResolvedAt(new Date()); + alertRepository.save(alert); + } + } + + public void updateAlertStatus(String tenantId, Long id, String status) { + Alert alert = getAlertById(tenantId, id); + if (alert != null) { + alert.setStatus(status); + if ("RESOLVED".equals(status)) { + alert.setResolvedAt(new Date()); + } + alertRepository.save(alert); + } + } + + public Map getAlertStats(String tenantId, Date startDate, Date endDate) { + List alerts = alertRepository.findByTenantIdAndCreatedAtBetween(tenantId, startDate, endDate); + int totalAlerts = alerts.size(); + + // 按严重程度统计 + Map severityStats = new java.util.HashMap<>(); + // 按状态统计 + Map statusStats = new java.util.HashMap<>(); + // 按类型统计 + Map typeStats = new java.util.HashMap<>(); + + for (var alert : alerts) { + severityStats.put(alert.getSeverity(), severityStats.getOrDefault(alert.getSeverity(), 0) + 1); + statusStats.put(alert.getStatus(), statusStats.getOrDefault(alert.getStatus(), 0) + 1); + typeStats.put(alert.getAlertType(), typeStats.getOrDefault(alert.getAlertType(), 0) + 1); + } + + return Map.of( + "totalAlerts", totalAlerts, + "severityStats", severityStats, + "statusStats", statusStats, + "typeStats", typeStats, + "startDate", startDate, + "endDate", endDate + ); + } + + public void checkThresholds(String tenantId) { + // 模拟检查阈值 + // 这里可以添加具体的阈值检查逻辑 + // 例如检查系统负载、数据库连接数、缓存使用率等 + } +} diff --git a/src/main/java/com/crawlful/hub/service/AuditRepository.java b/src/main/java/com/crawlful/hub/service/AuditRepository.java new file mode 100644 index 0000000..3f6d5f1 --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/AuditRepository.java @@ -0,0 +1,16 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.Audit; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Date; + +public interface AuditRepository extends JpaRepository { + List findByTenantId(String tenantId); + List findByTenantIdAndShopId(String tenantId, String shopId); + List findByTenantIdAndUserId(String tenantId, Long userId); + List findByTenantIdAndAction(String tenantId, String action); + List findByTenantIdAndResourceType(String tenantId, String resourceType); + List findByTenantIdAndCreatedAtBetween(String tenantId, Date startDate, Date endDate); +} diff --git a/src/main/java/com/crawlful/hub/service/AuditService.java b/src/main/java/com/crawlful/hub/service/AuditService.java new file mode 100644 index 0000000..a482dd5 --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/AuditService.java @@ -0,0 +1,110 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.Audit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Service +public class AuditService { + @Autowired + private AuditRepository auditRepository; + + public Audit createAudit(Map auditData) { + Audit audit = new Audit(); + audit.setTenantId((String) auditData.get("tenantId")); + audit.setShopId((String) auditData.get("shopId")); + audit.setUserId((Long) auditData.get("userId")); + audit.setAction((String) auditData.get("action")); + audit.setResourceType((String) auditData.get("resourceType")); + audit.setResourceId((String) auditData.get("resourceId")); + audit.setIpAddress((String) auditData.get("ipAddress")); + audit.setUserAgent((String) auditData.get("userAgent")); + audit.setDetails((String) auditData.get("details")); + audit.setCreatedAt(new Date()); + return auditRepository.save(audit); + } + + public List getAudits(String tenantId, Map filters) { + String shopId = (String) filters.get("shopId"); + Long userId = (Long) filters.get("userId"); + String action = (String) filters.get("action"); + String resourceType = (String) filters.get("resourceType"); + Date startDate = (Date) filters.get("startDate"); + Date endDate = (Date) filters.get("endDate"); + + if (startDate != null && endDate != null) { + return auditRepository.findByTenantIdAndCreatedAtBetween(tenantId, startDate, endDate); + } else if (shopId != null) { + return auditRepository.findByTenantIdAndShopId(tenantId, shopId); + } else if (userId != null) { + return auditRepository.findByTenantIdAndUserId(tenantId, userId); + } else if (action != null) { + return auditRepository.findByTenantIdAndAction(tenantId, action); + } else if (resourceType != null) { + return auditRepository.findByTenantIdAndResourceType(tenantId, resourceType); + } else { + return auditRepository.findByTenantId(tenantId); + } + } + + public Map getAuditStats(String tenantId, Date startDate, Date endDate) { + // 生成审计统计数据 + List audits = auditRepository.findByTenantIdAndCreatedAtBetween(tenantId, startDate, endDate); + int totalAudits = audits.size(); + + // 按操作类型统计 + Map actionStats = new java.util.HashMap<>(); + // 按资源类型统计 + Map resourceTypeStats = new java.util.HashMap<>(); + // 按用户统计 + Map userStats = new java.util.HashMap<>(); + + for (var audit : audits) { + actionStats.put(audit.getAction(), actionStats.getOrDefault(audit.getAction(), 0) + 1); + resourceTypeStats.put(audit.getResourceType(), resourceTypeStats.getOrDefault(audit.getResourceType(), 0) + 1); + if (audit.getUserId() != null) { + userStats.put(audit.getUserId(), userStats.getOrDefault(audit.getUserId(), 0) + 1); + } + } + + return Map.of( + "totalAudits", totalAudits, + "actionStats", actionStats, + "resourceTypeStats", resourceTypeStats, + "userStats", userStats, + "startDate", startDate, + "endDate", endDate + ); + } + + public List getRecentAudits(String tenantId, int limit) { + // 获取最近的审计日志 + List allAudits = auditRepository.findByTenantId(tenantId); + int endIndex = Math.min(limit, allAudits.size()); + return allAudits.subList(0, endIndex); + } + + public Map searchAudits(String tenantId, String keyword) { + // 模拟搜索审计日志 + List allAudits = auditRepository.findByTenantId(tenantId); + List filteredAudits = new java.util.ArrayList<>(); + + for (var audit : allAudits) { + if (audit.getAction().contains(keyword) || + (audit.getResourceType() != null && audit.getResourceType().contains(keyword)) || + (audit.getDetails() != null && audit.getDetails().contains(keyword))) { + filteredAudits.add(audit); + } + } + + return Map.of( + "keyword", keyword, + "results", filteredAudits, + "totalResults", filteredAudits.size() + ); + } +} diff --git a/src/main/java/com/crawlful/hub/service/AuthService.java b/src/main/java/com/crawlful/hub/service/AuthService.java new file mode 100644 index 0000000..2cc4024 --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/AuthService.java @@ -0,0 +1,98 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.User; +import com.crawlful.hub.util.ValidationUtil; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.Date; + +@Service +public class AuthService { + @Autowired + private UserRepository userRepository; + + @Autowired + private BCryptPasswordEncoder passwordEncoder; + + @Value("${spring.security.jwt.secret}") + private String jwtSecret; + + @Value("${spring.security.jwt.expiration}") + private long jwtExpiration; + + public User register(String tenantId, String username, String password, String email, String role) { + // 验证输入数据 + if (tenantId == null || tenantId.isEmpty()) { + throw new RuntimeException("Tenant ID is required"); + } + if (!ValidationUtil.validateStringLength(username, 3, 50)) { + throw new RuntimeException("Username must be between 3 and 50 characters"); + } + if (!ValidationUtil.validatePassword(password)) { + throw new RuntimeException("Password must be at least 6 characters"); + } + if (!ValidationUtil.validateEmail(email)) { + throw new RuntimeException("Invalid email format"); + } + if (role == null || role.isEmpty()) { + throw new RuntimeException("Role is required"); + } + + // 检查用户名是否已存在 + if (userRepository.findByTenantIdAndUsername(tenantId, username) != null) { + throw new RuntimeException("Username already exists"); + } + + User user = new User(); + user.setTenantId(tenantId); + user.setUsername(username); + user.setPassword(passwordEncoder.encode(password)); + user.setEmail(email); + user.setRole(role); + user.setCreatedAt(new Date()); + user.setUpdatedAt(new Date()); + return userRepository.save(user); + } + + public String login(String tenantId, String username, String password) { + // 验证输入数据 + if (tenantId == null || tenantId.isEmpty()) { + throw new RuntimeException("Tenant ID is required"); + } + if (username == null || username.isEmpty()) { + throw new RuntimeException("Username is required"); + } + if (password == null || password.isEmpty()) { + throw new RuntimeException("Password is required"); + } + + User user = userRepository.findByTenantIdAndUsername(tenantId, username); + if (user == null || !passwordEncoder.matches(password, user.getPassword())) { + throw new RuntimeException("Invalid username or password"); + } + return generateToken(user); + } + + private String generateToken(User user) { + return Jwts.builder() + .setSubject(user.getUsername()) + .claim("tenantId", user.getTenantId()) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + jwtExpiration)) + .signWith(SignatureAlgorithm.HS512, jwtSecret) + .compact(); + } + + public User findByUsername(String tenantId, String username) { + return userRepository.findByTenantIdAndUsername(tenantId, username); + } + + public User findByUsername(String username) { + return userRepository.findByUsername(username); + } +} diff --git a/src/main/java/com/crawlful/hub/service/ConfigRepository.java b/src/main/java/com/crawlful/hub/service/ConfigRepository.java new file mode 100644 index 0000000..e88e3a8 --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/ConfigRepository.java @@ -0,0 +1,14 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.Config; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface ConfigRepository extends JpaRepository { + List findByTenantId(String tenantId); + List findByTenantIdAndShopId(String tenantId, String shopId); + Optional findByTenantIdAndConfigKey(String tenantId, String configKey); + Optional findByTenantIdAndShopIdAndConfigKey(String tenantId, String shopId, String configKey); +} diff --git a/src/main/java/com/crawlful/hub/service/ConfigService.java b/src/main/java/com/crawlful/hub/service/ConfigService.java new file mode 100644 index 0000000..94df0eb --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/ConfigService.java @@ -0,0 +1,104 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.Config; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Service +public class ConfigService { + @Autowired + private ConfigRepository configRepository; + + public Config createConfig(Map configData) { + Config config = new Config(); + config.setTenantId((String) configData.get("tenantId")); + config.setShopId((String) configData.get("shopId")); + config.setConfigKey((String) configData.get("configKey")); + config.setConfigValue((String) configData.get("configValue")); + config.setConfigType((String) configData.get("configType")); + config.setDescription((String) configData.get("description")); + config.setCreatedAt(new Date()); + config.setUpdatedAt(new Date()); + return configRepository.save(config); + } + + public List getConfigs(String tenantId, String shopId) { + if (shopId != null) { + return configRepository.findByTenantIdAndShopId(tenantId, shopId); + } else { + return configRepository.findByTenantId(tenantId); + } + } + + public Config getConfigByKey(String tenantId, String configKey, String shopId) { + if (shopId != null) { + Optional config = configRepository.findByTenantIdAndShopIdAndConfigKey(tenantId, shopId, configKey); + return config.orElse(null); + } else { + Optional config = configRepository.findByTenantIdAndConfigKey(tenantId, configKey); + return config.orElse(null); + } + } + + public void updateConfig(String tenantId, Long id, Map updateData) { + Config config = configRepository.findById(id).orElse(null); + if (config != null && config.getTenantId().equals(tenantId)) { + if (updateData.containsKey("configValue")) { + config.setConfigValue((String) updateData.get("configValue")); + } + if (updateData.containsKey("configType")) { + config.setConfigType((String) updateData.get("configType")); + } + if (updateData.containsKey("description")) { + config.setDescription((String) updateData.get("description")); + } + config.setUpdatedAt(new Date()); + configRepository.save(config); + } + } + + public void deleteConfig(String tenantId, Long id) { + Config config = configRepository.findById(id).orElse(null); + if (config != null && config.getTenantId().equals(tenantId)) { + configRepository.delete(config); + } + } + + public String getConfigValue(String tenantId, String configKey, String shopId) { + Config config = getConfigByKey(tenantId, configKey, shopId); + return config != null ? config.getConfigValue() : null; + } + + public Map getConfigValues(String tenantId, List configKeys, String shopId) { + Map configValues = new java.util.HashMap<>(); + for (String configKey : configKeys) { + Config config = getConfigByKey(tenantId, configKey, shopId); + if (config != null) { + configValues.put(configKey, config.getConfigValue()); + } + } + return configValues; + } + + public void batchUpdateConfigs(String tenantId, List> configs) { + for (Map configData : configs) { + String configKey = (String) configData.get("configKey"); + String shopId = (String) configData.get("shopId"); + Config existingConfig = getConfigByKey(tenantId, configKey, shopId); + + if (existingConfig != null) { + // Update existing config + updateConfig(tenantId, existingConfig.getId(), configData); + } else { + // Create new config + configData.put("tenantId", tenantId); + createConfig(configData); + } + } + } +} diff --git a/src/main/java/com/crawlful/hub/service/DataService.java b/src/main/java/com/crawlful/hub/service/DataService.java new file mode 100644 index 0000000..1915b96 --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/DataService.java @@ -0,0 +1,169 @@ +package com.crawlful.hub.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.*; + +@Service +public class DataService { + @Autowired + private UserRepository userRepository; + @Autowired + private ProductRepository productRepository; + @Autowired + private OrderRepository orderRepository; + @Autowired + private PaymentRepository paymentRepository; + @Autowired + private LogisticsRepository logisticsRepository; + + public Map getDashboardData(String tenantId) { + // 模拟仪表盘数据 + int totalUsers = userRepository.findByTenantId(tenantId).size(); + int totalProducts = productRepository.findByTenantId(tenantId).size(); + int totalOrders = orderRepository.findByTenantId(tenantId).size(); + int totalPayments = paymentRepository.findByTenantId(tenantId).size(); + + double totalRevenue = 0.0; + for (var order : orderRepository.findByTenantId(tenantId)) { + totalRevenue += order.getTotalAmount() != null ? order.getTotalAmount() : 0; + } + + return Map.of( + "totalUsers", totalUsers, + "totalProducts", totalProducts, + "totalOrders", totalOrders, + "totalPayments", totalPayments, + "totalRevenue", totalRevenue, + "recentOrders", orderRepository.findByTenantId(tenantId).stream().limit(5).toList(), + "topProducts", productRepository.findByTenantId(tenantId).stream().limit(5).toList() + ); + } + + public Map getSalesReport(String tenantId, Date startDate, Date endDate) { + // 模拟销售报表数据 + List> salesData = new ArrayList<>(); + for (var order : orderRepository.findByTenantId(tenantId)) { + if (order.getCreatedAt().after(startDate) && order.getCreatedAt().before(endDate)) { + salesData.add(Map.of( + "orderId", order.getId(), + "platform", order.getPlatform(), + "amount", order.getTotalAmount(), + "currency", order.getCurrency(), + "status", order.getStatus(), + "createdAt", order.getCreatedAt() + )); + } + } + + double totalSales = salesData.stream() + .mapToDouble(item -> (Double) item.get("amount")) + .sum(); + int totalOrders = salesData.size(); + double averageOrderValue = totalOrders > 0 ? totalSales / totalOrders : 0; + + return Map.of( + "salesData", salesData, + "totalSales", totalSales, + "totalOrders", totalOrders, + "averageOrderValue", averageOrderValue, + "startDate", startDate, + "endDate", endDate + ); + } + + public Map getInventoryReport(String tenantId) { + // 模拟库存报表数据 + List> inventoryData = new ArrayList<>(); + for (var product : productRepository.findByTenantId(tenantId)) { + inventoryData.add(Map.of( + "productId", product.getId(), + "title", product.getTitle(), + "platform", product.getPlatform(), + "quantity", product.getQuantity(), + "price", product.getPrice(), + "status", product.getStatus() + )); + } + + int totalProducts = inventoryData.size(); + int lowStockProducts = (int) inventoryData.stream() + .filter(item -> (Integer) item.get("quantity") < 10) + .count(); + int outOfStockProducts = (int) inventoryData.stream() + .filter(item -> (Integer) item.get("quantity") == 0) + .count(); + + return Map.of( + "inventoryData", inventoryData, + "totalProducts", totalProducts, + "lowStockProducts", lowStockProducts, + "outOfStockProducts", outOfStockProducts + ); + } + + public Map exportData(String tenantId, String dataType, String format) { + // 模拟数据导出 + List data = new ArrayList<>(); + switch (dataType) { + case "users": + data = userRepository.findByTenantId(tenantId); + break; + case "products": + data = productRepository.findByTenantId(tenantId); + break; + case "orders": + data = orderRepository.findByTenantId(tenantId); + break; + case "payments": + data = paymentRepository.findByTenantId(tenantId); + break; + case "logistics": + data = logisticsRepository.findByTenantId(tenantId); + break; + } + + // 模拟导出文件路径 + String filePath = "/exports/" + dataType + "_" + System.currentTimeMillis() + "." + format; + + return Map.of( + "success", true, + "dataType", dataType, + "format", format, + "filePath", filePath, + "recordCount", data.size() + ); + } + + public Map getPlatformPerformance(String tenantId) { + // 模拟平台性能数据 + Map> platformData = new HashMap<>(); + for (var order : orderRepository.findByTenantId(tenantId)) { + String platform = order.getPlatform(); + if (!platformData.containsKey(platform)) { + platformData.put(platform, Map.of( + "orders", 0, + "revenue", 0.0, + "avgOrderValue", 0.0 + )); + } + + Map currentData = platformData.get(platform); + int orders = (Integer) currentData.get("orders") + 1; + double revenue = (Double) currentData.get("revenue") + (order.getTotalAmount() != null ? order.getTotalAmount() : 0); + double avgOrderValue = revenue / orders; + + platformData.put(platform, Map.of( + "orders", orders, + "revenue", revenue, + "avgOrderValue", avgOrderValue + )); + } + + return Map.of( + "platformData", platformData, + "totalPlatforms", platformData.size() + ); + } +} diff --git a/src/main/java/com/crawlful/hub/service/LogisticsRepository.java b/src/main/java/com/crawlful/hub/service/LogisticsRepository.java new file mode 100644 index 0000000..2d3a23f --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/LogisticsRepository.java @@ -0,0 +1,13 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.Logistics; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface LogisticsRepository extends JpaRepository { + List findByTenantId(String tenantId); + List findByTenantIdAndOrderId(String tenantId, Long orderId); + List findByTenantIdAndStatus(String tenantId, String status); + Logistics findByTenantIdAndTrackingNumber(String tenantId, String trackingNumber); +} diff --git a/src/main/java/com/crawlful/hub/service/LogisticsService.java b/src/main/java/com/crawlful/hub/service/LogisticsService.java new file mode 100644 index 0000000..d856fb9 --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/LogisticsService.java @@ -0,0 +1,121 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.Logistics; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Service +public class LogisticsService { + @Autowired + private LogisticsRepository logisticsRepository; + + public Logistics createLogistics(Map logisticsData) { + Logistics logistics = new Logistics(); + logistics.setTenantId((String) logisticsData.get("tenantId")); + logistics.setOrderId((Long) logisticsData.get("orderId")); + logistics.setShippingMethod((String) logisticsData.get("shippingMethod")); + logistics.setTrackingNumber((String) logisticsData.get("trackingNumber")); + logistics.setCarrier((String) logisticsData.get("carrier")); + logistics.setStatus((String) logisticsData.get("status")); + logistics.setEstimatedDeliveryDate((Date) logisticsData.get("estimatedDeliveryDate")); + logistics.setCreatedAt(new Date()); + logistics.setUpdatedAt(new Date()); + return logisticsRepository.save(logistics); + } + + public List getLogistics(String tenantId, Map filters) { + String status = (String) filters.get("status"); + Long orderId = (Long) filters.get("orderId"); + + if (orderId != null) { + return logisticsRepository.findByTenantIdAndOrderId(tenantId, orderId); + } else if (status != null) { + return logisticsRepository.findByTenantIdAndStatus(tenantId, status); + } else { + return logisticsRepository.findByTenantId(tenantId); + } + } + + public Logistics getLogisticsById(String tenantId, Long id) { + Logistics logistics = logisticsRepository.findById(id).orElse(null); + if (logistics != null && logistics.getTenantId().equals(tenantId)) { + return logistics; + } + return null; + } + + public Logistics getLogisticsByTrackingNumber(String tenantId, String trackingNumber) { + return logisticsRepository.findByTenantIdAndTrackingNumber(tenantId, trackingNumber); + } + + public void updateLogistics(String tenantId, Long id, Map updateData) { + Logistics logistics = getLogisticsById(tenantId, id); + if (logistics != null) { + if (updateData.containsKey("status")) { + logistics.setStatus((String) updateData.get("status")); + if ("DELIVERED".equals(updateData.get("status"))) { + logistics.setActualDeliveryDate(new Date()); + } + } + if (updateData.containsKey("trackingNumber")) { + logistics.setTrackingNumber((String) updateData.get("trackingNumber")); + } + if (updateData.containsKey("carrier")) { + logistics.setCarrier((String) updateData.get("carrier")); + } + if (updateData.containsKey("estimatedDeliveryDate")) { + logistics.setEstimatedDeliveryDate((Date) updateData.get("estimatedDeliveryDate")); + } + logistics.setUpdatedAt(new Date()); + logisticsRepository.save(logistics); + } + } + + public Map trackLogistics(String tenantId, String trackingNumber) { + Logistics logistics = getLogisticsByTrackingNumber(tenantId, trackingNumber); + if (logistics != null) { + // 模拟物流跟踪信息 + return Map.of( + "success", true, + "trackingNumber", trackingNumber, + "carrier", logistics.getCarrier(), + "status", logistics.getStatus(), + "estimatedDeliveryDate", logistics.getEstimatedDeliveryDate(), + "actualDeliveryDate", logistics.getActualDeliveryDate(), + "updates", List.of( + Map.of("time", logistics.getCreatedAt(), "status", "ORDER_CREATED", "location", "Warehouse"), + Map.of("time", new Date(), "status", logistics.getStatus(), "location", "In Transit") + ) + ); + } + return Map.of("success", false, "error", "Tracking number not found"); + } + + public Map calculateShippingCost(String tenantId, Map shippingInfo) { + // 模拟运费计算 + Double weight = (Double) shippingInfo.get("weight"); + String destination = (String) shippingInfo.get("destination"); + String method = (String) shippingInfo.get("method"); + + double baseCost = 10.0; + double weightCost = weight * 2.0; + double destinationCost = destination.equals("International") ? 50.0 : 0.0; + double methodCost = method.equals("Express") ? 20.0 : 0.0; + + double totalCost = baseCost + weightCost + destinationCost + methodCost; + + return Map.of( + "success", true, + "baseCost", baseCost, + "weightCost", weightCost, + "destinationCost", destinationCost, + "methodCost", methodCost, + "totalCost", totalCost, + "currency", "USD" + ); + } +} diff --git a/src/main/java/com/crawlful/hub/service/MonitoringService.java b/src/main/java/com/crawlful/hub/service/MonitoringService.java new file mode 100644 index 0000000..90ca1dc --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/MonitoringService.java @@ -0,0 +1,142 @@ +package com.crawlful.hub.service; + +import org.springframework.stereotype.Service; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.OperatingSystemMXBean; +import java.util.HashMap; +import java.util.Map; + +@Service +public class MonitoringService { + + public Map getSystemHealth() { + // 获取系统健康状态 + Map health = new HashMap<>(); + + // 检查服务状态 + health.put("status", "UP"); + health.put("timestamp", System.currentTimeMillis()); + + // 获取系统信息 + OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); + health.put("os", Map.of( + "name", osBean.getName(), + "version", osBean.getVersion(), + "arch", osBean.getArch(), + "availableProcessors", osBean.getAvailableProcessors() + )); + + // 获取内存信息 + MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); + long heapUsed = memoryBean.getHeapMemoryUsage().getUsed(); + long heapMax = memoryBean.getHeapMemoryUsage().getMax(); + health.put("memory", Map.of( + "heapUsed", heapUsed, + "heapMax", heapMax, + "heapUsedPercent", (double) heapUsed / heapMax * 100 + )); + + return health; + } + + public Map getPerformanceMetrics() { + // 获取性能指标 + Map metrics = new HashMap<>(); + + // 模拟请求处理时间 + metrics.put("requestProcessingTime", 123); // 毫秒 + + // 模拟数据库查询时间 + metrics.put("databaseQueryTime", 45); // 毫秒 + + // 模拟缓存命中率 + metrics.put("cacheHitRate", 0.85); // 85% + + // 模拟系统负载 + OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); + double systemLoad = osBean.getSystemLoadAverage(); + metrics.put("systemLoad", systemLoad); + + return metrics; + } + + public Map getServiceStatus() { + // 获取服务状态 + Map status = new HashMap<>(); + + // 模拟各个服务的状态 + status.put("services", Map.of( + "authService", Map.of("status", "UP", "responseTime", 10), + "productService", Map.of("status", "UP", "responseTime", 15), + "orderService", Map.of("status", "UP", "responseTime", 20), + "paymentService", Map.of("status", "UP", "responseTime", 25), + "logisticsService", Map.of("status", "UP", "responseTime", 30), + "dataService", Map.of("status", "UP", "responseTime", 35), + "reportService", Map.of("status", "UP", "responseTime", 40), + "configService", Map.of("status", "UP", "responseTime", 12), + "auditService", Map.of("status", "UP", "responseTime", 18) + )); + + // 计算整体服务状态 + long upServices = status.get("services").toString().contains("UP") ? 9 : 0; + status.put("totalServices", 9); + status.put("upServices", upServices); + status.put("downServices", 9 - upServices); + + return status; + } + + public Map getDatabaseStatus() { + // 获取数据库状态 + Map dbStatus = new HashMap<>(); + + // 模拟数据库连接状态 + dbStatus.put("connectionStatus", "UP"); + dbStatus.put("connectionCount", 10); + dbStatus.put("maxConnections", 100); + + // 模拟数据库查询性能 + dbStatus.put("queryPerformance", Map.of( + "avgQueryTime", 15, // 毫秒 + "slowQueries", 2, + "totalQueries", 1000 + )); + + return dbStatus; + } + + public Map getCacheStatus() { + // 获取缓存状态 + Map cacheStatus = new HashMap<>(); + + // 模拟缓存使用情况 + cacheStatus.put("cacheSize", 500); + cacheStatus.put("maxCacheSize", 1000); + cacheStatus.put("hitRate", 0.85); + cacheStatus.put("missRate", 0.15); + + return cacheStatus; + } + + public Map getSystemStats() { + // 获取系统统计信息 + Map stats = new HashMap<>(); + + // 模拟系统统计数据 + stats.put("totalRequests", 10000); + stats.put("successfulRequests", 9800); + stats.put("failedRequests", 200); + stats.put("successRate", 0.98); + + // 模拟响应时间统计 + stats.put("responseTime", Map.of( + "avg", 123, + "min", 10, + "max", 500 + )); + + return stats; + } +} diff --git a/src/main/java/com/crawlful/hub/service/OrderRepository.java b/src/main/java/com/crawlful/hub/service/OrderRepository.java new file mode 100644 index 0000000..24fd2c7 --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/OrderRepository.java @@ -0,0 +1,21 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.Order; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Date; + +public interface OrderRepository extends JpaRepository { + List findByTenantId(String tenantId); + List findByTenantIdAndStatus(String tenantId, String status); + List findByTenantIdAndPlatform(String tenantId, String platform); + List findByTenantIdAndPlatformAndStatus(String tenantId, String platform, String status); + + @Query("SELECT p FROM Order p WHERE p.tenantId = :tenantId AND p.createdAt BETWEEN :startDate AND :endDate") + List findByTenantIdAndDateRange(@Param("tenantId") String tenantId, @Param("startDate") Date startDate, @Param("endDate") Date endDate); + + Order findByTenantIdAndPlatformOrderId(String tenantId, String platformOrderId); +} diff --git a/src/main/java/com/crawlful/hub/service/OrderService.java b/src/main/java/com/crawlful/hub/service/OrderService.java new file mode 100644 index 0000000..92e4131 --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/OrderService.java @@ -0,0 +1,252 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.Order; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.HashMap; + +@Service +public class OrderService { + @Autowired + private OrderRepository orderRepository; + + public Order createOrder(Map orderData) { + Order order = new Order(); + order.setTenantId((String) orderData.get("tenantId")); + order.setShopId((String) orderData.get("shopId")); + order.setPlatform((String) orderData.get("platform")); + order.setPlatformOrderId((String) orderData.get("platformOrderId")); + order.setStatus((String) orderData.get("status")); + order.setTotalAmount((Double) orderData.get("totalAmount")); + order.setCurrency((String) orderData.get("currency")); + order.setCustomerInfo((String) orderData.get("customerInfo")); + order.setItems((String) orderData.get("items")); + order.setShippingAddress((String) orderData.get("shippingAddress")); + order.setTrackingNumber((String) orderData.get("trackingNumber")); + order.setCreatedAt(new Date()); + order.setUpdatedAt(new Date()); + return orderRepository.save(order); + } + + public List getOrders(String tenantId, Map params) { + Integer page = (Integer) params.get("page"); + Integer pageSize = (Integer) params.get("pageSize"); + String status = (String) params.get("status"); + String platform = (String) params.get("platform"); + Date startDate = (Date) params.get("startDate"); + Date endDate = (Date) params.get("endDate"); + + if (startDate != null && endDate != null) { + return orderRepository.findByTenantIdAndDateRange(tenantId, startDate, endDate); + } else if (platform != null && status != null) { + return orderRepository.findByTenantIdAndPlatformAndStatus(tenantId, platform, status); + } else if (platform != null) { + return orderRepository.findByTenantIdAndPlatform(tenantId, platform); + } else if (status != null) { + return orderRepository.findByTenantIdAndStatus(tenantId, status); + } else { + return orderRepository.findByTenantId(tenantId); + } + } + + public Order getOrderById(String tenantId, String id) { + try { + Long orderId = Long.parseLong(id); + Order order = orderRepository.findById(orderId).orElse(null); + if (order != null && order.getTenantId().equals(tenantId)) { + return order; + } + return null; + } catch (NumberFormatException e) { + return null; + } + } + + public void updateOrder(String id, String tenantId, Map updateData) { + Order order = getOrderById(tenantId, id); + if (order != null) { + if (updateData.containsKey("status")) { + order.setStatus((String) updateData.get("status")); + } + if (updateData.containsKey("totalAmount")) { + order.setTotalAmount((Double) updateData.get("totalAmount")); + } + if (updateData.containsKey("currency")) { + order.setCurrency((String) updateData.get("currency")); + } + if (updateData.containsKey("customerInfo")) { + order.setCustomerInfo((String) updateData.get("customerInfo")); + } + if (updateData.containsKey("items")) { + order.setItems((String) updateData.get("items")); + } + if (updateData.containsKey("shippingAddress")) { + order.setShippingAddress((String) updateData.get("shippingAddress")); + } + if (updateData.containsKey("trackingNumber")) { + order.setTrackingNumber((String) updateData.get("trackingNumber")); + } + order.setUpdatedAt(new Date()); + orderRepository.save(order); + } + } + + public void deleteOrder(String id, String tenantId) { + Order order = getOrderById(tenantId, id); + if (order != null) { + orderRepository.delete(order); + } + } + + public void transitionOrderStatus(String tenantId, String id, String status, String reason) { + Order order = getOrderById(tenantId, id); + if (order != null) { + order.setStatus(status); + order.setUpdatedAt(new Date()); + orderRepository.save(order); + } + } + + public Map batchUpdateOrders(String tenantId, List orderIds, Map updates) { + int successCount = 0; + int failureCount = 0; + + for (String orderId : orderIds) { + try { + updateOrder(orderId, tenantId, updates); + successCount++; + } catch (Exception e) { + failureCount++; + } + } + + Map result = new HashMap<>(); + result.put("successCount", successCount); + result.put("failureCount", failureCount); + result.put("totalCount", orderIds.size()); + return result; + } + + public Map batchAuditOrders(String tenantId, List orderIds) { + Map updates = new HashMap<>(); + updates.put("status", "AUDITED"); + return batchUpdateOrders(tenantId, orderIds, updates); + } + + public Map batchShipOrders(String tenantId, List orderIds) { + Map updates = new HashMap<>(); + updates.put("status", "SHIPPED"); + return batchUpdateOrders(tenantId, orderIds, updates); + } + + public void markOrderAsException(String tenantId, String id, String reason) { + Map updates = new HashMap<>(); + updates.put("status", "EXCEPTION"); + updateOrder(id, tenantId, updates); + } + + public void autoRerouteOrder(String tenantId, String id) { + Order order = getOrderById(tenantId, id); + if (order != null) { + // 实现自动改派逻辑 + // 这里只是一个简单的示例 + order.setStatus("REROUTED"); + order.setUpdatedAt(new Date()); + orderRepository.save(order); + } + } + + public void retryExceptionOrder(String tenantId, String id) { + Map updates = new HashMap<>(); + updates.put("status", "PENDING"); + updateOrder(id, tenantId, updates); + } + + public void cancelOrder(String tenantId, String id, String reason) { + Map updates = new HashMap<>(); + updates.put("status", "CANCELLED"); + updateOrder(id, tenantId, updates); + } + + public String requestRefund(String tenantId, String id, String reason, Double amount) { + // 实现退款申请逻辑 + // 这里只是一个简单的示例 + return "REFUND_" + System.currentTimeMillis(); + } + + public void approveRefund(String tenantId, String id, boolean approved, String note) { + // 实现退款审批逻辑 + // 这里只是一个简单的示例 + } + + public String requestAfterSales(String tenantId, String id, String type, String reason, List> items) { + // 实现售后申请逻辑 + // 这里只是一个简单的示例 + return "AFTER_SALES_" + System.currentTimeMillis(); + } + + public void processAfterSales(String tenantId, String id, String action, String note) { + // 实现售后处理逻辑 + // 这里只是一个简单的示例 + } + + public void completeOrder(String tenantId, String id) { + Map updates = new HashMap<>(); + updates.put("status", "COMPLETED"); + updateOrder(id, tenantId, updates); + } + + public Map getOrderStats(String tenantId) { + // 实现订单统计逻辑 + // 这里只是一个简单的示例 + List orders = orderRepository.findByTenantId(tenantId); + int totalOrders = orders.size(); + int pendingOrders = 0; + int shippedOrders = 0; + int completedOrders = 0; + + for (Order order : orders) { + switch (order.getStatus()) { + case "PENDING": + pendingOrders++; + break; + case "SHIPPED": + shippedOrders++; + break; + case "COMPLETED": + completedOrders++; + break; + } + } + + Map stats = new HashMap<>(); + stats.put("totalOrders", totalOrders); + stats.put("pendingOrders", pendingOrders); + stats.put("shippedOrders", shippedOrders); + stats.put("completedOrders", completedOrders); + return stats; + } + + public Order mapPlatformPayloadToOrder(String platform, Map payload, String tenantId, String shopId) { + // 实现平台订单映射逻辑 + // 这里只是一个简单的示例 + Order order = new Order(); + order.setTenantId(tenantId); + order.setShopId(shopId); + order.setPlatform(platform); + order.setPlatformOrderId((String) payload.get("orderId")); + order.setStatus("PENDING"); + order.setTotalAmount((Double) payload.get("totalAmount")); + order.setCurrency((String) payload.get("currency")); + order.setCustomerInfo((String) payload.get("customerInfo")); + order.setItems((String) payload.get("items")); + order.setShippingAddress((String) payload.get("shippingAddress")); + order.setCreatedAt(new Date()); + order.setUpdatedAt(new Date()); + return order; + } +} diff --git a/src/main/java/com/crawlful/hub/service/PaymentRepository.java b/src/main/java/com/crawlful/hub/service/PaymentRepository.java new file mode 100644 index 0000000..b015b77 --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/PaymentRepository.java @@ -0,0 +1,13 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.Payment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface PaymentRepository extends JpaRepository { + List findByTenantId(String tenantId); + List findByTenantIdAndOrderId(String tenantId, Long orderId); + List findByTenantIdAndStatus(String tenantId, String status); + Payment findByTenantIdAndTransactionId(String tenantId, String transactionId); +} diff --git a/src/main/java/com/crawlful/hub/service/PaymentService.java b/src/main/java/com/crawlful/hub/service/PaymentService.java new file mode 100644 index 0000000..2901884 --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/PaymentService.java @@ -0,0 +1,97 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.Payment; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Service +public class PaymentService { + @Autowired + private PaymentRepository paymentRepository; + + public Payment createPayment(Map paymentData) { + Payment payment = new Payment(); + payment.setTenantId((String) paymentData.get("tenantId")); + payment.setOrderId((Long) paymentData.get("orderId")); + payment.setPaymentMethod((String) paymentData.get("paymentMethod")); + payment.setAmount((Double) paymentData.get("amount")); + payment.setCurrency((String) paymentData.get("currency")); + payment.setStatus((String) paymentData.get("status")); + payment.setTransactionId((String) paymentData.get("transactionId")); + payment.setCreatedAt(new Date()); + payment.setUpdatedAt(new Date()); + return paymentRepository.save(payment); + } + + public List getPayments(String tenantId, Map filters) { + String status = (String) filters.get("status"); + Long orderId = (Long) filters.get("orderId"); + + if (orderId != null) { + return paymentRepository.findByTenantIdAndOrderId(tenantId, orderId); + } else if (status != null) { + return paymentRepository.findByTenantIdAndStatus(tenantId, status); + } else { + return paymentRepository.findByTenantId(tenantId); + } + } + + public Payment getPaymentById(String tenantId, Long id) { + Payment payment = paymentRepository.findById(id).orElse(null); + if (payment != null && payment.getTenantId().equals(tenantId)) { + return payment; + } + return null; + } + + public Payment getPaymentByTransactionId(String tenantId, String transactionId) { + return paymentRepository.findByTenantIdAndTransactionId(tenantId, transactionId); + } + + public void updatePayment(String tenantId, Long id, Map updateData) { + Payment payment = getPaymentById(tenantId, id); + if (payment != null) { + if (updateData.containsKey("status")) { + payment.setStatus((String) updateData.get("status")); + } + if (updateData.containsKey("transactionId")) { + payment.setTransactionId((String) updateData.get("transactionId")); + } + payment.setUpdatedAt(new Date()); + paymentRepository.save(payment); + } + } + + public void processPaymentCallback(String tenantId, String transactionId, Map callbackData) { + Payment payment = getPaymentByTransactionId(tenantId, transactionId); + if (payment != null) { + String status = (String) callbackData.get("status"); + payment.setStatus(status); + payment.setUpdatedAt(new Date()); + paymentRepository.save(payment); + } + } + + public Map refundPayment(String tenantId, Long id, Double amount, String reason) { + Payment payment = getPaymentById(tenantId, id); + if (payment != null) { + // 实现退款逻辑 + // 这里只是一个简单的示例 + payment.setStatus("REFUNDED"); + payment.setUpdatedAt(new Date()); + paymentRepository.save(payment); + + return Map.of( + "success", true, + "refundId", "REFUND_" + System.currentTimeMillis(), + "amount", amount, + "reason", reason + ); + } + return Map.of("success", false, "error", "Payment not found"); + } +} diff --git a/src/main/java/com/crawlful/hub/service/ProductRepository.java b/src/main/java/com/crawlful/hub/service/ProductRepository.java new file mode 100644 index 0000000..3837ff5 --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/ProductRepository.java @@ -0,0 +1,30 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.Product; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ProductRepository extends JpaRepository { + List findByTenantId(String tenantId); + List findByTenantIdAndPlatform(String tenantId, String platform); + List findByTenantIdAndStatus(String tenantId, String status); + List findByTenantIdAndPlatformAndStatus(String tenantId, String platform, String status); + + @Query("SELECT p FROM Product p WHERE p.tenantId = :tenantId ORDER BY p.id DESC LIMIT :size OFFSET :offset") + List findByTenantIdWithPagination(@Param("tenantId") String tenantId, @Param("offset") int offset, @Param("size") int size); + + @Query("SELECT p FROM Product p WHERE p.tenantId = :tenantId AND p.platform = :platform ORDER BY p.id DESC LIMIT :size OFFSET :offset") + List findByTenantIdAndPlatformWithPagination(@Param("tenantId") String tenantId, @Param("platform") String platform, @Param("offset") int offset, @Param("size") int size); + + @Query("SELECT p FROM Product p WHERE p.tenantId = :tenantId AND p.status = :status ORDER BY p.id DESC LIMIT :size OFFSET :offset") + List findByTenantIdAndStatusWithPagination(@Param("tenantId") String tenantId, @Param("status") String status, @Param("offset") int offset, @Param("size") int size); + + @Query("SELECT p FROM Product p WHERE p.tenantId = :tenantId AND p.platform = :platform AND p.status = :status ORDER BY p.id DESC LIMIT :size OFFSET :offset") + List findByTenantIdAndPlatformAndStatusWithPagination(@Param("tenantId") String tenantId, @Param("platform") String platform, @Param("status") String status, @Param("offset") int offset, @Param("size") int size); + + @Query("SELECT p FROM Product p WHERE p.tenantId = :tenantId AND p.phash = :phash") + List findByFingerprint(@Param("tenantId") String tenantId, @Param("phash") String phash); +} diff --git a/src/main/java/com/crawlful/hub/service/ProductService.java b/src/main/java/com/crawlful/hub/service/ProductService.java new file mode 100644 index 0000000..1b515c6 --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/ProductService.java @@ -0,0 +1,164 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.Product; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Service +public class ProductService { + @Autowired + private ProductRepository productRepository; + + public Product create(String tenantId, Map productData) { + Product product = new Product(); + product.setTenantId(tenantId); + product.setTitle((String) productData.get("title")); + product.setDescription((String) productData.get("description")); + product.setMainImage((String) productData.get("mainImage")); + product.setPlatform((String) productData.get("platform")); + product.setPlatformProductId((String) productData.get("platformProductId")); + product.setPrice((Double) productData.get("price")); + product.setCostPrice((Double) productData.get("costPrice")); + product.setQuantity((Integer) productData.get("quantity")); + product.setStatus((String) productData.get("status")); + product.setPhash((String) productData.get("phash")); + product.setSemanticHash((String) productData.get("semanticHash")); + product.setVectorEmbedding((String) productData.get("vectorEmbedding")); + product.setAttributes((String) productData.get("attributes")); + product.setCreatedAt(new Date()); + product.setUpdatedAt(new Date()); + return productRepository.save(product); + } + + public List getAll(String tenantId, Map filters, int page, int size) { + String platform = filters.get("platform"); + String status = filters.get("status"); + int offset = (page - 1) * size; + + if (platform != null && status != null) { + return productRepository.findByTenantIdAndPlatformAndStatusWithPagination(tenantId, platform, status, offset, size); + } else if (platform != null) { + return productRepository.findByTenantIdAndPlatformWithPagination(tenantId, platform, offset, size); + } else if (status != null) { + return productRepository.findByTenantIdAndStatusWithPagination(tenantId, status, offset, size); + } else { + return productRepository.findByTenantIdWithPagination(tenantId, offset, size); + } + } + + public Product getById(String tenantId, Long id) { + Product product = productRepository.findById(id).orElse(null); + if (product != null && product.getTenantId().equals(tenantId)) { + return product; + } + return null; + } + + public Product update(String tenantId, Long id, Map updateData) { + Product product = getById(tenantId, id); + if (product == null) { + return null; + } + + if (updateData.containsKey("title")) { + product.setTitle((String) updateData.get("title")); + } + if (updateData.containsKey("description")) { + product.setDescription((String) updateData.get("description")); + } + if (updateData.containsKey("mainImage")) { + product.setMainImage((String) updateData.get("mainImage")); + } + if (updateData.containsKey("platform")) { + product.setPlatform((String) updateData.get("platform")); + } + if (updateData.containsKey("platformProductId")) { + product.setPlatformProductId((String) updateData.get("platformProductId")); + } + if (updateData.containsKey("price")) { + product.setPrice((Double) updateData.get("price")); + } + if (updateData.containsKey("costPrice")) { + product.setCostPrice((Double) updateData.get("costPrice")); + } + if (updateData.containsKey("quantity")) { + product.setQuantity((Integer) updateData.get("quantity")); + } + if (updateData.containsKey("status")) { + product.setStatus((String) updateData.get("status")); + } + if (updateData.containsKey("phash")) { + product.setPhash((String) updateData.get("phash")); + } + if (updateData.containsKey("semanticHash")) { + product.setSemanticHash((String) updateData.get("semanticHash")); + } + if (updateData.containsKey("vectorEmbedding")) { + product.setVectorEmbedding((String) updateData.get("vectorEmbedding")); + } + if (updateData.containsKey("attributes")) { + product.setAttributes((String) updateData.get("attributes")); + } + + product.setUpdatedAt(new Date()); + return productRepository.save(product); + } + + public void delete(String tenantId, Long id) { + Product product = getById(tenantId, id); + if (product != null) { + productRepository.delete(product); + } + } + + public List findByFingerprint(String tenantId, Map fingerprint) { + // 简单实现,实际应该使用更复杂的指纹匹配算法 + String phash = fingerprint.get("phash"); + return productRepository.findByFingerprint(tenantId, phash); + } + + public Map washAndLocalize(String tenantId, Long id, String targetMarket, String targetLang) { + // 实现商品清洗与本地化逻辑 + // 这里只是一个简单的示例 + Product product = getById(tenantId, id); + if (product == null) { + return null; + } + + // 模拟清洗与本地化过程 + Map result = Map.of( + "id", product.getId(), + "title", product.getTitle() + " (Localized for " + targetMarket + ")", + "description", product.getDescription() + " (Localized for " + targetMarket + ")", + "targetMarket", targetMarket, + "targetLang", targetLang, + "status", "LOCALIZED" + ); + + return result; + } + + public Map analyzeProductArbitrage(String tenantId, Long id) { + // 实现套利机会分析逻辑 + // 这里只是一个简单的示例 + Product product = getById(tenantId, id); + if (product == null) { + return null; + } + + double profitMargin = (product.getPrice() - product.getCostPrice()) / product.getPrice() * 100; + Map result = Map.of( + "productId", product.getId(), + "price", product.getPrice(), + "costPrice", product.getCostPrice(), + "profitMargin", profitMargin, + "arbitrageOpportunity", profitMargin > 20 + ); + + return result; + } +} diff --git a/src/main/java/com/crawlful/hub/service/ReportService.java b/src/main/java/com/crawlful/hub/service/ReportService.java new file mode 100644 index 0000000..a622e02 --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/ReportService.java @@ -0,0 +1,246 @@ +package com.crawlful.hub.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.*; + +@Service +public class ReportService { + @Autowired + private UserRepository userRepository; + @Autowired + private ProductRepository productRepository; + @Autowired + private OrderRepository orderRepository; + @Autowired + private PaymentRepository paymentRepository; + @Autowired + private LogisticsRepository logisticsRepository; + + public Map generateSalesReport(String tenantId, Date startDate, Date endDate) { + // 生成销售报表 + List> salesData = new ArrayList<>(); + double totalSales = 0.0; + int totalOrders = 0; + + for (var order : orderRepository.findByTenantId(tenantId)) { + if (order.getCreatedAt().after(startDate) && order.getCreatedAt().before(endDate)) { + double orderAmount = order.getTotalAmount() != null ? order.getTotalAmount() : 0; + totalSales += orderAmount; + totalOrders++; + + salesData.add(Map.of( + "orderId", order.getId(), + "platform", order.getPlatform(), + "amount", orderAmount, + "currency", order.getCurrency(), + "status", order.getStatus(), + "createdAt", order.getCreatedAt() + )); + } + } + + double averageOrderValue = totalOrders > 0 ? totalSales / totalOrders : 0; + + // 按平台统计 + Map> platformStats = new HashMap<>(); + for (var order : orderRepository.findByTenantId(tenantId)) { + if (order.getCreatedAt().after(startDate) && order.getCreatedAt().before(endDate)) { + String platform = order.getPlatform(); + if (!platformStats.containsKey(platform)) { + platformStats.put(platform, Map.of( + "orders", 0, + "sales", 0.0 + )); + } + + Map currentStats = platformStats.get(platform); + int orders = (Integer) currentStats.get("orders") + 1; + double sales = (Double) currentStats.get("sales") + (order.getTotalAmount() != null ? order.getTotalAmount() : 0); + + platformStats.put(platform, Map.of( + "orders", orders, + "sales", sales + )); + } + } + + return Map.of( + "totalSales", totalSales, + "totalOrders", totalOrders, + "averageOrderValue", averageOrderValue, + "salesData", salesData, + "platformStats", platformStats, + "startDate", startDate, + "endDate", endDate + ); + } + + public Map generateInventoryReport(String tenantId) { + // 生成库存报表 + List> inventoryData = new ArrayList<>(); + int totalProducts = 0; + int lowStockProducts = 0; + int outOfStockProducts = 0; + + for (var product : productRepository.findByTenantId(tenantId)) { + totalProducts++; + int quantity = product.getQuantity() != null ? product.getQuantity() : 0; + if (quantity < 10) { + lowStockProducts++; + } + if (quantity == 0) { + outOfStockProducts++; + } + + inventoryData.add(Map.of( + "productId", product.getId(), + "title", product.getTitle(), + "platform", product.getPlatform(), + "quantity", quantity, + "price", product.getPrice(), + "status", product.getStatus() + )); + } + + return Map.of( + "totalProducts", totalProducts, + "lowStockProducts", lowStockProducts, + "outOfStockProducts", outOfStockProducts, + "inventoryData", inventoryData + ); + } + + public Map generateUserReport(String tenantId) { + // 生成用户报表 + List> userData = new ArrayList<>(); + int totalUsers = 0; + Map roleStats = new HashMap<>(); + + for (var user : userRepository.findByTenantId(tenantId)) { + totalUsers++; + String role = user.getRole(); + roleStats.put(role, roleStats.getOrDefault(role, 0) + 1); + + userData.add(Map.of( + "userId", user.getId(), + "username", user.getUsername(), + "email", user.getEmail(), + "role", role, + "createdAt", user.getCreatedAt() + )); + } + + return Map.of( + "totalUsers", totalUsers, + "roleStats", roleStats, + "userData", userData + ); + } + + public Map generatePaymentReport(String tenantId, Date startDate, Date endDate) { + // 生成支付报表 + List> paymentData = new ArrayList<>(); + double totalPayments = 0.0; + int totalTransactions = 0; + Map paymentMethodStats = new HashMap<>(); + + for (var payment : paymentRepository.findByTenantId(tenantId)) { + if (payment.getCreatedAt().after(startDate) && payment.getCreatedAt().before(endDate)) { + double amount = payment.getAmount() != null ? payment.getAmount() : 0; + totalPayments += amount; + totalTransactions++; + + String paymentMethod = payment.getPaymentMethod(); + paymentMethodStats.put(paymentMethod, paymentMethodStats.getOrDefault(paymentMethod, 0) + 1); + + paymentData.add(Map.of( + "paymentId", payment.getId(), + "orderId", payment.getOrderId(), + "paymentMethod", paymentMethod, + "amount", amount, + "currency", payment.getCurrency(), + "status", payment.getStatus(), + "transactionId", payment.getTransactionId(), + "createdAt", payment.getCreatedAt() + )); + } + } + + return Map.of( + "totalPayments", totalPayments, + "totalTransactions", totalTransactions, + "paymentMethodStats", paymentMethodStats, + "paymentData", paymentData, + "startDate", startDate, + "endDate", endDate + ); + } + + public Map generateLogisticsReport(String tenantId, Date startDate, Date endDate) { + // 生成物流报表 + List> logisticsData = new ArrayList<>(); + int totalShipments = 0; + int deliveredShipments = 0; + Map carrierStats = new HashMap<>(); + + for (var logistics : logisticsRepository.findByTenantId(tenantId)) { + if (logistics.getCreatedAt().after(startDate) && logistics.getCreatedAt().before(endDate)) { + totalShipments++; + if ("DELIVERED".equals(logistics.getStatus())) { + deliveredShipments++; + } + + String carrier = logistics.getCarrier(); + carrierStats.put(carrier, carrierStats.getOrDefault(carrier, 0) + 1); + + logisticsData.add(Map.of( + "logisticsId", logistics.getId(), + "orderId", logistics.getOrderId(), + "shippingMethod", logistics.getShippingMethod(), + "trackingNumber", logistics.getTrackingNumber(), + "carrier", carrier, + "status", logistics.getStatus(), + "estimatedDeliveryDate", logistics.getEstimatedDeliveryDate(), + "actualDeliveryDate", logistics.getActualDeliveryDate(), + "createdAt", logistics.getCreatedAt() + )); + } + } + + double deliveryRate = totalShipments > 0 ? (double) deliveredShipments / totalShipments * 100 : 0; + + return Map.of( + "totalShipments", totalShipments, + "deliveredShipments", deliveredShipments, + "deliveryRate", deliveryRate, + "carrierStats", carrierStats, + "logisticsData", logisticsData, + "startDate", startDate, + "endDate", endDate + ); + } + + public Map generateCustomReport(String tenantId, Map reportParams) { + // 生成自定义报表 + String reportType = (String) reportParams.get("reportType"); + Date startDate = (Date) reportParams.get("startDate"); + Date endDate = (Date) reportParams.get("endDate"); + + switch (reportType) { + case "sales": + return generateSalesReport(tenantId, startDate, endDate); + case "inventory": + return generateInventoryReport(tenantId); + case "user": + return generateUserReport(tenantId); + case "payment": + return generatePaymentReport(tenantId, startDate, endDate); + case "logistics": + return generateLogisticsReport(tenantId, startDate, endDate); + default: + return Map.of("error", "Invalid report type"); + } + } +} diff --git a/src/main/java/com/crawlful/hub/service/UserRepository.java b/src/main/java/com/crawlful/hub/service/UserRepository.java new file mode 100644 index 0000000..22f496f --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/UserRepository.java @@ -0,0 +1,13 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface UserRepository extends JpaRepository { + User findByUsername(String username); + User findByEmail(String email); + List findByTenantId(String tenantId); + User findByTenantIdAndUsername(String tenantId, String username); +} diff --git a/src/main/java/com/crawlful/hub/service/UserService.java b/src/main/java/com/crawlful/hub/service/UserService.java new file mode 100644 index 0000000..47e1716 --- /dev/null +++ b/src/main/java/com/crawlful/hub/service/UserService.java @@ -0,0 +1,73 @@ +package com.crawlful.hub.service; + +import com.crawlful.hub.model.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Service +public class UserService { + @Autowired + private UserRepository userRepository; + + public User create(String tenantId, Map userData) { + User user = new User(); + user.setTenantId(tenantId); + user.setUsername((String) userData.get("username")); + user.setPassword((String) userData.get("password")); + user.setEmail((String) userData.get("email")); + user.setRole((String) userData.get("role")); + user.setCreatedAt(new Date()); + user.setUpdatedAt(new Date()); + return userRepository.save(user); + } + + public List getAll(String tenantId) { + return userRepository.findByTenantId(tenantId); + } + + public User getById(String tenantId, Long id) { + User user = userRepository.findById(id).orElse(null); + if (user != null && user.getTenantId().equals(tenantId)) { + return user; + } + return null; + } + + public User getByUsername(String tenantId, String username) { + return userRepository.findByTenantIdAndUsername(tenantId, username); + } + + public User update(String tenantId, Long id, Map updateData) { + User user = getById(tenantId, id); + if (user == null) { + return null; + } + + if (updateData.containsKey("username")) { + user.setUsername((String) updateData.get("username")); + } + if (updateData.containsKey("password")) { + user.setPassword((String) updateData.get("password")); + } + if (updateData.containsKey("email")) { + user.setEmail((String) updateData.get("email")); + } + if (updateData.containsKey("role")) { + user.setRole((String) updateData.get("role")); + } + + user.setUpdatedAt(new Date()); + return userRepository.save(user); + } + + public void delete(String tenantId, Long id) { + User user = getById(tenantId, id); + if (user != null) { + userRepository.delete(user); + } + } +} diff --git a/src/main/java/com/crawlful/hub/util/ValidationUtil.java b/src/main/java/com/crawlful/hub/util/ValidationUtil.java new file mode 100644 index 0000000..2ddeb13 --- /dev/null +++ b/src/main/java/com/crawlful/hub/util/ValidationUtil.java @@ -0,0 +1,80 @@ +package com.crawlful.hub.util; + +import java.util.Map; +import java.util.regex.Pattern; + +public class ValidationUtil { + + private static final Pattern EMAIL_PATTERN = Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"); + private static final Pattern PASSWORD_PATTERN = Pattern.compile("^.{6,}$"); + private static final Pattern PHONE_PATTERN = Pattern.compile("^\\d{10,11}$"); + + public static boolean validateEmail(String email) { + return email != null && EMAIL_PATTERN.matcher(email).matches(); + } + + public static boolean validatePassword(String password) { + return password != null && PASSWORD_PATTERN.matcher(password).matches(); + } + + public static boolean validatePhone(String phone) { + return phone != null && PHONE_PATTERN.matcher(phone).matches(); + } + + public static boolean validateRequiredFields(Map data, String... fields) { + for (String field : fields) { + if (!data.containsKey(field) || data.get(field) == null) { + return false; + } + } + return true; + } + + public static boolean validateNumber(Object value) { + if (value == null) { + return false; + } + try { + Double.parseDouble(value.toString()); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + public static boolean validatePositiveNumber(Object value) { + if (!validateNumber(value)) { + return false; + } + double num = Double.parseDouble(value.toString()); + return num > 0; + } + + public static boolean validateInteger(Object value) { + if (value == null) { + return false; + } + try { + Integer.parseInt(value.toString()); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + public static boolean validatePositiveInteger(Object value) { + if (!validateInteger(value)) { + return false; + } + int num = Integer.parseInt(value.toString()); + return num > 0; + } + + public static boolean validateStringLength(String value, int min, int max) { + if (value == null) { + return false; + } + int length = value.length(); + return length >= min && length <= max; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..1b04b46 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,36 @@ +spring: + datasource: + url: jdbc:mysql://localhost:3306/crawlful_hub?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC + username: root + password: 123456 + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 10 + minimum-idle: 5 + idle-timeout: 30000 + connection-timeout: 20000 + max-lifetime: 1800000 + jpa: + hibernate: + ddl-auto: update + show-sql: true + redis: + host: localhost + port: 6379 + password: + database: 0 + security: + jwt: + secret: your-secret-key + expiration: 86400000 + springdoc: + api-docs: + path: /api-docs + swagger-ui: + path: /swagger-ui.html + enabled: true + +server: + port: 3001 + servlet: + context-path: /api diff --git a/src/main/resources/db/migration/V1__init_schema.sql b/src/main/resources/db/migration/V1__init_schema.sql new file mode 100644 index 0000000..1210603 --- /dev/null +++ b/src/main/resources/db/migration/V1__init_schema.sql @@ -0,0 +1,130 @@ +-- 创建用户表 +CREATE TABLE IF NOT EXISTS cf_user ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255) NOT NULL, + username VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + role VARCHAR(50), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- 创建商品表 +CREATE TABLE IF NOT EXISTS cf_product ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255) NOT NULL, + shop_id VARCHAR(255), + title VARCHAR(255) NOT NULL, + description TEXT, + main_image VARCHAR(255), + platform VARCHAR(50), + platform_product_id VARCHAR(255), + price DECIMAL(10,2), + cost_price DECIMAL(10,2), + quantity INT, + status VARCHAR(50), + phash VARCHAR(255), + semantic_hash VARCHAR(255), + vector_embedding TEXT, + attributes JSON, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- 创建订单表 +CREATE TABLE IF NOT EXISTS cf_order ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255) NOT NULL, + shop_id VARCHAR(255), + platform VARCHAR(50), + platform_order_id VARCHAR(255), + status VARCHAR(50), + total_amount DECIMAL(10,2), + currency VARCHAR(10), + customer_info JSON, + items JSON, + shipping_address JSON, + tracking_number VARCHAR(255), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- 创建支付表 +CREATE TABLE IF NOT EXISTS cf_payment ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255) NOT NULL, + order_id BIGINT, + payment_method VARCHAR(50), + amount DECIMAL(10,2), + currency VARCHAR(10), + status VARCHAR(50), + transaction_id VARCHAR(255), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (order_id) REFERENCES cf_order(id) +); + +-- 创建物流表 +CREATE TABLE IF NOT EXISTS cf_logistics ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255) NOT NULL, + order_id BIGINT, + shipping_method VARCHAR(50), + tracking_number VARCHAR(255), + carrier VARCHAR(50), + status VARCHAR(50), + estimated_delivery_date DATETIME, + actual_delivery_date DATETIME, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (order_id) REFERENCES cf_order(id) +); + +-- 创建配置表 +CREATE TABLE IF NOT EXISTS cf_config ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255), + shop_id VARCHAR(255), + config_key VARCHAR(255) NOT NULL, + config_value VARCHAR(255) NOT NULL, + config_type VARCHAR(50), + description TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- 创建审计表 +CREATE TABLE IF NOT EXISTS cf_audit ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255), + shop_id VARCHAR(255), + user_id BIGINT, + action VARCHAR(255), + resource_type VARCHAR(255), + resource_id VARCHAR(255), + ip_address VARCHAR(100), + user_agent TEXT, + details TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- 创建索引 +CREATE INDEX IF NOT EXISTS idx_user_tenant_id ON cf_user(tenant_id); +CREATE INDEX IF NOT EXISTS idx_product_tenant_id ON cf_product(tenant_id); +CREATE INDEX IF NOT EXISTS idx_product_platform ON cf_product(platform); +CREATE INDEX IF NOT EXISTS idx_order_tenant_id ON cf_order(tenant_id); +CREATE INDEX IF NOT EXISTS idx_order_platform ON cf_order(platform); +CREATE INDEX IF NOT EXISTS idx_payment_tenant_id ON cf_payment(tenant_id); +CREATE INDEX IF NOT EXISTS idx_payment_order_id ON cf_payment(order_id); +CREATE INDEX IF NOT EXISTS idx_logistics_tenant_id ON cf_logistics(tenant_id); +CREATE INDEX IF NOT EXISTS idx_logistics_order_id ON cf_logistics(order_id); +CREATE INDEX IF NOT EXISTS idx_config_tenant_id ON cf_config(tenant_id); +CREATE INDEX IF NOT EXISTS idx_config_shop_id ON cf_config(shop_id); +CREATE INDEX IF NOT EXISTS idx_config_key ON cf_config(config_key); +CREATE INDEX IF NOT EXISTS idx_audit_tenant_id ON cf_audit(tenant_id); +CREATE INDEX IF NOT EXISTS idx_audit_shop_id ON cf_audit(shop_id); +CREATE INDEX IF NOT EXISTS idx_audit_user_id ON cf_audit(user_id); +CREATE INDEX IF NOT EXISTS idx_audit_action ON cf_audit(action); +CREATE INDEX IF NOT EXISTS idx_audit_resource_type ON cf_audit(resource_type); +CREATE INDEX IF NOT EXISTS idx_audit_created_at ON cf_audit(created_at); diff --git a/src/main/resources/db/migration/V2__add_alert_table.sql b/src/main/resources/db/migration/V2__add_alert_table.sql new file mode 100644 index 0000000..63faf24 --- /dev/null +++ b/src/main/resources/db/migration/V2__add_alert_table.sql @@ -0,0 +1,21 @@ +-- 创建告警表 +CREATE TABLE IF NOT EXISTS cf_alert ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255) NOT NULL, + alert_type VARCHAR(50), + severity VARCHAR(50), + message TEXT, + status VARCHAR(50), + source VARCHAR(255), + threshold VARCHAR(255), + actual_value VARCHAR(255), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + resolved_at DATETIME +); + +-- 为告警表添加索引 +CREATE INDEX IF NOT EXISTS idx_alert_tenant_id ON cf_alert(tenant_id); +CREATE INDEX IF NOT EXISTS idx_alert_status ON cf_alert(status); +CREATE INDEX IF NOT EXISTS idx_alert_severity ON cf_alert(severity); +CREATE INDEX IF NOT EXISTS idx_alert_alert_type ON cf_alert(alert_type); +CREATE INDEX IF NOT EXISTS idx_alert_created_at ON cf_alert(created_at); diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..28e38f5 --- /dev/null +++ b/src/main/resources/i18n/messages.properties @@ -0,0 +1,62 @@ +# Authentication messages +auth.register.success=User registered successfully +auth.login.success=Login successful +auth.login.failure=Invalid username or password +auth.username.required=Username is required +auth.password.required=Password is required +auth.email.required=Email is required +auth.email.invalid=Invalid email format +auth.password.minlength=Password must be at least 6 characters +auth.username.minlength=Username must be at least 3 characters +auth.username.exists=Username already exists + +# Product messages +product.create.success=Product created successfully +product.update.success=Product updated successfully +product.delete.success=Product deleted successfully +product.not.found=Product not found +product.title.required=Product title is required +product.price.required=Product price is required +product.price.positive=Product price must be positive + +# Order messages +order.create.success=Order created successfully +order.update.success=Order updated successfully +order.not.found=Order not found +order.status.updated=Order status updated successfully + +# Payment messages +payment.create.success=Payment created successfully +payment.update.success=Payment updated successfully +payment.not.found=Payment not found +payment.status.updated=Payment status updated successfully + +# Logistics messages +logistics.create.success=Logistics created successfully +logistics.update.success=Logistics updated successfully +logistics.not.found=Logistics not found +logistics.status.updated=Logistics status updated successfully + +# Alert messages +alert.create.success=Alert created successfully +alert.update.success=Alert updated successfully +alert.resolve.success=Alert resolved successfully +alert.not.found=Alert not found + +# Monitoring messages +monitoring.health.ok=System health is OK +monitoring.health.error=System health check failed +monitoring.metrics.success=Performance metrics retrieved successfully +monitoring.services.success=Service status retrieved successfully +monitoring.database.success=Database status retrieved successfully +monitoring.cache.success=Cache status retrieved successfully +monitoring.stats.success=System stats retrieved successfully + +# Common messages +common.success=Operation successful +common.error=Operation failed +common.invalid.request=Invalid request +common.missing.parameter=Missing required parameter +common.not.found=Resource not found +common.access.denied=Access denied +common.server.error=Server internal error diff --git a/src/main/resources/i18n/messages_zh.properties b/src/main/resources/i18n/messages_zh.properties new file mode 100644 index 0000000..78a3c2d --- /dev/null +++ b/src/main/resources/i18n/messages_zh.properties @@ -0,0 +1,62 @@ +# Authentication messages +auth.register.success=用户注册成功 +auth.login.success=登录成功 +auth.login.failure=用户名或密码错误 +auth.username.required=用户名不能为空 +auth.password.required=密码不能为空 +auth.email.required=邮箱不能为空 +auth.email.invalid=邮箱格式错误 +auth.password.minlength=密码至少6个字符 +auth.username.minlength=用户名至少3个字符 +auth.username.exists=用户名已存在 + +# Product messages +product.create.success=商品创建成功 +product.update.success=商品更新成功 +product.delete.success=商品删除成功 +product.not.found=商品不存在 +product.title.required=商品标题不能为空 +product.price.required=商品价格不能为空 +product.price.positive=商品价格必须大于0 + +# Order messages +order.create.success=订单创建成功 +order.update.success=订单更新成功 +order.not.found=订单不存在 +order.status.updated=订单状态更新成功 + +# Payment messages +payment.create.success=支付创建成功 +payment.update.success=支付更新成功 +payment.not.found=支付不存在 +payment.status.updated=支付状态更新成功 + +# Logistics messages +logistics.create.success=物流创建成功 +logistics.update.success=物流更新成功 +logistics.not.found=物流不存在 +logistics.status.updated=物流状态更新成功 + +# Alert messages +alert.create.success=告警创建成功 +alert.update.success=告警更新成功 +alert.resolve.success=告警已解决 +alert.not.found=告警不存在 + +# Monitoring messages +monitoring.health.ok=系统健康状态良好 +monitoring.health.error=系统健康检查失败 +monitoring.metrics.success=性能指标获取成功 +monitoring.services.success=服务状态获取成功 +monitoring.database.success=数据库状态获取成功 +monitoring.cache.success=缓存状态获取成功 +monitoring.stats.success=系统统计信息获取成功 + +# Common messages +common.success=操作成功 +common.error=操作失败 +common.invalid.request=无效的请求 +common.missing.parameter=缺少必要参数 +common.not.found=资源不存在 +common.access.denied=访问被拒绝 +common.server.error=服务器内部错误 diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..cb255d0 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,83 @@ + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + ${LOG_HOME}/app.log + + ${LOG_HOME}/app-%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + ${LOG_HOME}/error.log + + ${LOG_HOME}/error-%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + ERROR + ACCEPT + DENY + + + + + + ${LOG_HOME}/business.log + + ${LOG_HOME}/business-%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/com/crawlful/hub/integration/SystemIntegrationTest.java b/src/test/java/com/crawlful/hub/integration/SystemIntegrationTest.java new file mode 100644 index 0000000..dfa5612 --- /dev/null +++ b/src/test/java/com/crawlful/hub/integration/SystemIntegrationTest.java @@ -0,0 +1,208 @@ +package com.crawlful.hub.integration; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import com.crawlful.hub.service.AuthService; +import com.crawlful.hub.service.ProductService; +import com.crawlful.hub.service.OrderService; +import com.crawlful.hub.service.PaymentService; +import com.crawlful.hub.service.LogisticsService; +import com.crawlful.hub.service.DataService; +import com.crawlful.hub.service.ReportService; +import com.crawlful.hub.service.ConfigService; +import com.crawlful.hub.service.AuditService; +import com.crawlful.hub.service.MonitoringService; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles("test") +class SystemIntegrationTest { + + @Autowired + private AuthService authService; + + @Autowired + private ProductService productService; + + @Autowired + private OrderService orderService; + + @Autowired + private PaymentService paymentService; + + @Autowired + private LogisticsService logisticsService; + + @Autowired + private DataService dataService; + + @Autowired + private ReportService reportService; + + @Autowired + private ConfigService configService; + + @Autowired + private AuditService auditService; + + @Autowired + private MonitoringService monitoringService; + + @Test + void testSystemHealth() { + // 测试系统健康状态 + Map health = monitoringService.getSystemHealth(); + assertNotNull(health); + assertEquals("UP", health.get("status")); + } + + @Test + void testAuthService() { + // 测试认证服务 + Map userData = Map.of( + "username", "testuser", + "password", "password123", + "email", "test@example.com", + "role", "USER", + "tenantId", "tenant1" + ); + + Map registerResult = authService.register(userData); + assertTrue((Boolean) registerResult.get("success")); + assertNotNull(registerResult.get("userId")); + + Map loginData = Map.of( + "username", "testuser", + "password", "password123", + "tenantId", "tenant1" + ); + + Map loginResult = authService.login(loginData); + assertTrue((Boolean) loginResult.get("success")); + assertNotNull(loginResult.get("token")); + } + + @Test + void testProductService() { + // 测试商品服务 + Map productData = Map.of( + "title", "Test Product", + "description", "Test Description", + "price", 100.0, + "quantity", 10, + "platform", "Amazon", + "tenantId", "tenant1" + ); + + Map createResult = productService.createProduct(productData); + assertTrue((Boolean) createResult.get("success")); + assertNotNull(createResult.get("productId")); + + Map filters = Map.of(); + assertNotNull(productService.getProducts("tenant1", filters)); + } + + @Test + void testOrderService() { + // 测试订单服务 + Map orderData = Map.of( + "platform", "Amazon", + "totalAmount", 100.0, + "currency", "USD", + "items", Map.of(), + "tenantId", "tenant1" + ); + + Map createResult = orderService.createOrder(orderData); + assertTrue((Boolean) createResult.get("success")); + assertNotNull(createResult.get("orderId")); + + Map filters = Map.of(); + assertNotNull(orderService.getOrders("tenant1", filters)); + } + + @Test + void testPaymentService() { + // 测试支付服务 + Map paymentData = Map.of( + "orderId", 1L, + "paymentMethod", "Credit Card", + "amount", 100.0, + "currency", "USD", + "tenantId", "tenant1" + ); + + assertNotNull(paymentService.createPayment(paymentData)); + } + + @Test + void testLogisticsService() { + // 测试物流服务 + Map logisticsData = Map.of( + "orderId", 1L, + "shippingMethod", "Standard", + "trackingNumber", "123456789", + "carrier", "UPS", + "status", "PENDING", + "tenantId", "tenant1" + ); + + assertNotNull(logisticsService.createLogistics(logisticsData)); + } + + @Test + void testDataService() { + // 测试数据服务 + assertNotNull(dataService.getDashboardData("tenant1")); + } + + @Test + void testReportService() { + // 测试报表服务 + assertNotNull(reportService.generateInventoryReport("tenant1")); + } + + @Test + void testConfigService() { + // 测试配置服务 + Map configData = Map.of( + "configKey", "test.key", + "configValue", "test.value", + "configType", "string", + "description", "Test configuration", + "tenantId", "tenant1" + ); + + assertNotNull(configService.createConfig(configData)); + } + + @Test + void testAuditService() { + // 测试审计服务 + Map auditData = Map.of( + "action", "TEST_ACTION", + "resourceType", "TEST_RESOURCE", + "resourceId", "1", + "ipAddress", "127.0.0.1", + "userAgent", "Test Agent", + "details", "Test audit log", + "tenantId", "tenant1" + ); + + assertNotNull(auditService.createAudit(auditData)); + } + + @Test + void testMonitoringService() { + // 测试监控服务 + assertNotNull(monitoringService.getSystemHealth()); + assertNotNull(monitoringService.getPerformanceMetrics()); + assertNotNull(monitoringService.getServiceStatus()); + } +} diff --git a/src/test/java/com/crawlful/hub/service/AuthServiceTest.java b/src/test/java/com/crawlful/hub/service/AuthServiceTest.java new file mode 100644 index 0000000..a3c7c6b --- /dev/null +++ b/src/test/java/com/crawlful/hub/service/AuthServiceTest.java @@ -0,0 +1,62 @@ +package com.crawlful.hub.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class AuthServiceTest { + + @Mock + private UserRepository userRepository; + + @InjectMocks + private AuthService authService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void testRegisterUser() { + Map userData = Map.of( + "username", "testuser", + "password", "password123", + "email", "test@example.com", + "role", "USER", + "tenantId", "tenant1" + ); + + Map result = authService.register(userData); + assertTrue((Boolean) result.get("success")); + assertNotNull(result.get("userId")); + } + + @Test + void testLoginUser() { + Map loginData = Map.of( + "username", "testuser", + "password", "password123", + "tenantId", "tenant1" + ); + + Map result = authService.login(loginData); + assertTrue((Boolean) result.get("success")); + assertNotNull(result.get("token")); + assertNotNull(result.get("user")); + } + + @Test + void testValidateToken() { + String token = "test-token"; + Map result = authService.validateToken(token); + assertTrue((Boolean) result.get("valid")); + } +} diff --git a/src/test/java/com/crawlful/hub/service/OrderServiceTest.java b/src/test/java/com/crawlful/hub/service/OrderServiceTest.java new file mode 100644 index 0000000..3655119 --- /dev/null +++ b/src/test/java/com/crawlful/hub/service/OrderServiceTest.java @@ -0,0 +1,72 @@ +package com.crawlful.hub.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class OrderServiceTest { + + @Mock + private OrderRepository orderRepository; + + @InjectMocks + private OrderService orderService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void testCreateOrder() { + Map orderData = Map.of( + "platform", "Amazon", + "totalAmount", 100.0, + "currency", "USD", + "items", Map.of(), + "tenantId", "tenant1" + ); + + Map result = orderService.createOrder(orderData); + assertTrue((Boolean) result.get("success")); + assertNotNull(result.get("orderId")); + } + + @Test + void testGetOrders() { + String tenantId = "tenant1"; + Map filters = Map.of(); + assertNotNull(orderService.getOrders(tenantId, filters)); + } + + @Test + void testUpdateOrderStatus() { + String tenantId = "tenant1"; + Long orderId = 1L; + String status = "COMPLETED"; + + Map result = orderService.updateOrderStatus(tenantId, orderId, status); + assertTrue((Boolean) result.get("success")); + } + + @Test + void testBatchUpdateOrders() { + String tenantId = "tenant1"; + Map updateData = Map.of( + "status", "PROCESSING" + ); + Map filters = Map.of( + "status", "PENDING" + ); + + Map result = orderService.batchUpdateOrders(tenantId, updateData, filters); + assertTrue((Boolean) result.get("success")); + } +} diff --git a/src/test/java/com/crawlful/hub/service/ProductServiceTest.java b/src/test/java/com/crawlful/hub/service/ProductServiceTest.java new file mode 100644 index 0000000..2ac39c2 --- /dev/null +++ b/src/test/java/com/crawlful/hub/service/ProductServiceTest.java @@ -0,0 +1,68 @@ +package com.crawlful.hub.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class ProductServiceTest { + + @Mock + private ProductRepository productRepository; + + @InjectMocks + private ProductService productService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void testCreateProduct() { + Map productData = Map.of( + "title", "Test Product", + "description", "Test Description", + "price", 100.0, + "quantity", 10, + "platform", "Amazon", + "tenantId", "tenant1" + ); + + Map result = productService.createProduct(productData); + assertTrue((Boolean) result.get("success")); + assertNotNull(result.get("productId")); + } + + @Test + void testGetProducts() { + String tenantId = "tenant1"; + Map filters = Map.of(); + assertNotNull(productService.getProducts(tenantId, filters)); + } + + @Test + void testUpdateProduct() { + String tenantId = "tenant1"; + Long productId = 1L; + Map updateData = Map.of( + "title", "Updated Product", + "price", 150.0 + ); + + assertDoesNotThrow(() -> productService.updateProduct(tenantId, productId, updateData)); + } + + @Test + void testDeleteProduct() { + String tenantId = "tenant1"; + Long productId = 1L; + assertDoesNotThrow(() -> productService.deleteProduct(tenantId, productId)); + } +} diff --git a/target/classes/application.yml b/target/classes/application.yml new file mode 100644 index 0000000..1b04b46 --- /dev/null +++ b/target/classes/application.yml @@ -0,0 +1,36 @@ +spring: + datasource: + url: jdbc:mysql://localhost:3306/crawlful_hub?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC + username: root + password: 123456 + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 10 + minimum-idle: 5 + idle-timeout: 30000 + connection-timeout: 20000 + max-lifetime: 1800000 + jpa: + hibernate: + ddl-auto: update + show-sql: true + redis: + host: localhost + port: 6379 + password: + database: 0 + security: + jwt: + secret: your-secret-key + expiration: 86400000 + springdoc: + api-docs: + path: /api-docs + swagger-ui: + path: /swagger-ui.html + enabled: true + +server: + port: 3001 + servlet: + context-path: /api diff --git a/target/classes/com/crawlful/hub/Application.class b/target/classes/com/crawlful/hub/Application.class new file mode 100644 index 0000000..535e9c5 Binary files /dev/null and b/target/classes/com/crawlful/hub/Application.class differ diff --git a/target/classes/com/crawlful/hub/api/controllers/AlertController.class b/target/classes/com/crawlful/hub/api/controllers/AlertController.class new file mode 100644 index 0000000..17f7b08 Binary files /dev/null and b/target/classes/com/crawlful/hub/api/controllers/AlertController.class differ diff --git a/target/classes/com/crawlful/hub/api/controllers/AuditController.class b/target/classes/com/crawlful/hub/api/controllers/AuditController.class new file mode 100644 index 0000000..1e68a0f Binary files /dev/null and b/target/classes/com/crawlful/hub/api/controllers/AuditController.class differ diff --git a/target/classes/com/crawlful/hub/api/controllers/AuthController.class b/target/classes/com/crawlful/hub/api/controllers/AuthController.class new file mode 100644 index 0000000..cd6bb67 Binary files /dev/null and b/target/classes/com/crawlful/hub/api/controllers/AuthController.class differ diff --git a/target/classes/com/crawlful/hub/api/controllers/ConfigController.class b/target/classes/com/crawlful/hub/api/controllers/ConfigController.class new file mode 100644 index 0000000..622f738 Binary files /dev/null and b/target/classes/com/crawlful/hub/api/controllers/ConfigController.class differ diff --git a/target/classes/com/crawlful/hub/api/controllers/DataController.class b/target/classes/com/crawlful/hub/api/controllers/DataController.class new file mode 100644 index 0000000..214f881 Binary files /dev/null and b/target/classes/com/crawlful/hub/api/controllers/DataController.class differ diff --git a/target/classes/com/crawlful/hub/api/controllers/LogisticsController.class b/target/classes/com/crawlful/hub/api/controllers/LogisticsController.class new file mode 100644 index 0000000..d5244d6 Binary files /dev/null and b/target/classes/com/crawlful/hub/api/controllers/LogisticsController.class differ diff --git a/target/classes/com/crawlful/hub/api/controllers/MonitoringController.class b/target/classes/com/crawlful/hub/api/controllers/MonitoringController.class new file mode 100644 index 0000000..619fbb2 Binary files /dev/null and b/target/classes/com/crawlful/hub/api/controllers/MonitoringController.class differ diff --git a/target/classes/com/crawlful/hub/api/controllers/OrderController.class b/target/classes/com/crawlful/hub/api/controllers/OrderController.class new file mode 100644 index 0000000..d13a98a Binary files /dev/null and b/target/classes/com/crawlful/hub/api/controllers/OrderController.class differ diff --git a/target/classes/com/crawlful/hub/api/controllers/PaymentController.class b/target/classes/com/crawlful/hub/api/controllers/PaymentController.class new file mode 100644 index 0000000..4ceb755 Binary files /dev/null and b/target/classes/com/crawlful/hub/api/controllers/PaymentController.class differ diff --git a/target/classes/com/crawlful/hub/api/controllers/ProductController.class b/target/classes/com/crawlful/hub/api/controllers/ProductController.class new file mode 100644 index 0000000..e151a94 Binary files /dev/null and b/target/classes/com/crawlful/hub/api/controllers/ProductController.class differ diff --git a/target/classes/com/crawlful/hub/api/controllers/ReportController.class b/target/classes/com/crawlful/hub/api/controllers/ReportController.class new file mode 100644 index 0000000..3111592 Binary files /dev/null and b/target/classes/com/crawlful/hub/api/controllers/ReportController.class differ diff --git a/target/classes/com/crawlful/hub/api/controllers/UserController.class b/target/classes/com/crawlful/hub/api/controllers/UserController.class new file mode 100644 index 0000000..8277336 Binary files /dev/null and b/target/classes/com/crawlful/hub/api/controllers/UserController.class differ diff --git a/target/classes/com/crawlful/hub/config/CacheConfig.class b/target/classes/com/crawlful/hub/config/CacheConfig.class new file mode 100644 index 0000000..8e10f65 Binary files /dev/null and b/target/classes/com/crawlful/hub/config/CacheConfig.class differ diff --git a/target/classes/com/crawlful/hub/config/InternationalizationConfig.class b/target/classes/com/crawlful/hub/config/InternationalizationConfig.class new file mode 100644 index 0000000..6d705d1 Binary files /dev/null and b/target/classes/com/crawlful/hub/config/InternationalizationConfig.class differ diff --git a/target/classes/com/crawlful/hub/config/OpenApiConfig.class b/target/classes/com/crawlful/hub/config/OpenApiConfig.class new file mode 100644 index 0000000..fb04670 Binary files /dev/null and b/target/classes/com/crawlful/hub/config/OpenApiConfig.class differ diff --git a/target/classes/com/crawlful/hub/config/RateLimitFilter$RequestRate.class b/target/classes/com/crawlful/hub/config/RateLimitFilter$RequestRate.class new file mode 100644 index 0000000..13a9ff7 Binary files /dev/null and b/target/classes/com/crawlful/hub/config/RateLimitFilter$RequestRate.class differ diff --git a/target/classes/com/crawlful/hub/config/RateLimitFilter.class b/target/classes/com/crawlful/hub/config/RateLimitFilter.class new file mode 100644 index 0000000..f328698 Binary files /dev/null and b/target/classes/com/crawlful/hub/config/RateLimitFilter.class differ diff --git a/target/classes/com/crawlful/hub/config/SecurityConfig.class b/target/classes/com/crawlful/hub/config/SecurityConfig.class new file mode 100644 index 0000000..0a2a189 Binary files /dev/null and b/target/classes/com/crawlful/hub/config/SecurityConfig.class differ diff --git a/target/classes/com/crawlful/hub/exception/BusinessException.class b/target/classes/com/crawlful/hub/exception/BusinessException.class new file mode 100644 index 0000000..030a2ec Binary files /dev/null and b/target/classes/com/crawlful/hub/exception/BusinessException.class differ diff --git a/target/classes/com/crawlful/hub/exception/GlobalExceptionHandler.class b/target/classes/com/crawlful/hub/exception/GlobalExceptionHandler.class new file mode 100644 index 0000000..9ea99af Binary files /dev/null and b/target/classes/com/crawlful/hub/exception/GlobalExceptionHandler.class differ diff --git a/target/classes/com/crawlful/hub/model/Alert.class b/target/classes/com/crawlful/hub/model/Alert.class new file mode 100644 index 0000000..12421b5 Binary files /dev/null and b/target/classes/com/crawlful/hub/model/Alert.class differ diff --git a/target/classes/com/crawlful/hub/model/Audit.class b/target/classes/com/crawlful/hub/model/Audit.class new file mode 100644 index 0000000..4c33295 Binary files /dev/null and b/target/classes/com/crawlful/hub/model/Audit.class differ diff --git a/target/classes/com/crawlful/hub/model/Config.class b/target/classes/com/crawlful/hub/model/Config.class new file mode 100644 index 0000000..5a9c942 Binary files /dev/null and b/target/classes/com/crawlful/hub/model/Config.class differ diff --git a/target/classes/com/crawlful/hub/model/Logistics.class b/target/classes/com/crawlful/hub/model/Logistics.class new file mode 100644 index 0000000..45ebeb7 Binary files /dev/null and b/target/classes/com/crawlful/hub/model/Logistics.class differ diff --git a/target/classes/com/crawlful/hub/model/Order.class b/target/classes/com/crawlful/hub/model/Order.class new file mode 100644 index 0000000..de8af25 Binary files /dev/null and b/target/classes/com/crawlful/hub/model/Order.class differ diff --git a/target/classes/com/crawlful/hub/model/Payment.class b/target/classes/com/crawlful/hub/model/Payment.class new file mode 100644 index 0000000..842f3ad Binary files /dev/null and b/target/classes/com/crawlful/hub/model/Payment.class differ diff --git a/target/classes/com/crawlful/hub/model/Product.class b/target/classes/com/crawlful/hub/model/Product.class new file mode 100644 index 0000000..c77561f Binary files /dev/null and b/target/classes/com/crawlful/hub/model/Product.class differ diff --git a/target/classes/com/crawlful/hub/model/User.class b/target/classes/com/crawlful/hub/model/User.class new file mode 100644 index 0000000..769e083 Binary files /dev/null and b/target/classes/com/crawlful/hub/model/User.class differ diff --git a/target/classes/com/crawlful/hub/service/AlertRepository.class b/target/classes/com/crawlful/hub/service/AlertRepository.class new file mode 100644 index 0000000..ef4a104 Binary files /dev/null and b/target/classes/com/crawlful/hub/service/AlertRepository.class differ diff --git a/target/classes/com/crawlful/hub/service/AlertService.class b/target/classes/com/crawlful/hub/service/AlertService.class new file mode 100644 index 0000000..ccd63de Binary files /dev/null and b/target/classes/com/crawlful/hub/service/AlertService.class differ diff --git a/target/classes/com/crawlful/hub/service/AuditRepository.class b/target/classes/com/crawlful/hub/service/AuditRepository.class new file mode 100644 index 0000000..b28dc60 Binary files /dev/null and b/target/classes/com/crawlful/hub/service/AuditRepository.class differ diff --git a/target/classes/com/crawlful/hub/service/AuditService.class b/target/classes/com/crawlful/hub/service/AuditService.class new file mode 100644 index 0000000..1f29b7b Binary files /dev/null and b/target/classes/com/crawlful/hub/service/AuditService.class differ diff --git a/target/classes/com/crawlful/hub/service/AuthService.class b/target/classes/com/crawlful/hub/service/AuthService.class new file mode 100644 index 0000000..c693a17 Binary files /dev/null and b/target/classes/com/crawlful/hub/service/AuthService.class differ diff --git a/target/classes/com/crawlful/hub/service/ConfigRepository.class b/target/classes/com/crawlful/hub/service/ConfigRepository.class new file mode 100644 index 0000000..335748b Binary files /dev/null and b/target/classes/com/crawlful/hub/service/ConfigRepository.class differ diff --git a/target/classes/com/crawlful/hub/service/ConfigService.class b/target/classes/com/crawlful/hub/service/ConfigService.class new file mode 100644 index 0000000..37f7a07 Binary files /dev/null and b/target/classes/com/crawlful/hub/service/ConfigService.class differ diff --git a/target/classes/com/crawlful/hub/service/DataService.class b/target/classes/com/crawlful/hub/service/DataService.class new file mode 100644 index 0000000..c6feff3 Binary files /dev/null and b/target/classes/com/crawlful/hub/service/DataService.class differ diff --git a/target/classes/com/crawlful/hub/service/LogisticsRepository.class b/target/classes/com/crawlful/hub/service/LogisticsRepository.class new file mode 100644 index 0000000..c63375c Binary files /dev/null and b/target/classes/com/crawlful/hub/service/LogisticsRepository.class differ diff --git a/target/classes/com/crawlful/hub/service/LogisticsService.class b/target/classes/com/crawlful/hub/service/LogisticsService.class new file mode 100644 index 0000000..c8e53e0 Binary files /dev/null and b/target/classes/com/crawlful/hub/service/LogisticsService.class differ diff --git a/target/classes/com/crawlful/hub/service/MonitoringService.class b/target/classes/com/crawlful/hub/service/MonitoringService.class new file mode 100644 index 0000000..29beb61 Binary files /dev/null and b/target/classes/com/crawlful/hub/service/MonitoringService.class differ diff --git a/target/classes/com/crawlful/hub/service/OrderRepository.class b/target/classes/com/crawlful/hub/service/OrderRepository.class new file mode 100644 index 0000000..59f21df Binary files /dev/null and b/target/classes/com/crawlful/hub/service/OrderRepository.class differ diff --git a/target/classes/com/crawlful/hub/service/OrderService.class b/target/classes/com/crawlful/hub/service/OrderService.class new file mode 100644 index 0000000..2061cdf Binary files /dev/null and b/target/classes/com/crawlful/hub/service/OrderService.class differ diff --git a/target/classes/com/crawlful/hub/service/PaymentRepository.class b/target/classes/com/crawlful/hub/service/PaymentRepository.class new file mode 100644 index 0000000..6d7ba59 Binary files /dev/null and b/target/classes/com/crawlful/hub/service/PaymentRepository.class differ diff --git a/target/classes/com/crawlful/hub/service/PaymentService.class b/target/classes/com/crawlful/hub/service/PaymentService.class new file mode 100644 index 0000000..bafea2b Binary files /dev/null and b/target/classes/com/crawlful/hub/service/PaymentService.class differ diff --git a/target/classes/com/crawlful/hub/service/ProductRepository.class b/target/classes/com/crawlful/hub/service/ProductRepository.class new file mode 100644 index 0000000..906cc7a Binary files /dev/null and b/target/classes/com/crawlful/hub/service/ProductRepository.class differ diff --git a/target/classes/com/crawlful/hub/service/ProductService.class b/target/classes/com/crawlful/hub/service/ProductService.class new file mode 100644 index 0000000..4df6e4b Binary files /dev/null and b/target/classes/com/crawlful/hub/service/ProductService.class differ diff --git a/target/classes/com/crawlful/hub/service/ReportService.class b/target/classes/com/crawlful/hub/service/ReportService.class new file mode 100644 index 0000000..eae0503 Binary files /dev/null and b/target/classes/com/crawlful/hub/service/ReportService.class differ diff --git a/target/classes/com/crawlful/hub/service/UserRepository.class b/target/classes/com/crawlful/hub/service/UserRepository.class new file mode 100644 index 0000000..827709c Binary files /dev/null and b/target/classes/com/crawlful/hub/service/UserRepository.class differ diff --git a/target/classes/com/crawlful/hub/service/UserService.class b/target/classes/com/crawlful/hub/service/UserService.class new file mode 100644 index 0000000..4c35210 Binary files /dev/null and b/target/classes/com/crawlful/hub/service/UserService.class differ diff --git a/target/classes/com/crawlful/hub/util/ValidationUtil.class b/target/classes/com/crawlful/hub/util/ValidationUtil.class new file mode 100644 index 0000000..180617c Binary files /dev/null and b/target/classes/com/crawlful/hub/util/ValidationUtil.class differ diff --git a/target/classes/db/migration/V1__init_schema.sql b/target/classes/db/migration/V1__init_schema.sql new file mode 100644 index 0000000..1210603 --- /dev/null +++ b/target/classes/db/migration/V1__init_schema.sql @@ -0,0 +1,130 @@ +-- 创建用户表 +CREATE TABLE IF NOT EXISTS cf_user ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255) NOT NULL, + username VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + role VARCHAR(50), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- 创建商品表 +CREATE TABLE IF NOT EXISTS cf_product ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255) NOT NULL, + shop_id VARCHAR(255), + title VARCHAR(255) NOT NULL, + description TEXT, + main_image VARCHAR(255), + platform VARCHAR(50), + platform_product_id VARCHAR(255), + price DECIMAL(10,2), + cost_price DECIMAL(10,2), + quantity INT, + status VARCHAR(50), + phash VARCHAR(255), + semantic_hash VARCHAR(255), + vector_embedding TEXT, + attributes JSON, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- 创建订单表 +CREATE TABLE IF NOT EXISTS cf_order ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255) NOT NULL, + shop_id VARCHAR(255), + platform VARCHAR(50), + platform_order_id VARCHAR(255), + status VARCHAR(50), + total_amount DECIMAL(10,2), + currency VARCHAR(10), + customer_info JSON, + items JSON, + shipping_address JSON, + tracking_number VARCHAR(255), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- 创建支付表 +CREATE TABLE IF NOT EXISTS cf_payment ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255) NOT NULL, + order_id BIGINT, + payment_method VARCHAR(50), + amount DECIMAL(10,2), + currency VARCHAR(10), + status VARCHAR(50), + transaction_id VARCHAR(255), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (order_id) REFERENCES cf_order(id) +); + +-- 创建物流表 +CREATE TABLE IF NOT EXISTS cf_logistics ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255) NOT NULL, + order_id BIGINT, + shipping_method VARCHAR(50), + tracking_number VARCHAR(255), + carrier VARCHAR(50), + status VARCHAR(50), + estimated_delivery_date DATETIME, + actual_delivery_date DATETIME, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (order_id) REFERENCES cf_order(id) +); + +-- 创建配置表 +CREATE TABLE IF NOT EXISTS cf_config ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255), + shop_id VARCHAR(255), + config_key VARCHAR(255) NOT NULL, + config_value VARCHAR(255) NOT NULL, + config_type VARCHAR(50), + description TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- 创建审计表 +CREATE TABLE IF NOT EXISTS cf_audit ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255), + shop_id VARCHAR(255), + user_id BIGINT, + action VARCHAR(255), + resource_type VARCHAR(255), + resource_id VARCHAR(255), + ip_address VARCHAR(100), + user_agent TEXT, + details TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- 创建索引 +CREATE INDEX IF NOT EXISTS idx_user_tenant_id ON cf_user(tenant_id); +CREATE INDEX IF NOT EXISTS idx_product_tenant_id ON cf_product(tenant_id); +CREATE INDEX IF NOT EXISTS idx_product_platform ON cf_product(platform); +CREATE INDEX IF NOT EXISTS idx_order_tenant_id ON cf_order(tenant_id); +CREATE INDEX IF NOT EXISTS idx_order_platform ON cf_order(platform); +CREATE INDEX IF NOT EXISTS idx_payment_tenant_id ON cf_payment(tenant_id); +CREATE INDEX IF NOT EXISTS idx_payment_order_id ON cf_payment(order_id); +CREATE INDEX IF NOT EXISTS idx_logistics_tenant_id ON cf_logistics(tenant_id); +CREATE INDEX IF NOT EXISTS idx_logistics_order_id ON cf_logistics(order_id); +CREATE INDEX IF NOT EXISTS idx_config_tenant_id ON cf_config(tenant_id); +CREATE INDEX IF NOT EXISTS idx_config_shop_id ON cf_config(shop_id); +CREATE INDEX IF NOT EXISTS idx_config_key ON cf_config(config_key); +CREATE INDEX IF NOT EXISTS idx_audit_tenant_id ON cf_audit(tenant_id); +CREATE INDEX IF NOT EXISTS idx_audit_shop_id ON cf_audit(shop_id); +CREATE INDEX IF NOT EXISTS idx_audit_user_id ON cf_audit(user_id); +CREATE INDEX IF NOT EXISTS idx_audit_action ON cf_audit(action); +CREATE INDEX IF NOT EXISTS idx_audit_resource_type ON cf_audit(resource_type); +CREATE INDEX IF NOT EXISTS idx_audit_created_at ON cf_audit(created_at); diff --git a/target/classes/db/migration/V2__add_alert_table.sql b/target/classes/db/migration/V2__add_alert_table.sql new file mode 100644 index 0000000..63faf24 --- /dev/null +++ b/target/classes/db/migration/V2__add_alert_table.sql @@ -0,0 +1,21 @@ +-- 创建告警表 +CREATE TABLE IF NOT EXISTS cf_alert ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + tenant_id VARCHAR(255) NOT NULL, + alert_type VARCHAR(50), + severity VARCHAR(50), + message TEXT, + status VARCHAR(50), + source VARCHAR(255), + threshold VARCHAR(255), + actual_value VARCHAR(255), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + resolved_at DATETIME +); + +-- 为告警表添加索引 +CREATE INDEX IF NOT EXISTS idx_alert_tenant_id ON cf_alert(tenant_id); +CREATE INDEX IF NOT EXISTS idx_alert_status ON cf_alert(status); +CREATE INDEX IF NOT EXISTS idx_alert_severity ON cf_alert(severity); +CREATE INDEX IF NOT EXISTS idx_alert_alert_type ON cf_alert(alert_type); +CREATE INDEX IF NOT EXISTS idx_alert_created_at ON cf_alert(created_at); diff --git a/target/classes/i18n/messages.properties b/target/classes/i18n/messages.properties new file mode 100644 index 0000000..28e38f5 --- /dev/null +++ b/target/classes/i18n/messages.properties @@ -0,0 +1,62 @@ +# Authentication messages +auth.register.success=User registered successfully +auth.login.success=Login successful +auth.login.failure=Invalid username or password +auth.username.required=Username is required +auth.password.required=Password is required +auth.email.required=Email is required +auth.email.invalid=Invalid email format +auth.password.minlength=Password must be at least 6 characters +auth.username.minlength=Username must be at least 3 characters +auth.username.exists=Username already exists + +# Product messages +product.create.success=Product created successfully +product.update.success=Product updated successfully +product.delete.success=Product deleted successfully +product.not.found=Product not found +product.title.required=Product title is required +product.price.required=Product price is required +product.price.positive=Product price must be positive + +# Order messages +order.create.success=Order created successfully +order.update.success=Order updated successfully +order.not.found=Order not found +order.status.updated=Order status updated successfully + +# Payment messages +payment.create.success=Payment created successfully +payment.update.success=Payment updated successfully +payment.not.found=Payment not found +payment.status.updated=Payment status updated successfully + +# Logistics messages +logistics.create.success=Logistics created successfully +logistics.update.success=Logistics updated successfully +logistics.not.found=Logistics not found +logistics.status.updated=Logistics status updated successfully + +# Alert messages +alert.create.success=Alert created successfully +alert.update.success=Alert updated successfully +alert.resolve.success=Alert resolved successfully +alert.not.found=Alert not found + +# Monitoring messages +monitoring.health.ok=System health is OK +monitoring.health.error=System health check failed +monitoring.metrics.success=Performance metrics retrieved successfully +monitoring.services.success=Service status retrieved successfully +monitoring.database.success=Database status retrieved successfully +monitoring.cache.success=Cache status retrieved successfully +monitoring.stats.success=System stats retrieved successfully + +# Common messages +common.success=Operation successful +common.error=Operation failed +common.invalid.request=Invalid request +common.missing.parameter=Missing required parameter +common.not.found=Resource not found +common.access.denied=Access denied +common.server.error=Server internal error diff --git a/target/classes/i18n/messages_zh.properties b/target/classes/i18n/messages_zh.properties new file mode 100644 index 0000000..78a3c2d --- /dev/null +++ b/target/classes/i18n/messages_zh.properties @@ -0,0 +1,62 @@ +# Authentication messages +auth.register.success=用户注册成功 +auth.login.success=登录成功 +auth.login.failure=用户名或密码错误 +auth.username.required=用户名不能为空 +auth.password.required=密码不能为空 +auth.email.required=邮箱不能为空 +auth.email.invalid=邮箱格式错误 +auth.password.minlength=密码至少6个字符 +auth.username.minlength=用户名至少3个字符 +auth.username.exists=用户名已存在 + +# Product messages +product.create.success=商品创建成功 +product.update.success=商品更新成功 +product.delete.success=商品删除成功 +product.not.found=商品不存在 +product.title.required=商品标题不能为空 +product.price.required=商品价格不能为空 +product.price.positive=商品价格必须大于0 + +# Order messages +order.create.success=订单创建成功 +order.update.success=订单更新成功 +order.not.found=订单不存在 +order.status.updated=订单状态更新成功 + +# Payment messages +payment.create.success=支付创建成功 +payment.update.success=支付更新成功 +payment.not.found=支付不存在 +payment.status.updated=支付状态更新成功 + +# Logistics messages +logistics.create.success=物流创建成功 +logistics.update.success=物流更新成功 +logistics.not.found=物流不存在 +logistics.status.updated=物流状态更新成功 + +# Alert messages +alert.create.success=告警创建成功 +alert.update.success=告警更新成功 +alert.resolve.success=告警已解决 +alert.not.found=告警不存在 + +# Monitoring messages +monitoring.health.ok=系统健康状态良好 +monitoring.health.error=系统健康检查失败 +monitoring.metrics.success=性能指标获取成功 +monitoring.services.success=服务状态获取成功 +monitoring.database.success=数据库状态获取成功 +monitoring.cache.success=缓存状态获取成功 +monitoring.stats.success=系统统计信息获取成功 + +# Common messages +common.success=操作成功 +common.error=操作失败 +common.invalid.request=无效的请求 +common.missing.parameter=缺少必要参数 +common.not.found=资源不存在 +common.access.denied=访问被拒绝 +common.server.error=服务器内部错误 diff --git a/target/classes/logback.xml b/target/classes/logback.xml new file mode 100644 index 0000000..cb255d0 --- /dev/null +++ b/target/classes/logback.xml @@ -0,0 +1,83 @@ + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + ${LOG_HOME}/app.log + + ${LOG_HOME}/app-%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + ${LOG_HOME}/error.log + + ${LOG_HOME}/error-%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + ERROR + ACCEPT + DENY + + + + + + ${LOG_HOME}/business.log + + ${LOG_HOME}/business-%d{yyyy-MM-dd}.log + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/target/test-classes/com/crawlful/hub/integration/SystemIntegrationTest.class b/target/test-classes/com/crawlful/hub/integration/SystemIntegrationTest.class new file mode 100644 index 0000000..9985528 Binary files /dev/null and b/target/test-classes/com/crawlful/hub/integration/SystemIntegrationTest.class differ diff --git a/target/test-classes/com/crawlful/hub/service/AuthServiceTest.class b/target/test-classes/com/crawlful/hub/service/AuthServiceTest.class new file mode 100644 index 0000000..380d6fd Binary files /dev/null and b/target/test-classes/com/crawlful/hub/service/AuthServiceTest.class differ diff --git a/target/test-classes/com/crawlful/hub/service/OrderServiceTest.class b/target/test-classes/com/crawlful/hub/service/OrderServiceTest.class new file mode 100644 index 0000000..5bf85b5 Binary files /dev/null and b/target/test-classes/com/crawlful/hub/service/OrderServiceTest.class differ diff --git a/target/test-classes/com/crawlful/hub/service/ProductServiceTest.class b/target/test-classes/com/crawlful/hub/service/ProductServiceTest.class new file mode 100644 index 0000000..1b7c25e Binary files /dev/null and b/target/test-classes/com/crawlful/hub/service/ProductServiceTest.class differ