Browse Source

feat(openapi): 构建开放平台基础架构与业务系统数据整合

- 新增开放平台应用信息实体及Mapper,实现应用信息管理
- 新增API调用日志实体及Mapper,记录API调用详情
- 提供统一API响应数据结构ApiResponse,支持泛型返回
- 实现API签名拦截器,保障请求安全,防止重放攻击和伪造请求
- 引入业务系统充电站、充电订单相关实体、Mapper及服务层,支持多数据源访问
- 开发充电桩控制器,提供充电桩列表、详情及状态查询接口
- 设计数据库架构文档,详细说明开放平台与业务系统数据库分离方案
- 配置多环境数据源与MyBatis Plus,完成动态切换及性能优化
- 添加跨域配置,允许跨域调用支持多种HTTP方法
- 编写业务异常类,实现统一业务异常处理机制
- 完善application.yml及环境专用配置,设置端口、跨域、安全及日志详细参数
- 配置.gitignore,忽略常见编译文件及IDE配置,保持代码库整洁
SheepHy 1 tuần trước cách đây
commit
9daee7c269
35 tập tin đã thay đổi với 2726 bổ sung0 xóa
  1. 82 0
      .gitignore
  2. 241 0
      DATABASE_ARCHITECTURE.md
  3. 28 0
      Dockerfile
  4. 303 0
      README.md
  5. 61 0
      docker-compose.yml
  6. 186 0
      pom.xml
  7. 22 0
      src/main/java/com/zsElectric/openapi/ZsElectricOpenApiApplication.java
  8. 118 0
      src/main/java/com/zsElectric/openapi/business/entity/ChargingOrder.java
  9. 99 0
      src/main/java/com/zsElectric/openapi/business/entity/ChargingStation.java
  10. 18 0
      src/main/java/com/zsElectric/openapi/business/mapper/ChargingOrderMapper.java
  11. 18 0
      src/main/java/com/zsElectric/openapi/business/mapper/ChargingStationMapper.java
  12. 122 0
      src/main/java/com/zsElectric/openapi/business/service/ChargingOrderService.java
  13. 62 0
      src/main/java/com/zsElectric/openapi/business/service/ChargingStationService.java
  14. 111 0
      src/main/java/com/zsElectric/openapi/common/Result.java
  15. 87 0
      src/main/java/com/zsElectric/openapi/common/ResultCode.java
  16. 21 0
      src/main/java/com/zsElectric/openapi/common/annotation/DS.java
  17. 42 0
      src/main/java/com/zsElectric/openapi/common/exception/BusinessException.java
  18. 69 0
      src/main/java/com/zsElectric/openapi/common/exception/GlobalExceptionHandler.java
  19. 33 0
      src/main/java/com/zsElectric/openapi/config/CorsConfig.java
  20. 40 0
      src/main/java/com/zsElectric/openapi/config/OpenApiConfig.java
  21. 32 0
      src/main/java/com/zsElectric/openapi/config/WebMvcConfig.java
  22. 105 0
      src/main/java/com/zsElectric/openapi/controller/ChargingController.java
  23. 34 0
      src/main/java/com/zsElectric/openapi/controller/HealthController.java
  24. 103 0
      src/main/java/com/zsElectric/openapi/controller/OrderController.java
  25. 50 0
      src/main/java/com/zsElectric/openapi/controller/TokenController.java
  26. 51 0
      src/main/java/com/zsElectric/openapi/dto/ApiResponse.java
  27. 84 0
      src/main/java/com/zsElectric/openapi/entity/ApiLog.java
  28. 95 0
      src/main/java/com/zsElectric/openapi/entity/AppInfo.java
  29. 15 0
      src/main/java/com/zsElectric/openapi/mapper/ApiLogMapper.java
  30. 15 0
      src/main/java/com/zsElectric/openapi/mapper/AppInfoMapper.java
  31. 102 0
      src/main/java/com/zsElectric/openapi/security/ApiSignatureInterceptor.java
  32. 72 0
      src/main/resources/application-dev.yml
  33. 95 0
      src/main/resources/application-prod.yml
  34. 46 0
      src/main/resources/application.yml
  35. 64 0
      src/main/resources/sql/init.sql

+ 82 - 0
.gitignore

@@ -0,0 +1,82 @@
+# Compiled class files
+*.class
+
+# Log files
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# Virtual machine crash logs
+hs_err_pid*
+replay_pid*
+
+# Maven
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+.mvn/wrapper/maven-wrapper.jar
+
+# IntelliJ IDEA
+.idea/
+*.iws
+*.iml
+*.ipr
+out/
+
+# Eclipse
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+# NetBeans
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+# VS Code
+.vscode/
+
+# Mac
+.DS_Store
+
+# Windows
+Thumbs.db
+ehthumbs.db
+Desktop.ini
+
+# Application specific
+application-local.yml
+
+# Logs directory
+logs/
+
+# Ignore .git directory itself
+.git/

+ 241 - 0
DATABASE_ARCHITECTURE.md

@@ -0,0 +1,241 @@
+# 数据库架构设计说明
+
+## 概述
+
+zsElectric-OpenApi 开放平台采用**双数据库架构**,将开放平台数据和业务系统数据进行物理隔离,同时通过动态数据源实现跨数据库访问。
+
+## 数据库设计
+
+### 1. 开放平台独立数据库 (zs_electric_openapi)
+
+这是开放平台专用的数据库,存储以下数据:
+
+- **应用管理数据**: 第三方应用信息(AppId、AppSecret、权限等)
+- **API调用日志**: 所有第三方API调用记录
+- **访问控制数据**: IP白名单、调用频率限制等
+
+**优点**:
+- 数据隔离,互不影响
+- 独立备份和恢复
+- 便于权限管控
+- 性能优化
+
+### 2. 业务系统数据库 (zs_electric)
+
+这是原有的业务系统数据库,开放平台通过动态数据源**只读访问**该数据库:
+
+- 充电站信息
+- 充电订单数据
+- 用户数据
+- 其他业务数据
+
+**访问方式**:
+- 只读访问,确保业务系统数据安全
+- 通过 `@DS("business")` 注解指定数据源
+- 使用独立的Service层封装业务数据访问
+
+## 数据源配置
+
+### 开发环境 (application-dev.yml)
+
+```yaml
+spring:
+  datasource:
+    dynamic:
+      primary: openapi  # 默认使用开放平台数据库
+      strict: false
+      datasource:
+        # 开放平台独立数据库
+        openapi:
+          url: jdbc:mysql://localhost:3306/zs_electric_openapi
+          username: root
+          password: root
+
+        # 业务系统数据库(只读)
+        business:
+          url: jdbc:mysql://localhost:3306/zs_electric
+          username: root
+          password: root
+```
+
+### 生产环境 (application-prod.yml)
+
+```yaml
+spring:
+  datasource:
+    dynamic:
+      primary: openapi
+      datasource:
+        openapi:
+          url: jdbc:mysql://prod-db-host:3306/zs_electric_openapi
+          username: openapi_user  # 专用账号,仅拥有openapi数据库权限
+          password: xxxxx
+
+        business:
+          url: jdbc:mysql://prod-db-host:3306/zs_electric
+          username: business_readonly_user  # 只读账号
+          password: xxxxx
+```
+
+## 数据源使用方式
+
+### 1. 使用默认数据源(openapi)
+
+对于开放平台自己的实体类(如AppInfo、ApiLog),不需要任何注解:
+
+```java
+@Data
+@TableName("openapi_app_info")
+public class AppInfo {
+    // 使用默认数据源(openapi)
+}
+
+@Mapper
+public interface AppInfoMapper extends BaseMapper<AppInfo> {
+    // 使用默认数据源(openapi)
+}
+
+@Service
+public class AppInfoService extends ServiceImpl<AppInfoMapper, AppInfo> {
+    // 使用默认数据源(openapi)
+}
+```
+
+### 2. 使用业务数据库(business)
+
+对于需要访问业务系统数据的实体类,使用 `@DS("business")` 注解:
+
+```java
+@Data
+@TableName("charging_station")
+public class ChargingStation {
+    // 实体类定义
+}
+
+@Mapper
+@DS("business")  // 指定使用业务数据库
+public interface ChargingStationMapper extends BaseMapper<ChargingStation> {
+}
+
+@Service
+@DS("business")  // Service层也指定
+public class ChargingStationService extends ServiceImpl<ChargingStationMapper, ChargingStation> {
+    public List<ChargingStation> getActiveStations() {
+        // 查询业务数据库
+        return lambdaQuery()
+            .eq(ChargingStation::getStatus, 1)
+            .list();
+    }
+}
+```
+
+### 3. Controller层调用
+
+Controller层直接调用对应的Service即可,数据源切换由Service层的注解控制:
+
+```java
+@RestController
+@RequestMapping("/api/v1/charging")
+@RequiredArgsConstructor
+public class ChargingController {
+
+    private final ChargingStationService chargingStationService;  // 使用business数据源
+    private final ApiLogMapper apiLogMapper;  // 使用openapi数据源
+
+    @GetMapping("/stations")
+    public Result<Map<String, Object>> getStationList() {
+        // 调用业务系统数据
+        List<ChargingStation> stations = chargingStationService.getActiveStations();
+
+        // 记录到开放平台数据库
+        ApiLog apiLog = new ApiLog();
+        // ... 设置日志信息
+        apiLogMapper.insert(apiLog);
+
+        return Result.success(stations);
+    }
+}
+```
+
+## 代码结构
+
+```
+com.zsElectric.openapi/
+├── entity/              # 开放平台实体(使用openapi数据源)
+│   ├── AppInfo.java
+│   └── ApiLog.java
+├── mapper/              # 开放平台Mapper(使用openapi数据源)
+│   ├── AppInfoMapper.java
+│   └── ApiLogMapper.java
+├── business/            # 业务系统相关(使用business数据源)
+│   ├── entity/
+│   │   ├── ChargingStation.java
+│   │   └── ChargingOrder.java
+│   ├── mapper/
+│   │   ├── ChargingStationMapper.java
+│   │   └── ChargingOrderMapper.java
+│   └── service/
+│       ├── ChargingStationService.java
+│       └── ChargingOrderService.java
+└── controller/          # 控制器
+    ├── ChargingController.java
+    └── OrderController.java
+```
+
+## 数据安全
+
+### 1. 权限控制
+
+生产环境应为不同的数据库创建专门的账号:
+
+```sql
+-- 开放平台数据库账号
+CREATE USER 'openapi_user'@'%' IDENTIFIED BY 'password';
+GRANT SELECT, INSERT, UPDATE, DELETE ON zs_electric_openapi.* TO 'openapi_user'@'%';
+
+-- 业务系统只读账号
+CREATE USER 'business_readonly_user'@'%' IDENTIFIED BY 'password';
+GRANT SELECT ON zs_electric.* TO 'business_readonly_user'@'%';
+```
+
+### 2. 访问限制
+
+- 开放平台只能**只读访问**业务数据库
+- 不允许开放平台修改业务系统数据
+- 所有的写操作都在开放平台自己的数据库中
+
+### 3. 监控审计
+
+- 所有API调用都记录日志
+- 可以追踪每个应用的访问记录
+- 异常访问及时发现和处理
+
+## 优势总结
+
+1. **数据隔离**: 开放平台和业务系统完全解耦
+2. **安全性高**: 业务系统数据不会被第三方误操作
+3. **可维护性强**: 便于独立升级和维护
+4. **性能优化**: 可以针对不同的数据库进行优化
+5. **灵活扩展**: 未来可以轻松添加更多数据源
+
+## 常见问题
+
+### Q1: 为什么不直接使用业务数据库?
+
+A: 考虑到安全性、性能和可维护性,开放平台应该有独立的数据库,同时通过动态数据源访问业务数据。
+
+### Q2: 开放平台能否写业务数据库?
+
+A: 不能。生产环境应该使用只读账号访问业务数据库,所有写操作都在开放平台自己的数据库中。
+
+### Q3: 如何在同一个方法中使用多个数据源?
+
+A: 不能在同一个方法中直接切换数据源。需要将不同数据源的操作拆分到不同的Service中。
+
+### Q4: 性能如何?
+
+A: 动态数据源切换的性能损耗非常小,可以忽略不计。通过连接池管理,可以保证高性能。
+
+### Q5: 如何处理事务?
+
+A: 如果需要跨数据源事务,建议使用分布式事务方案,如Seata。但对于开放平台场景,通常不需要跨数据源事务。

+ 28 - 0
Dockerfile

@@ -0,0 +1,28 @@
+FROM openjdk:17-jdk-slim
+
+LABEL maintainer="Ray.Hao <support@zselectric.com>"
+LABEL description="zsElectric Open Platform API Service"
+
+# 设置工作目录
+WORKDIR /app
+
+# 添加时区数据
+RUN apt-get update && apt-get install -y tzdata && \
+    ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
+    echo "Asia/Shanghai" > /etc/timezone && \
+    apt-get clean && rm -rf /var/lib/apt/lists/*
+
+# 复制jar包
+COPY target/zsElectric-openapi.jar app.jar
+
+# 创建日志目录
+RUN mkdir -p /var/log/zsElectric-openapi
+
+# 设置JVM参数
+ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
+
+# 暴露端口
+EXPOSE 8081
+
+# 启动应用
+ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]

+ 303 - 0
README.md

@@ -0,0 +1,303 @@
+# zsElectric-OpenApi 开放平台服务
+
+## 项目简介
+
+zsElectric-OpenApi 是一个独立的开放平台API服务,提供第三方应用调用的接口服务。该服务可以独立部署到服务器上,对外提供RESTful API接口。
+
+## 技术栈
+
+- **Java**: 17
+- **Spring Boot**: 3.5.6
+- **MyBatis Plus**: 3.5.5
+- **Druid**: 1.2.24
+- **Hutool**: 5.8.34
+- **Knife4j**: 4.5.0 (API文档)
+- **MySQL**: 8.0+
+- **Lombok**: 工具类
+
+## 项目结构
+
+```
+zsElectric-openapi/
+├── src/
+│   ├── main/
+│   │   ├── java/com/zsElectric/openapi/
+│   │   │   ├── config/          # 配置类
+│   │   │   ├── controller/      # 控制器
+│   │   │   ├── service/         # 服务层
+│   │   │   ├── entity/          # 实体类
+│   │   │   ├── mapper/          # MyBatis Mapper
+│   │   │   ├── common/          # 公共类
+│   │   │   ├── dto/             # 数据传输对象
+│   │   │   ├── vo/              # 视图对象
+│   │   │   ├── security/        # 安全相关
+│   │   │   └── ZsElectricOpenApiApplication.java
+│   │   └── resources/
+│   │       ├── application.yml          # 主配置文件
+│   │       ├── application-dev.yml      # 开发环境配置
+│   │       ├── application-prod.yml     # 生产环境配置
+│   │       └── mapper/                  # MyBatis映射文件
+│   └── test/
+├── pom.xml
+└── README.md
+```
+
+## 快速开始
+
+### 1. 环境要求
+
+- JDK 17+
+- Maven 3.6+
+- MySQL 8.0+
+
+### 2. 数据库初始化
+
+创建数据库并执行以下SQL脚本:
+
+```sql
+-- 创建开放平台应用信息表
+CREATE TABLE `openapi_app_info` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `app_id` varchar(64) NOT NULL COMMENT '应用ID',
+  `app_name` varchar(128) NOT NULL COMMENT '应用名称',
+  `app_secret` varchar(128) NOT NULL COMMENT '应用密钥',
+  `contact_name` varchar(64) DEFAULT NULL COMMENT '联系人',
+  `contact_phone` varchar(20) DEFAULT NULL COMMENT '联系电话',
+  `contact_email` varchar(128) DEFAULT NULL COMMENT '联系邮箱',
+  `callback_url` varchar(512) DEFAULT NULL COMMENT '回调地址',
+  `ip_whitelist` varchar(1024) DEFAULT NULL COMMENT 'IP白名单',
+  `status` tinyint DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
+  `daily_limit` int DEFAULT '10000' COMMENT '每日调用限额',
+  `permissions` json DEFAULT NULL COMMENT '权限列表',
+  `remark` varchar(512) DEFAULT NULL COMMENT '备注信息',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_app_id` (`app_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开放平台应用信息表';
+
+-- 创建API调用日志表
+CREATE TABLE `openapi_api_log` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `app_id` varchar(64) NOT NULL COMMENT '应用ID',
+  `api_path` varchar(512) NOT NULL COMMENT '接口路径',
+  `request_method` varchar(10) NOT NULL COMMENT '请求方法',
+  `request_params` text COMMENT '请求参数',
+  `request_ip` varchar(64) DEFAULT NULL COMMENT '请求IP',
+  `response_code` int DEFAULT NULL COMMENT '响应状态码',
+  `response_message` varchar(512) DEFAULT NULL COMMENT '响应消息',
+  `response_time` bigint DEFAULT NULL COMMENT '响应时间(ms)',
+  `success` tinyint DEFAULT NULL COMMENT '是否成功:0-失败,1-成功',
+  `error_message` text COMMENT '错误信息',
+  `request_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '请求时间',
+  `response_time_field` datetime DEFAULT NULL COMMENT '响应时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_app_id` (`app_id`),
+  KEY `idx_request_time` (`request_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='API调用日志表';
+
+-- 插入测试应用
+INSERT INTO `openapi_app_info` (`app_id`, `app_name`, `app_secret`, `contact_name`, `contact_phone`, `status`)
+VALUES ('TEST_APP_001', '测试应用', 'test_secret_123456', '测试人员', '13800138000', 1);
+```
+
+### 3. 配置修改
+
+修改 `application-dev.yml` 中的数据库连接信息:
+
+```yaml
+spring:
+  datasource:
+    url: jdbc:mysql://localhost:3306/zs_electric?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+    username: root
+    password: your_password
+```
+
+修改 `application.yml` 中的API安全配置:
+
+```yaml
+api:
+  security:
+    secret-key: your-secret-key-please-change-in-production  # 生产环境请修改
+    timestamp-expire-minutes: 5
+    token-expire-hours: 24
+    rate-limit: 100
+```
+
+### 4. 编译运行
+
+```bash
+# 编译项目
+mvn clean package
+
+# 运行项目
+java -jar target/zsElectric-openapi.jar
+
+# 或者指定环境
+java -jar target/zsElectric-openapi.jar --spring.profiles.active=prod
+```
+
+### 5. 访问文档
+
+服务启动后,访问以下地址查看API文档:
+
+- **Knife4j文档**: http://localhost:8081/openapi/doc.html
+- **Swagger UI**: http://localhost:8081/openapi/swagger-ui.html
+
+## API调用流程
+
+### 1. 获取访问令牌
+
+```http
+POST /openapi/api/v1/token/get
+Content-Type: application/json
+X-App-Id: TEST_APP_001
+X-App-Secret: test_secret_123456
+```
+
+响应:
+
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "accessToken": "A1B2C3D4E5F6G7H8I9J0",
+    "tokenType": "Bearer",
+    "expireTime": 1704067200000
+  },
+  "timestamp": 1703980800000
+}
+```
+
+### 2. API签名认证
+
+所有需要认证的API请求都需要包含以下请求头:
+
+- `X-App-Id`: 应用ID
+- `X-Timestamp`: 时间戳(毫秒)
+- `X-Nonce`: 随机字符串
+- `X-Signature`: 签名(MD5)
+
+签名生成算法:
+
+```
+signString = appId + timestamp + nonce + requestParams + secretKey
+signature = MD5(signString).toUpperCase()
+```
+
+示例:
+
+```http
+GET /openapi/api/v1/charging/stations?status=AVAILABLE
+X-App-Id: TEST_APP_001
+X-Timestamp: 1703980800000
+X-Nonce: random123456
+X-Signature: AABBCCDD11223344556677889900
+```
+
+### 3. 调用API
+
+使用获取到的令牌和签名调用具体的API接口。
+
+## API接口列表
+
+### 健康检查
+- `GET /api/v1/health/check` - 健康检查
+
+### 令牌管理
+- `POST /api/v1/token/get` - 获取访问令牌
+
+### 充电桩管理
+- `GET /api/v1/charging/stations` - 获取充电桩列表
+- `GET /api/v1/charging/stations/{stationId}` - 获取充电桩详情
+- `GET /api/v1/charging/stations/{stationId}/status` - 获取充电桩实时状态
+
+### 订单管理
+- `GET /api/v1/order/list` - 查询订单列表
+- `GET /api/v1/order/{orderId}` - 查询订单详情
+
+## 部署说明
+
+### 打包部署
+
+```bash
+# 打包
+mvn clean package
+
+# 生成的jar包位于: target/zsElectric-openapi.jar
+```
+
+### 生产环境配置
+
+修改 `application-prod.yml` 中的配置:
+
+- 数据库连接信息
+- 日志路径
+- API密钥
+- 其他生产环境特定配置
+
+### 启动服务
+
+```bash
+# 后台启动
+nohup java -jar zsElectric-openapi.jar --spring.profiles.active=prod > app.log 2>&1 &
+
+# 查看日志
+tail -f app.log
+```
+
+### Docker部署
+
+创建 `Dockerfile`:
+
+```dockerfile
+FROM openjdk:17-jdk-alpine
+VOLUME /tmp
+ADD target/zsElectric-openapi.jar app.jar
+ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
+```
+
+构建并运行:
+
+```bash
+docker build -t zselectric-openapi:1.0.0 .
+docker run -d -p 8081:8081 --name openapi zselectric-openapi:1.0.0
+```
+
+## 安全建议
+
+1. **生产环境必须修改**:
+   - API签名密钥 (`api.security.secret-key`)
+   - 数据库密码
+   - 应用密钥 (`app_secret`)
+
+2. **使用HTTPS**: 生产环境必须使用HTTPS协议
+
+3. **IP白名单**: 为每个应用配置IP白名单
+
+4. **限流控制**: 根据业务需求调整API调用频率限制
+
+5. **日志监控**: 定期查看API调用日志,及时发现异常
+
+6. **权限控制**: 为不同应用分配不同的权限
+
+## 常见问题
+
+### Q: 签名验证失败?
+A: 请检查:
+- 签名算法是否正确
+- 时间戳是否在有效期内(默认5分钟)
+- 请求参数是否包含在签名中
+- 密钥是否正确
+
+### Q: 时间戳过期?
+A: 请确保客户端时间与服务器时间同步,误差不超过配置的过期时间(默认5分钟)
+
+### Q: 如何限制应用调用频率?
+A: 在 `openapi_app_info` 表中设置 `daily_limit` 字段,或在 `application.yml` 中配置 `api.security.rate-limit`
+
+## 联系方式
+
+- 项目作者: Ray.Hao
+- 技术支持: support@zselectric.com

+ 61 - 0
docker-compose.yml

@@ -0,0 +1,61 @@
+version: '3.8'
+
+services:
+  # MySQL数据库
+  mysql:
+    image: mysql:8.0
+    container_name: zselectric-openapi-mysql
+    restart: always
+    ports:
+      - "3306:3306"
+    environment:
+      MYSQL_ROOT_PASSWORD: root
+      MYSQL_DATABASE: zs_electric
+      MYSQL_USER: zselectric
+      MYSQL_PASSWORD: zselectric123
+      TZ: Asia/Shanghai
+    volumes:
+      - mysql-data:/var/lib/mysql
+      - ./src/main/resources/sql:/docker-entrypoint-initdb.d
+    command:
+      - --character-set-server=utf8mb4
+      - --collation-server=utf8mb4_unicode_ci
+      - --default-time-zone=+08:00
+    networks:
+      - zselectric-network
+
+  # 开放平台API服务
+  openapi:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    container_name: zselectric-openapi
+    restart: always
+    ports:
+      - "8081:8081"
+    environment:
+      SPRING_PROFILES_ACTIVE: prod
+      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/zs_electric?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
+      SPRING_DATASOURCE_USERNAME: zselectric
+      SPRING_DATASOURCE_PASSWORD: zselectric123
+      TZ: Asia/Shanghai
+    volumes:
+      - ./logs:/var/log/zsElectric-openapi
+    depends_on:
+      - mysql
+    networks:
+      - zselectric-network
+    healthcheck:
+      test: ["CMD", "curl", "-f", "http://localhost:8081/openapi/api/v1/health/check"]
+      interval: 30s
+      timeout: 10s
+      retries: 3
+      start_period: 60s
+
+volumes:
+  mysql-data:
+    driver: local
+
+networks:
+  zselectric-network:
+    driver: bridge

+ 186 - 0
pom.xml

@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.zsElectric</groupId>
+    <artifactId>zsElectric-openapi</artifactId>
+    <version>1.0.0</version>
+    <packaging>jar</packaging>
+    <description>第三方开放平台API服务 - 提供给第三方调用的独立接口服务</description>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>3.5.6</version>
+        <relativePath/>
+    </parent>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+        <hutool.version>5.8.34</hutool.version>
+        <knife4j.version>4.5.0</knife4j.version>
+        <mysql-connector-j.version>9.1.0</mysql-connector-j.version>
+        <mybatis-plus.version>3.5.5</mybatis-plus.version>
+        <druid.version>1.2.24</druid.version>
+        <fastjson2.version>2.0.52</fastjson2.version>
+        <ip2region.version>2.7.0</ip2region.version>
+        <minio.version>8.5.10</minio.version>
+        <aliyun-sdk-oss.version>3.16.3</aliyun-sdk-oss.version>
+    </properties>
+
+    <dependencies>
+        <!-- Spring Boot Web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!-- Spring Boot Validation -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <!-- Spring Boot AOP -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Jakarta Annotations API -->
+        <dependency>
+            <groupId>jakarta.annotation</groupId>
+            <artifactId>jakarta.annotation-api</artifactId>
+            <version>2.1.1</version>
+        </dependency>
+
+        <!-- Hutool 工具类 -->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>${hutool.version}</version>
+        </dependency>
+
+        <!-- FastJSON2 -->
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
+            <version>${fastjson2.version}</version>
+        </dependency>
+
+        <!-- JWT -->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-api</artifactId>
+            <version>0.11.5</version>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-impl</artifactId>
+            <version>0.11.5</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-jackson</artifactId>
+            <version>0.11.5</version>
+            <scope>runtime</scope>
+        </dependency>
+
+        <!-- MySQL驱动 -->
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+            <version>${mysql-connector-j.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+
+        <!-- Druid数据源 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>${druid.version}</version>
+        </dependency>
+
+        <!-- MyBatis Plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+            <version>${mybatis-plus.version}</version>
+        </dependency>
+
+        <!-- Knife4j 接口文档 -->
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
+            <version>${knife4j.version}</version>
+        </dependency>
+
+        <!-- IP 地区转换 -->
+        <dependency>
+            <groupId>org.lionsoul</groupId>
+            <artifactId>ip2region</artifactId>
+            <version>${ip2region.version}</version>
+        </dependency>
+
+        <!-- MinIO 对象存储 -->
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+            <version>${minio.version}</version>
+        </dependency>
+
+        <!-- 阿里云 OSS 对象存储 -->
+        <dependency>
+            <groupId>com.aliyun.oss</groupId>
+            <artifactId>aliyun-sdk-oss</artifactId>
+            <version>${aliyun-sdk-oss.version}</version>
+        </dependency>
+
+        <!-- Jackson 时间格式化 -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+        </dependency>
+
+        <!-- 动态多数据源 -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
+            <version>4.3.1</version>
+        </dependency>
+
+        <!-- Spring Boot Test -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>com.zsElectric.openapi.ZsElectricOpenApiApplication</mainClass>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 22 - 0
src/main/java/com/zsElectric/openapi/ZsElectricOpenApiApplication.java

@@ -0,0 +1,22 @@
+package com.zsElectric.openapi;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
+
+/**
+ * 开放平台API服务启动类
+ * 提供给第三方调用的独立接口服务
+ *
+ * @author Ray.Hao
+ * @since 1.0.0
+ */
+@SpringBootApplication
+@ConfigurationPropertiesScan
+public class ZsElectricOpenApiApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(ZsElectricOpenApiApplication.class, args);
+    }
+
+}

+ 118 - 0
src/main/java/com/zsElectric/openapi/business/entity/ChargingOrder.java

@@ -0,0 +1,118 @@
+package com.zsElectric.openapi.business.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 充电订单实体(业务系统数据)
+ * 注意: 使用@DS("business")注解指定使用业务数据库
+ *
+ * @author Ray.Hao
+ */
+@Data
+@TableName("charging_order")
+public class ChargingOrder implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 订单ID
+     */
+    @TableField("id")
+    private Long id;
+
+    /**
+     * 订单编号
+     */
+    @TableField("order_no")
+    private String orderNo;
+
+    /**
+     * 站点ID
+     */
+    @TableField("station_id")
+    private Long stationId;
+
+    /**
+     * 充电桩ID
+     */
+    @TableField("pile_id")
+    private Long pileId;
+
+    /**
+     * 用户ID
+     */
+    @TableField("user_id")
+    private Long userId;
+
+    /**
+     * 用户手机号
+     */
+    @TableField("user_phone")
+    private String userPhone;
+
+    /**
+     * 订单状态:0-待支付,1-充电中,2-已完成,3-已取消,4-已退款
+     */
+    @TableField("status")
+    private Integer status;
+
+    /**
+     * 充电开始时间
+     */
+    @TableField("start_time")
+    private LocalDateTime startTime;
+
+    /**
+     * 充电结束时间
+     */
+    @TableField("end_time")
+    private LocalDateTime endTime;
+
+    /**
+     * 充电时长(秒)
+     */
+    @TableField("duration")
+    private Long duration;
+
+    /**
+     * 充电量(度)
+     */
+    @TableField("electricity")
+    private BigDecimal electricity;
+
+    /**
+     * 订单金额(元)
+     */
+    @TableField("amount")
+    private BigDecimal amount;
+
+    /**
+     * 实付金额(元)
+     */
+    @TableField("pay_amount")
+    private BigDecimal payAmount;
+
+    /**
+     * 支付状态:0-未支付,1-已支付,2-已退款
+     */
+    @TableField("pay_status")
+    private Integer payStatus;
+
+    /**
+     * 创建时间
+     */
+    @TableField("create_time")
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+}

+ 99 - 0
src/main/java/com/zsElectric/openapi/business/entity/ChargingStation.java

@@ -0,0 +1,99 @@
+package com.zsElectric.openapi.business.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 充电站实体(业务系统数据)
+ * 注意: 使用@DS("business")注解指定使用业务数据库
+ *
+ * @author Ray.Hao
+ */
+@Data
+@TableName("charging_station")
+public class ChargingStation implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableField("id")
+    private Long id;
+
+    /**
+     * 站点名称
+     */
+    @TableField("station_name")
+    private String stationName;
+
+    /**
+     * 站点编码
+     */
+    @TableField("station_code")
+    private String stationCode;
+
+    /**
+     * 经度
+     */
+    @TableField("longitude")
+    private String longitude;
+
+    /**
+     * 纬度
+     */
+    @TableField("latitude")
+    private String latitude;
+
+    /**
+     * 省份
+     */
+    @TableField("province")
+    private String province;
+
+    /**
+     * 城市
+     */
+    @TableField("city")
+    private String city;
+
+    /**
+     * 区县
+     */
+    @TableField("district")
+    private String district;
+
+    /**
+     * 详细地址
+     */
+    @TableField("address")
+    private String address;
+
+    /**
+     * 联系电话
+     */
+    @TableField("phone")
+    private String phone;
+
+    /**
+     * 运营状态:0-停运,1-运营中
+     */
+    @TableField("status")
+    private Integer status;
+
+    /**
+     * 创建时间
+     */
+    @TableField("create_time")
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+}

+ 18 - 0
src/main/java/com/zsElectric/openapi/business/mapper/ChargingOrderMapper.java

@@ -0,0 +1,18 @@
+package com.zsElectric.openapi.business.mapper;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zsElectric.openapi.business.entity.ChargingOrder;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 充电订单Mapper(业务系统数据)
+ * 使用@DS("business")注解指定使用业务数据库
+ *
+ * @author Ray.Hao
+ */
+@Mapper
+@DS("business") // 指定使用业务数据库
+public interface ChargingOrderMapper extends BaseMapper<ChargingOrder> {
+
+}

+ 18 - 0
src/main/java/com/zsElectric/openapi/business/mapper/ChargingStationMapper.java

@@ -0,0 +1,18 @@
+package com.zsElectric.openapi.business.mapper;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zsElectric.openapi.business.entity.ChargingStation;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 充电站Mapper(业务系统数据)
+ * 使用@DS("business")注解指定使用业务数据库
+ *
+ * @author Ray.Hao
+ */
+@Mapper
+@DS("business") // 指定使用业务数据库
+public interface ChargingStationMapper extends BaseMapper<ChargingStation> {
+
+}

+ 122 - 0
src/main/java/com/zsElectric/openapi/business/service/ChargingOrderService.java

@@ -0,0 +1,122 @@
+package com.zsElectric.openapi.business.service;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zsElectric.openapi.business.entity.ChargingOrder;
+import com.zsElectric.openapi.business.mapper.ChargingOrderMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 充电订单服务(业务系统数据)
+ * 使用@DS("business")注解指定使用业务数据库
+ *
+ * @author Ray.Hao
+ */
+@Slf4j
+@Service
+@DS("business") // 指定使用业务数据库
+public class ChargingOrderService extends ServiceImpl<ChargingOrderMapper, ChargingOrder> {
+
+    /**
+     * 根据订单ID查询订单详情
+     *
+     * @param orderId 订单ID
+     * @return 订单详情
+     */
+    public ChargingOrder getOrderById(Long orderId) {
+        log.info("查询订单详情, orderId={}", orderId);
+        return getById(orderId);
+    }
+
+    /**
+     * 根据订单编号查询订单详情
+     *
+     * @param orderNo 订单编号
+     * @return 订单详情
+     */
+    public ChargingOrder getOrderByOrderNo(String orderNo) {
+        log.info("根据订单编号查询订单详情, orderNo={}", orderNo);
+        return lambdaQuery()
+                .eq(ChargingOrder::getOrderNo, orderNo)
+                .one();
+    }
+
+    /**
+     * 根据用户ID查询订单列表
+     *
+     * @param userId 用户ID
+     * @return 订单列表
+     */
+    public List<ChargingOrder> getOrdersByUserId(Long userId) {
+        log.info("根据用户ID查询订单列表, userId={}", userId);
+        return lambdaQuery()
+                .eq(ChargingOrder::getUserId, userId)
+                .orderByDesc(ChargingOrder::getCreateTime)
+                .list();
+    }
+
+    /**
+     * 根据充电站ID查询订单列表
+     *
+     * @param stationId 充电站ID
+     * @return 订单列表
+     */
+    public List<ChargingOrder> getOrdersByStationId(Long stationId) {
+        log.info("根据充电站ID查询订单列表, stationId={}", stationId);
+        return lambdaQuery()
+                .eq(ChargingOrder::getStationId, stationId)
+                .orderByDesc(ChargingOrder::getCreateTime)
+                .list();
+    }
+
+    /**
+     * 根据订单状态查询订单列表
+     *
+     * @param status 订单状态
+     * @return 订单列表
+     */
+    public List<ChargingOrder> getOrdersByStatus(Integer status) {
+        log.info("根据订单状态查询订单列表, status={}", status);
+        return lambdaQuery()
+                .eq(ChargingOrder::getStatus, status)
+                .orderByDesc(ChargingOrder::getCreateTime)
+                .list();
+    }
+
+    /**
+     * 根据订单编号或用户手机号查询订单列表
+     *
+     * @param orderNo     订单编号(可选)
+     * @param userPhone   用户手机号(可选)
+     * @param status      订单状态(可选)
+     * @param page        页码
+     * @param size        每页数量
+     * @return 订单列表
+     */
+    public List<ChargingOrder> searchOrders(String orderNo, String userPhone, Integer status, int page, int size) {
+        log.info("搜索订单列表, orderNo={}, userPhone={}, status={}, page={}, size={}", orderNo, userPhone, status, page, size);
+
+        LambdaQueryWrapper<ChargingOrder> wrapper = new LambdaQueryWrapper<>();
+
+        if (orderNo != null && !orderNo.isEmpty()) {
+            wrapper.like(ChargingOrder::getOrderNo, orderNo);
+        }
+
+        if (userPhone != null && !userPhone.isEmpty()) {
+            wrapper.eq(ChargingOrder::getUserPhone, userPhone);
+        }
+
+        if (status != null) {
+            wrapper.eq(ChargingOrder::getStatus, status);
+        }
+
+        wrapper.orderByDesc(ChargingOrder::getCreateTime)
+                .last("LIMIT " + size + " OFFSET " + (page - 1) * size);
+
+        return list(wrapper);
+    }
+}

+ 62 - 0
src/main/java/com/zsElectric/openapi/business/service/ChargingStationService.java

@@ -0,0 +1,62 @@
+package com.zsElectric.openapi.business.service;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zsElectric.openapi.business.entity.ChargingStation;
+import com.zsElectric.openapi.business.mapper.ChargingStationMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 充电站服务(业务系统数据)
+ * 使用@DS("business")注解指定使用业务数据库
+ *
+ * @author Ray.Hao
+ */
+@Slf4j
+@Service
+@DS("business") // 指定使用业务数据库
+public class ChargingStationService extends ServiceImpl<ChargingStationMapper, ChargingStation> {
+
+    /**
+     * 获取所有运营中的充电站列表
+     *
+     * @return 充电站列表
+     */
+    public List<ChargingStation> getActiveStations() {
+        log.info("查询运营中的充电站列表");
+        return lambdaQuery()
+                .eq(ChargingStation::getStatus, 1)
+                .orderByAsc(ChargingStation::getId)
+                .list();
+    }
+
+    /**
+     * 根据ID获取充电站详情
+     *
+     * @param stationId 充电站ID
+     * @return 充电站详情
+     */
+    public ChargingStation getStationById(Long stationId) {
+        log.info("查询充电站详情, stationId={}", stationId);
+        return lambdaQuery()
+                .eq(ChargingStation::getId, stationId)
+                .one();
+    }
+
+    /**
+     * 根据城市获取充电站列表
+     *
+     * @param city 城市名称
+     * @return 充电站列表
+     */
+    public List<ChargingStation> getStationsByCity(String city) {
+        log.info("根据城市查询充电站列表, city={}", city);
+        return lambdaQuery()
+                .eq(ChargingStation::getCity, city)
+                .eq(ChargingStation::getStatus, 1)
+                .list();
+    }
+}

+ 111 - 0
src/main/java/com/zsElectric/openapi/common/Result.java

@@ -0,0 +1,111 @@
+package com.zsElectric.openapi.common;
+
+import lombok.Data;
+import java.io.Serializable;
+
+/**
+ * 统一响应结果
+ *
+ * @author Ray.Hao
+ */
+@Data
+public class Result<T> implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 状态码
+     */
+    private Integer code;
+
+    /**
+     * 返回消息
+     */
+    private String message;
+
+    /**
+     * 返回数据
+     */
+    private T data;
+
+    /**
+     * 时间戳
+     */
+    private Long timestamp;
+
+    public Result() {
+        this.timestamp = System.currentTimeMillis();
+    }
+
+    public Result(Integer code, String message) {
+        this();
+        this.code = code;
+        this.message = message;
+    }
+
+    public Result(Integer code, String message, T data) {
+        this(code, message);
+        this.data = data;
+    }
+
+    /**
+     * 成功返回结果
+     */
+    public static <T> Result<T> success() {
+        return new Result<>(200, "操作成功");
+    }
+
+    /**
+     * 成功返回结果
+     *
+     * @param data 获取的数据
+     */
+    public static <T> Result<T> success(T data) {
+        return new Result<>(200, "操作成功", data);
+    }
+
+    /**
+     * 成功返回结果
+     *
+     * @param message 提示信息
+     */
+    public static <T> Result<T> success(String message) {
+        return new Result<>(200, message);
+    }
+
+    /**
+     * 成功返回结果
+     *
+     * @param message 提示信息
+     * @param data    获取的数据
+     */
+    public static <T> Result<T> success(String message, T data) {
+        return new Result<>(200, message, data);
+    }
+
+    /**
+     * 失败返回结果
+     *
+     * @param message 提示信息
+     */
+    public static <T> Result<T> error(String message) {
+        return new Result<>(500, message);
+    }
+
+    /**
+     * 失败返回结果
+     *
+     * @param code    状态码
+     * @param message 提示信息
+     */
+    public static <T> Result<T> error(Integer code, String message) {
+        return new Result<>(code, message);
+    }
+
+    /**
+     * 失败返回结果
+     */
+    public static <T> Result<T> error() {
+        return new Result<>(500, "操作失败");
+    }
+}

+ 87 - 0
src/main/java/com/zsElectric/openapi/common/ResultCode.java

@@ -0,0 +1,87 @@
+package com.zsElectric.openapi.common;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 返回状态码
+ *
+ * @author Ray.Hao
+ */
+@Getter
+@AllArgsConstructor
+public enum ResultCode {
+
+    /**
+     * 成功
+     */
+    SUCCESS(200, "操作成功"),
+
+    /**
+     * 失败
+     */
+    FAIL(500, "操作失败"),
+
+    /**
+     * 参数错误
+     */
+    PARAM_ERROR(400, "参数错误"),
+
+    /**
+     * 未授权
+     */
+    UNAUTHORIZED(401, "未授权"),
+
+    /**
+     * 禁止访问
+     */
+    FORBIDDEN(403, "禁止访问"),
+
+    /**
+     * 资源不存在
+     */
+    NOT_FOUND(404, "资源不存在"),
+
+    /**
+     * 签名验证失败
+     */
+    SIGN_ERROR(1001, "签名验证失败"),
+
+    /**
+     * 时间戳过期
+     */
+    TIMESTAMP_EXPIRED(1002, "时间戳过期"),
+
+    /**
+     * 令牌无效
+     */
+    TOKEN_INVALID(1003, "令牌无效"),
+
+    /**
+     * 令牌过期
+     */
+    TOKEN_EXPIRED(1004, "令牌过期"),
+
+    /**
+     * 接口调用频率超限
+     */
+    RATE_LIMIT(1005, "接口调用频率超限"),
+
+    /**
+     * 应用不存在
+     */
+    APP_NOT_FOUND(2001, "应用不存在"),
+
+    /**
+     * 应用已被禁用
+     */
+    APP_DISABLED(2002, "应用已被禁用"),
+
+    /**
+     * 服务异常
+     */
+    SERVICE_ERROR(9999, "服务异常");
+
+    private final Integer code;
+    private final String message;
+}

+ 21 - 0
src/main/java/com/zsElectric/openapi/common/annotation/DS.java

@@ -0,0 +1,21 @@
+package com.zsElectric.openapi.common.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 数据源切换注解
+ * 用于在方法或类级别指定使用哪个数据源
+ *
+ * @author Ray.Hao
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DS {
+
+    /**
+     * 数据源名称
+     * 可选值: openapi(开放平台数据库), business(业务系统数据库)
+     */
+    String value() default "openapi";
+}

+ 42 - 0
src/main/java/com/zsElectric/openapi/common/exception/BusinessException.java

@@ -0,0 +1,42 @@
+package com.zsElectric.openapi.common.exception;
+
+import com.zsElectric.openapi.common.ResultCode;
+import lombok.Getter;
+
+/**
+ * 业务异常
+ *
+ * @author Ray.Hao
+ */
+@Getter
+public class BusinessException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    private final Integer code;
+    private final String message;
+
+    public BusinessException(String message) {
+        super(message);
+        this.code = ResultCode.FAIL.getCode();
+        this.message = message;
+    }
+
+    public BusinessException(Integer code, String message) {
+        super(message);
+        this.code = code;
+        this.message = message;
+    }
+
+    public BusinessException(ResultCode resultCode) {
+        super(resultCode.getMessage());
+        this.code = resultCode.getCode();
+        this.message = resultCode.getMessage();
+    }
+
+    public BusinessException(ResultCode resultCode, String message) {
+        super(message);
+        this.code = resultCode.getCode();
+        this.message = message;
+    }
+}

+ 69 - 0
src/main/java/com/zsElectric/openapi/common/exception/GlobalExceptionHandler.java

@@ -0,0 +1,69 @@
+package com.zsElectric.openapi.common.exception;
+
+import com.zsElectric.openapi.common.Result;
+import com.zsElectric.openapi.common.ResultCode;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * 全局异常处理器
+ *
+ * @author Ray.Hao
+ */
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    /**
+     * 业务异常
+     */
+    @ExceptionHandler(BusinessException.class)
+    public Result<?> handleBusinessException(BusinessException e) {
+        log.error("业务异常: {}", e.getMessage());
+        return Result.error(e.getCode(), e.getMessage());
+    }
+
+    /**
+     * 参数校验异常
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+        FieldError fieldError = e.getBindingResult().getFieldError();
+        String message = fieldError != null ? fieldError.getDefaultMessage() : "参数校验失败";
+        log.error("参数校验异常: {}", message);
+        return Result.error(ResultCode.PARAM_ERROR.getCode(), message);
+    }
+
+    /**
+     * 参数绑定异常
+     */
+    @ExceptionHandler(BindException.class)
+    public Result<?> handleBindException(BindException e) {
+        FieldError fieldError = e.getBindingResult().getFieldError();
+        String message = fieldError != null ? fieldError.getDefaultMessage() : "参数绑定失败";
+        log.error("参数绑定异常: {}", message);
+        return Result.error(ResultCode.PARAM_ERROR.getCode(), message);
+    }
+
+    /**
+     * 运行时异常
+     */
+    @ExceptionHandler(RuntimeException.class)
+    public Result<?> handleRuntimeException(RuntimeException e) {
+        log.error("运行时异常", e);
+        return Result.error(ResultCode.SERVICE_ERROR.getCode(), "系统异常,请稍后重试");
+    }
+
+    /**
+     * 其他异常
+     */
+    @ExceptionHandler(Exception.class)
+    public Result<?> handleException(Exception e) {
+        log.error("系统异常", e);
+        return Result.error(ResultCode.SERVICE_ERROR.getCode(), "系统异常,请稍后重试");
+    }
+}

+ 33 - 0
src/main/java/com/zsElectric/openapi/config/CorsConfig.java

@@ -0,0 +1,33 @@
+package com.zsElectric.openapi.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+
+/**
+ * 跨域配置
+ *
+ * @author Ray.Hao
+ */
+@Configuration
+public class CorsConfig {
+
+    @Bean
+    public CorsFilter corsFilter() {
+        CorsConfiguration config = new CorsConfiguration();
+        // 允许所有域名进行跨域调用
+        config.addAllowedOriginPattern("*");
+        // 允许跨域发送cookie
+        config.setAllowCredentials(true);
+        // 放行全部原始头信息
+        config.addAllowedHeader("*");
+        // 允许所有请求方法跨域调用
+        config.addAllowedMethod("*");
+
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        source.registerCorsConfiguration("/**", config);
+        return new CorsFilter(source);
+    }
+}

+ 40 - 0
src/main/java/com/zsElectric/openapi/config/OpenApiConfig.java

@@ -0,0 +1,40 @@
+package com.zsElectric.openapi.config;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.License;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Knife4j接口文档配置
+ *
+ * @author Ray.Hao
+ */
+@Configuration
+public class OpenApiConfig {
+
+    @Value("${app.swagger-enabled:true}")
+    private Boolean swaggerEnabled;
+
+    @Bean
+    public OpenAPI customOpenAPI() {
+        if (!swaggerEnabled) {
+            return null;
+        }
+
+        return new OpenAPI()
+                .info(new Info()
+                        .title("zsElectric开放平台API")
+                        .version("1.0.0")
+                        .description("提供第三方调用的开放平台API接口文档")
+                        .contact(new Contact()
+                                .name("API Support")
+                                .email("support@zselectric.com"))
+                        .license(new License()
+                                .name("Apache 2.0")
+                                .url("https://www.apache.org/licenses/LICENSE-2.0")));
+    }
+}

+ 32 - 0
src/main/java/com/zsElectric/openapi/config/WebMvcConfig.java

@@ -0,0 +1,32 @@
+package com.zsElectric.openapi.config;
+
+import com.zsElectric.openapi.security.ApiSignatureInterceptor;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * Web MVC配置
+ *
+ * @author Ray.Hao
+ */
+@Configuration
+@RequiredArgsConstructor
+public class WebMvcConfig implements WebMvcConfigurer {
+
+    private final ApiSignatureInterceptor apiSignatureInterceptor;
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        // 注册API签名拦截器,拦截所有API请求
+        registry.addInterceptor(apiSignatureInterceptor)
+                .addPathPatterns("/api/**")
+                .excludePathPatterns(
+                        "/api/v1/token/**",      // 获取令牌接口不需要签名
+                        "/swagger-ui/**",         // Swagger UI
+                        "/v3/api-docs/**",        // API文档
+                        "/doc.html"                // Knife4j文档
+                );
+    }
+}

+ 105 - 0
src/main/java/com/zsElectric/openapi/controller/ChargingController.java

@@ -0,0 +1,105 @@
+package com.zsElectric.openapi.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.zsElectric.openapi.business.entity.ChargingStation;
+import com.zsElectric.openapi.business.mapper.ChargingStationMapper;
+import com.zsElectric.openapi.business.service.ChargingStationService;
+import com.zsElectric.openapi.common.Result;
+import com.zsElectric.openapi.entity.ApiLog;
+import com.zsElectric.openapi.mapper.ApiLogMapper;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 充电桩API控制器
+ *
+ * @author Ray.Hao
+ */
+@Tag(name = "充电桩管理", description = "充电桩相关API接口")
+@RestController
+@RequestMapping("/api/v1/charging")
+@RequiredArgsConstructor
+public class ChargingController {
+
+    private final ChargingStationService chargingStationService;
+    private final ChargingStationMapper chargingStationMapper;
+    private final ApiLogMapper apiLogMapper;
+
+    @Operation(summary = "获取充电桩列表", description = "获取所有可用充电桩列表")
+    @GetMapping("/stations")
+    public Result<Map<String, Object>> getStationList(
+            @Parameter(description = "城市名称")
+            @RequestParam(required = false) String city,
+            HttpServletRequest request) {
+
+        // 调用业务系统数据源获取充电站数据
+        List<ChargingStation> stations;
+        if (city != null && !city.isEmpty()) {
+            stations = chargingStationService.getStationsByCity(city);
+        } else {
+            stations = chargingStationService.getActiveStations();
+        }
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("total", stations.size());
+        data.put("list", stations);
+        return Result.success(data);
+    }
+
+    @Operation(summary = "获取充电桩详情", description = "根据ID获取充电桩详细信息")
+    @GetMapping("/stations/{stationId}")
+    public Result<Map<String, Object>> getStationDetail(
+            @Parameter(description = "充电桩ID", required = true)
+            @PathVariable("stationId") Long stationId) {
+
+        // 调用业务系统数据源获取充电站详情
+        ChargingStation station = chargingStationService.getStationById(stationId);
+
+        if (station == null) {
+            return Result.error(404, "充电站不存在");
+        }
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("stationId", station.getId());
+        data.put("stationCode", station.getStationCode());
+        data.put("stationName", station.getStationName());
+        data.put("longitude", station.getLongitude());
+        data.put("latitude", station.getLatitude());
+        data.put("province", station.getProvince());
+        data.put("city", station.getCity());
+        data.put("district", station.getDistrict());
+        data.put("address", station.getAddress());
+        data.put("phone", station.getPhone());
+        data.put("status", station.getStatus());
+        return Result.success(data);
+    }
+
+    @Operation(summary = "获取充电桩实时状态", description = "获取充电桩的实时使用状态")
+    @GetMapping("/stations/{stationId}/status")
+    public Result<Map<String, Object>> getStationStatus(
+            @Parameter(description = "充电桩ID", required = true)
+            @PathVariable("stationId") Long stationId) {
+
+        // TODO: 从业务系统的实时数据表中查询充电桩状态
+        // 这里先返回模拟数据
+        Map<String, Object> data = new HashMap<>();
+        data.put("stationId", stationId);
+        data.put("status", "AVAILABLE");
+        data.put("availablePiles", 10);
+        data.put("totalPiles", 20);
+        data.put("chargingCount", 10);
+        return Result.success(data);
+    }
+}

+ 34 - 0
src/main/java/com/zsElectric/openapi/controller/HealthController.java

@@ -0,0 +1,34 @@
+package com.zsElectric.openapi.controller;
+
+import com.zsElectric.openapi.common.Result;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 健康检查控制器
+ *
+ * @author Ray.Hao
+ */
+@Tag(name = "健康检查", description = "健康检查接口")
+@RestController
+@RequestMapping("/api/v1/health")
+public class HealthController {
+
+    @Operation(summary = "健康检查", description = "检查服务是否正常运行")
+    @GetMapping("/check")
+    public Result<Map<String, Object>> healthCheck() {
+        Map<String, Object> data = new HashMap<>();
+        data.put("status", "UP");
+        data.put("timestamp", LocalDateTime.now());
+        data.put("service", "zsElectric-openapi");
+        data.put("version", "1.0.0");
+        return Result.success(data);
+    }
+}

+ 103 - 0
src/main/java/com/zsElectric/openapi/controller/OrderController.java

@@ -0,0 +1,103 @@
+package com.zsElectric.openapi.controller;
+
+import com.zsElectric.openapi.business.entity.ChargingOrder;
+import com.zsElectric.openapi.business.service.ChargingOrderService;
+import com.zsElectric.openapi.common.Result;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 订单API控制器
+ *
+ * @author Ray.Hao
+ */
+@Tag(name = "订单管理", description = "订单相关API接口")
+@RestController
+@RequestMapping("/api/v1/order")
+@RequiredArgsConstructor
+public class OrderController {
+
+    private final ChargingOrderService chargingOrderService;
+
+    @Operation(summary = "查询订单列表", description = "根据条件查询订单列表")
+    @GetMapping("/list")
+    public Result<Map<String, Object>> getOrderList(
+            @Parameter(description = "订单编号")
+            @RequestParam(required = false) String orderNo,
+            @Parameter(description = "用户手机号")
+            @RequestParam(required = false) String userPhone,
+            @Parameter(description = "订单状态")
+            @RequestParam(required = false) Integer status,
+            @Parameter(description = "页码")
+            @RequestParam(defaultValue = "1") Integer page,
+            @Parameter(description = "每页数量")
+            @RequestParam(defaultValue = "10") Integer size) {
+
+        // 调用业务系统数据源查询订单列表
+        List<ChargingOrder> orders = chargingOrderService.searchOrders(orderNo, userPhone, status, page, size);
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("total", orders.size());
+        data.put("page", page);
+        data.put("size", size);
+        data.put("list", orders);
+        return Result.success(data);
+    }
+
+    @Operation(summary = "查询订单详情", description = "根据订单ID查询订单详细信息")
+    @GetMapping("/{orderId}")
+    public Result<Map<String, Object>> getOrderDetail(
+            @Parameter(description = "订单ID", required = true)
+            @PathVariable("orderId") Long orderId) {
+
+        // 调用业务系统数据源查询订单详情
+        ChargingOrder order = chargingOrderService.getOrderById(orderId);
+
+        if (order == null) {
+            return Result.error(404, "订单不存在");
+        }
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("orderId", order.getId());
+        data.put("orderNo", order.getOrderNo());
+        data.put("stationId", order.getStationId());
+        data.put("pileId", order.getPileId());
+        data.put("userId", order.getUserId());
+        data.put("userPhone", order.getUserPhone());
+        data.put("status", order.getStatus());
+        data.put("startTime", order.getStartTime());
+        data.put("endTime", order.getEndTime());
+        data.put("duration", order.getDuration());
+        data.put("electricity", order.getElectricity());
+        data.put("amount", order.getAmount());
+        data.put("payAmount", order.getPayAmount());
+        data.put("payStatus", order.getPayStatus());
+        return Result.success(data);
+    }
+
+    @Operation(summary = "根据用户ID查询订单", description = "根据用户ID查询该用户的所有订单")
+    @GetMapping("/user/{userId}")
+    public Result<Map<String, Object>> getOrdersByUserId(
+            @Parameter(description = "用户ID", required = true)
+            @PathVariable("userId") Long userId) {
+
+        // 调用业务系统数据源查询用户订单
+        List<ChargingOrder> orders = chargingOrderService.getOrdersByUserId(userId);
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("total", orders.size());
+        data.put("list", orders);
+        return Result.success(data);
+    }
+}

+ 50 - 0
src/main/java/com/zsElectric/openapi/controller/TokenController.java

@@ -0,0 +1,50 @@
+package com.zsElectric.openapi.controller;
+
+import com.zsElectric.openapi.common.Result;
+import com.zsElectric.openapi.dto.ApiResponse;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * 令牌控制器
+ *
+ * @author Ray.Hao
+ */
+@Tag(name = "令牌管理", description = "获取访问令牌")
+@RestController
+@RequestMapping("/api/v1/token")
+public class TokenController {
+
+    @Operation(summary = "获取访问令牌", description = "使用AppId和AppSecret获取访问令牌")
+    @PostMapping("/get")
+    public ApiResponse<Map<String, Object>> getToken(
+            @Parameter(description = "应用ID", required = true)
+            @RequestHeader("X-App-Id") String appId,
+            @Parameter(description = "应用密钥", required = true)
+            @RequestHeader("X-App-Secret") String appSecret) {
+
+        // TODO: 验证AppId和AppSecret是否正确
+        // TODO: 验证应用状态是否启用
+        // TODO: 使用Redis缓存令牌
+
+        // 生成访问令牌
+        String accessToken = UUID.randomUUID().toString().replace("-", "").toUpperCase();
+        long expireTime = System.currentTimeMillis() + (24 * 60 * 60 * 1000); // 24小时过期
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("accessToken", accessToken);
+        data.put("tokenType", "Bearer");
+        data.put("expireTime", expireTime);
+
+        return ApiResponse.success(data);
+    }
+}

+ 51 - 0
src/main/java/com/zsElectric/openapi/dto/ApiResponse.java

@@ -0,0 +1,51 @@
+package com.zsElectric.openapi.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * API响应数据
+ *
+ * @author Ray.Hao
+ */
+@Data
+@Schema(description = "API响应数据")
+public class ApiResponse<T> implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "状态码")
+    private Integer code;
+
+    @Schema(description = "返回消息")
+    private String message;
+
+    @Schema(description = "返回数据")
+    private T data;
+
+    @Schema(description = "时间戳")
+    private Long timestamp;
+
+    public ApiResponse() {
+        this.timestamp = System.currentTimeMillis();
+    }
+
+    public ApiResponse(Integer code, String message, T data) {
+        this();
+        this.code = code;
+        this.message = message;
+        this.data = data;
+    }
+
+    public static <T> ApiResponse<T> success(T data) {
+        return new ApiResponse<>(200, "操作成功", data);
+    }
+
+    public static <T> ApiResponse<T> error(Integer code, String message) {
+        return new ApiResponse<>(code, message, null);
+    }
+}

+ 84 - 0
src/main/java/com/zsElectric/openapi/entity/ApiLog.java

@@ -0,0 +1,84 @@
+package com.zsElectric.openapi.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * API调用日志
+ *
+ * @author Ray.Hao
+ */
+@Data
+@TableName("openapi_api_log")
+public class ApiLog {
+
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 应用ID
+     */
+    private String appId;
+
+    /**
+     * 接口路径
+     */
+    private String apiPath;
+
+    /**
+     * 请求方法
+     */
+    private String requestMethod;
+
+    /**
+     * 请求参数
+     */
+    private String requestParams;
+
+    /**
+     * 请求IP
+     */
+    private String requestIp;
+
+    /**
+     * 响应状态码
+     */
+    private Integer responseCode;
+
+    /**
+     * 响应消息
+     */
+    private String responseMessage;
+
+    /**
+     * 响应时间(ms)
+     */
+    private Long responseTime;
+
+    /**
+     * 是否成功:0-失败,1-成功
+     */
+    private Integer success;
+
+    /**
+     * 错误信息
+     */
+    private String errorMessage;
+
+    /**
+     * 请求时间
+     */
+    private LocalDateTime requestTime;
+
+    /**
+     * 响应时间
+     */
+    private LocalDateTime responseTimeField;
+}

+ 95 - 0
src/main/java/com/zsElectric/openapi/entity/AppInfo.java

@@ -0,0 +1,95 @@
+package com.zsElectric.openapi.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 开放平台应用信息
+ * 使用默认数据源(openapi)
+ *
+ * @author Ray.Hao
+ */
+@Data
+@TableName("openapi_app_info")
+public class AppInfo {
+
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 应用ID
+     */
+    private String appId;
+
+    /**
+     * 应用名称
+     */
+    private String appName;
+
+    /**
+     * 应用密钥
+     */
+    private String appSecret;
+
+    /**
+     * 联系人
+     */
+    private String contactName;
+
+    /**
+     * 联系电话
+     */
+    private String contactPhone;
+
+    /**
+     * 联系邮箱
+     */
+    private String contactEmail;
+
+    /**
+     * 回调地址
+     */
+    private String callbackUrl;
+
+    /**
+     * IP白名单
+     */
+    private String ipWhitelist;
+
+    /**
+     * 状态:0-禁用,1-启用
+     */
+    private Integer status;
+
+    /**
+     * 每日调用限额
+     */
+    private Integer dailyLimit;
+
+    /**
+     * 权限列表(JSON格式)
+     */
+    private String permissions;
+
+    /**
+     * 备注信息
+     */
+    private String remark;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+}

+ 15 - 0
src/main/java/com/zsElectric/openapi/mapper/ApiLogMapper.java

@@ -0,0 +1,15 @@
+package com.zsElectric.openapi.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zsElectric.openapi.entity.ApiLog;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * API调用日志Mapper
+ *
+ * @author Ray.Hao
+ */
+@Mapper
+public interface ApiLogMapper extends BaseMapper<ApiLog> {
+
+}

+ 15 - 0
src/main/java/com/zsElectric/openapi/mapper/AppInfoMapper.java

@@ -0,0 +1,15 @@
+package com.zsElectric.openapi.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zsElectric.openapi.entity.AppInfo;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 开放平台应用信息Mapper
+ *
+ * @author Ray.Hao
+ */
+@Mapper
+public interface AppInfoMapper extends BaseMapper<AppInfo> {
+
+}

+ 102 - 0
src/main/java/com/zsElectric/openapi/security/ApiSignatureInterceptor.java

@@ -0,0 +1,102 @@
+package com.zsElectric.openapi.security;
+
+import cn.hutool.crypto.SecureUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * API签名拦截器
+ *
+ * @author Ray.Hao
+ */
+@Slf4j
+@Component
+public class ApiSignatureInterceptor implements HandlerInterceptor {
+
+    @Value("${api.security.secret-key}")
+    private String secretKey;
+
+    @Value("${api.security.timestamp-expire-minutes:5}")
+    private Long timestampExpireMinutes;
+
+    private static final String APP_ID_HEADER = "X-App-Id";
+    private static final String TIMESTAMP_HEADER = "X-Timestamp";
+    private static final String SIGNATURE_HEADER = "X-Signature";
+    private static final String NONCE_HEADER = "X-Nonce";
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        // 获取请求头
+        String appId = request.getHeader(APP_ID_HEADER);
+        String timestamp = request.getHeader(TIMESTAMP_HEADER);
+        String signature = request.getHeader(SIGNATURE_HEADER);
+        String nonce = request.getHeader(NONCE_HEADER);
+
+        log.info("API请求 - AppId: {}, Timestamp: {}, Signature: {}, Nonce: {}", appId, timestamp, signature, nonce);
+
+        // 验证参数
+        if (appId == null || timestamp == null || signature == null || nonce == null) {
+            log.warn("API请求缺少必要的签名参数");
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            response.setContentType("application/json;charset=UTF-8");
+            response.getWriter().write("{\"code\":1001,\"message\":\"签名验证失败\"}");
+            return false;
+        }
+
+        // 验证时间戳(防止重放攻击)
+        long requestTime = Long.parseLong(timestamp);
+        long currentTime = System.currentTimeMillis();
+        long expireMillis = timestampExpireMinutes * 60 * 1000;
+
+        if (Math.abs(currentTime - requestTime) > expireMillis) {
+            log.warn("API请求时间戳过期: requestTime={}, currentTime={}", requestTime, currentTime);
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            response.setContentType("application/json;charset=UTF-8");
+            response.getWriter().write("{\"code\":1002,\"message\":\"时间戳过期\"}");
+            return false;
+        }
+
+        // TODO: 验证AppId是否存在且有效
+        // TODO: 验证Nonce是否重复(需要Redis支持)
+
+        // 验证签名
+        String expectedSignature = generateSignature(appId, timestamp, nonce, request);
+        if (!expectedSignature.equals(signature)) {
+            log.warn("API请求签名验证失败: expected={}, actual={}", expectedSignature, signature);
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            response.setContentType("application/json;charset=UTF-8");
+            response.getWriter().write("{\"code\":1001,\"message\":\"签名验证失败\"}");
+            return false;
+        }
+
+        log.info("API请求签名验证成功");
+        return true;
+    }
+
+    /**
+     * 生成签名
+     */
+    private String generateSignature(String appId, String timestamp, String nonce, HttpServletRequest request) {
+        // 获取请求参数
+        Map<String, String[]> parameterMap = request.getParameterMap();
+        String params = parameterMap.entrySet().stream()
+                .sorted(Map.Entry.comparingByKey())
+                .map(entry -> entry.getKey() + "=" + String.join(",", entry.getValue()))
+                .collect(Collectors.joining("&"));
+
+        // 拼接签名字符串: appId + timestamp + nonce + params + secretKey
+        String signString = appId + timestamp + nonce + params + secretKey;
+
+        // MD5加密并转大写
+        return SecureUtil.md5(signString).toUpperCase();
+    }
+}

+ 72 - 0
src/main/resources/application-dev.yml

@@ -0,0 +1,72 @@
+spring:
+  # 动态多数据源配置
+  datasource:
+    dynamic:
+      primary: openapi  # 设置默认数据源为openapi
+      strict: false     # 严格模式,如果匹配不到指定的数据源则报错
+      datasource:
+        # 开放平台独立数据库
+        openapi:
+          type: com.alibaba.druid.pool.DruidDataSource
+          driver-class-name: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://localhost:3306/zs_electric_openapi?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+          username: root
+          password: root
+          druid:
+            initial-size: 5
+            min-idle: 5
+            max-active: 20
+            max-wait: 60000
+            time-between-eviction-runs-millis: 60000
+            min-evictable-idle-time-millis: 300000
+            validation-query: SELECT 1
+            test-while-idle: true
+            test-on-borrow: false
+            test-on-return: false
+            pool-prepared-statements: true
+            max-pool-prepared-statement-per-connection-size: 20
+        # 业务系统数据库(只读)
+        business:
+          type: com.alibaba.druid.pool.DruidDataSource
+          driver-class-name: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://localhost:3306/zs_electric?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+          username: root
+          password: root
+          druid:
+            initial-size: 5
+            min-idle: 5
+            max-active: 20
+            max-wait: 60000
+            time-between-eviction-runs-millis: 60000
+            min-evictable-idle-time-millis: 300000
+            validation-query: SELECT 1
+            test-while-idle: true
+            test-on-borrow: false
+            test-on-return: false
+            pool-prepared-statements: true
+            max-pool-prepared-statement-per-connection-size: 20
+
+# MyBatis Plus配置
+mybatis-plus:
+  # 搜索指定包别名
+  type-aliases-package: com.zsElectric.openapi.entity
+  # 配置mapper的扫描,找到所有的mapper.xml映射文件
+  mapper-locations: classpath*:mapper/**/*Mapper.xml
+  # 加载全局的配置文件
+  configuration:
+    # 使用驼峰命名法转换字段
+    map-underscore-to-camel-case: true
+    # 开启缓存
+    cache-enabled: true
+    # 日志实现
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+# 日志配置
+logging:
+  level:
+    com.zsElectric.openapi: debug
+    com.zsElectric.openapi.mapper: debug
+
+# 应用配置
+app:
+  swagger-enabled: true

+ 95 - 0
src/main/resources/application-prod.yml

@@ -0,0 +1,95 @@
+spring:
+  # 动态多数据源配置
+  datasource:
+    dynamic:
+      primary: openapi  # 设置默认数据源为openapi
+      strict: false     # 严格模式,如果匹配不到指定的数据源则报错
+      datasource:
+        # 开放平台独立数据库
+        openapi:
+          type: com.alibaba.druid.pool.DruidDataSource
+          driver-class-name: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://your-prod-host:3306/zs_electric_openapi?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+          username: your-prod-username
+          password: your-prod-password
+          druid:
+            initial-size: 10
+            min-idle: 10
+            max-active: 50
+            max-wait: 60000
+            time-between-eviction-runs-millis: 60000
+            min-evictable-idle-time-millis: 300000
+            validation-query: SELECT 1
+            test-while-idle: true
+            test-on-borrow: false
+            test-on-return: false
+            pool-prepared-statements: true
+            max-pool-prepared-statement-per-connection-size: 20
+            filters: stat,wall
+            connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=1000
+            # 监控页面配置(生产环境关闭)
+            stat-view-servlet:
+              enabled: false
+            web-stat-filter:
+              enabled: false
+            # 监控页面配置
+            stat-view-servlet:
+              enabled: false
+            web-stat-filter:
+              enabled: false
+        # 业务系统数据库(只读)
+        business:
+          type: com.alibaba.druid.pool.DruidDataSource
+          driver-class-name: com.mysql.cj.jdbc.Driver
+          url: jdbc:mysql://your-prod-host:3306/zs_electric?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+          username: your-prod-username
+          password: your-prod-password
+          druid:
+            initial-size: 10
+            min-idle: 10
+            max-active: 50
+            max-wait: 60000
+            time-between-eviction-runs-millis: 60000
+            min-evictable-idle-time-millis: 300000
+            validation-query: SELECT 1
+            test-while-idle: true
+            test-on-borrow: false
+            test-on-return: false
+            pool-prepared-statements: true
+            max-pool-prepared-statement-per-connection-size: 20
+            filters: stat,wall
+            connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=1000
+            # 监控页面配置(生产环境关闭)
+            stat-view-servlet:
+              enabled: false
+            web-stat-filter:
+              enabled: false
+
+# MyBatis Plus配置
+mybatis-plus:
+  type-aliases-package: com.zsElectric.openapi.entity
+  mapper-locations: classpath*:mapper/**/*Mapper.xml
+  configuration:
+    map-underscore-to-camel-case: true
+    cache-enabled: true
+    # 生产环境使用slf4j
+    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
+
+# 日志配置
+logging:
+  level:
+    com.zsElectric.openapi: info
+    com.zsElectric.openapi.mapper: warn
+  file:
+    name: /var/log/zsElectric-openapi/application.log
+  logback:
+    rollingpolicy:
+      max-file-size: 100MB
+      max-history: 30
+  pattern:
+    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n"
+    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n"
+
+# 应用配置
+app:
+  swagger-enabled: false

+ 46 - 0
src/main/resources/application.yml

@@ -0,0 +1,46 @@
+spring:
+  application:
+    name: zsElectric-openapi
+  profiles:
+    active: dev
+  servlet:
+    multipart:
+      max-file-size: 50MB
+      max-request-size: 50MB
+
+# 应用端口
+server:
+  port: 8081
+  servlet:
+    context-path: /openapi
+  compression:
+    enabled: true
+
+# 项目信息
+project:
+  version: @project.version@
+  name: zsElectric开放平台
+
+# API安全配置
+api:
+  security:
+    # 签名密钥(生产环境请修改)
+    secret-key: your-secret-key-please-change-in-production
+    # 时间戳过期时间(分钟)
+    timestamp-expire-minutes: 5
+    # 令牌过期时间(小时)
+    token-expire-hours: 24
+    # API调用频率限制(每分钟)
+    rate-limit: 100
+
+# 应用配置
+app:
+  # 是否启用文档
+  swagger-enabled: true
+
+# Knife4j配置
+knife4j:
+  enable: true
+  setting:
+    language: zh_cn
+    swagger-model-name: 实体类列表

+ 64 - 0
src/main/resources/sql/init.sql

@@ -0,0 +1,64 @@
+-- ========================================
+-- zsElectric-OpenApi 开放平台数据库初始化脚本
+-- ========================================
+
+-- 1. 创建开放平台独立数据库(如果不存在)
+CREATE DATABASE IF NOT EXISTS `zs_electric_openapi` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='zsElectric开放平台数据库';
+
+USE `zs_electric_openapi`;
+
+-- 2. 创建开放平台应用信息表
+CREATE TABLE IF NOT EXISTS `openapi_app_info` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `app_id` varchar(64) NOT NULL COMMENT '应用ID',
+  `app_name` varchar(128) NOT NULL COMMENT '应用名称',
+  `app_secret` varchar(128) NOT NULL COMMENT '应用密钥',
+  `contact_name` varchar(64) DEFAULT NULL COMMENT '联系人',
+  `contact_phone` varchar(20) DEFAULT NULL COMMENT '联系电话',
+  `contact_email` varchar(128) DEFAULT NULL COMMENT '联系邮箱',
+  `callback_url` varchar(512) DEFAULT NULL COMMENT '回调地址',
+  `ip_whitelist` varchar(1024) DEFAULT NULL COMMENT 'IP白名单,多个IP用逗号分隔',
+  `status` tinyint DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
+  `daily_limit` int DEFAULT '10000' COMMENT '每日调用限额',
+  `permissions` json DEFAULT NULL COMMENT '权限列表',
+  `remark` varchar(512) DEFAULT NULL COMMENT '备注信息',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_app_id` (`app_id`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='开放平台应用信息表';
+
+-- 3. 创建API调用日志表
+CREATE TABLE IF NOT EXISTS `openapi_api_log` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `app_id` varchar(64) NOT NULL COMMENT '应用ID',
+  `api_path` varchar(512) NOT NULL COMMENT '接口路径',
+  `request_method` varchar(10) NOT NULL COMMENT '请求方法',
+  `request_params` text COMMENT '请求参数',
+  `request_ip` varchar(64) DEFAULT NULL COMMENT '请求IP',
+  `response_code` int DEFAULT NULL COMMENT '响应状态码',
+  `response_message` varchar(512) DEFAULT NULL COMMENT '响应消息',
+  `response_time` bigint DEFAULT NULL COMMENT '响应时间(ms)',
+  `success` tinyint DEFAULT NULL COMMENT '是否成功:0-失败,1-成功',
+  `error_message` text COMMENT '错误信息',
+  `request_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '请求时间',
+  `response_time_field` datetime DEFAULT NULL COMMENT '响应时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_app_id` (`app_id`),
+  KEY `idx_request_time` (`request_time`),
+  KEY `idx_api_path` (`api_path`(255))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='API调用日志表';
+
+-- 4. 插入测试应用数据
+INSERT INTO `openapi_app_info`
+  (`app_id`, `app_name`, `app_secret`, `contact_name`, `contact_phone`, `contact_email`, `status`, `daily_limit`, `remark`)
+VALUES
+  ('TEST_APP_001', '测试应用', 'test_secret_123456', '测试人员', '13800138000', 'test@example.com', 1, 10000, '用于测试的示例应用')
+ON DUPLICATE KEY UPDATE `update_time` = CURRENT_TIMESTAMP;
+
+-- ========================================
+-- 注意: 业务系统的数据库表(charging_station, charging_order等)
+-- 需要在原有的zs_electric数据库中,无需在这里创建
+-- 开放平台通过动态数据源配置访问业务系统的数据库
+-- ========================================