# JCPP 项目充电桩数据同步接口实现 Prompt > 请将以下内容复制给 JCPP 项目的 Claude Code 执行 --- ## 任务概述 需要在 JCPP 项目中实现充电桩数据同步接口,用于接收来自 Web 项目的充电桩和充电枪数据。 ## 背景说明 Web 项目(MySQL)中维护了充电桩的主数据,现在需要将这些数据同步到 JCPP 项目(PostgreSQL)中,用于登录鉴权等操作。 ### 数据流向 ``` Web 项目 (MySQL) → HTTP API → JCPP 项目 (PostgreSQL) ``` ### 关键差异 1. **主键类型**:Web 使用 int,JCPP 使用 uuid 2. **station_id 类型**:Web 是 int,JCPP 是 uuid(需要映射) 3. **字段名称**:sn → pile_code, name → pile_name --- ## 需要实现的功能 ### 1. 认证接口 **接口路径**:`POST /api/auth/login` **请求格式**: ```json { "username": "sanbing", "password": "password123" } ``` **响应格式**: ```json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expiresIn": 1800 } ``` **说明**: - 用于 Web 项目获取访问令牌 - token 有效期 30 分钟 - Web 项目会将 token 缓存在 Redis 中 --- ### 2. 充电桩同步接口 **接口路径**:`POST /api/sync/piles` **认证方式**:Bearer Token(在请求头中添加 `Authorization: Bearer {token}`) **重要说明**: - ✅ 所有充电桩的 `station_id` 统一使用固定值:`88bca8da-cdbf-6587-aecc-75784838c501` - Web 项目的原始 station_id 保存在 `additionalInfo.webStationId` 中便于追溯 **请求格式**: ```json { "piles": [ { "pileCode": "20231212000010", "pileName": "1号充电桩", "protocol": "yunkuaichongV150", "brand": "特来电", "model": "AC-7KW", "manufacturer": "特来电", "type": "OPERATION", "additionalInfo": { "webPileId": 6844, "webStationId": 123, "businessType": "1", "secretKey": "abc123", "longitude": "116.404", "latitude": "39.915", "iccid": "89860123456789012345" } } ] } ``` **字段说明**: - `pileCode`:充电桩编码(对应 Web 的 sn),唯一标识 - `pileName`:充电桩名称(对应 Web 的 name) - `protocol`:软件协议(对应 Web 的 software_protocol) - `brand`:品牌(可为空) - `model`:型号(可为空) - `manufacturer`:制造商(可为空) - `type`:类型,枚举值: - `OPERATION`:运营桩(对应 Web 的 business_type = "1") - `PERSONAL`:个人桩(对应 Web 的 business_type = "2") - `additionalInfo`:附加信息(JSON 格式),包含: - `webPileId`:Web 项目的充电桩 ID - `webStationId`:Web 项目的充电站 ID(原始 int 值) - 其他 Web 项目的字段 **响应格式**: ```json { "success": true, "message": "同步成功", "results": [ { "pileCode": "20231212000010", "pileId": "550e8400-e29b-41d4-a716-446655440000", "success": true, "message": "创建成功" } ] } ``` **处理逻辑**: 1. 遍历 `piles` 数组 2. 对于每个充电桩: - 根据 `pileCode` 查询 `t_pile` 表,判断是否已存在 - **station_id 统一使用固定值**:`88bca8da-cdbf-6587-aecc-75784838c501` - 如果充电桩已存在,执行 UPDATE 操作 - 如果充电桩不存在,执行 INSERT 操作(生成新的 uuid) - 将 `additionalInfo` 存储为 jsonb 类型 3. 返回每个充电桩的处理结果 **注意事项**: - 使用事务保证数据一致性 - pile_code 必须唯一(已有唯一索引) - 所有充电桩的 station_id 都使用固定值 `88bca8da-cdbf-6587-aecc-75784838c501` - **需要验证 Authorization 请求头中的 token** - token 无效时返回 401 Unauthorized --- ### 3. 充电枪同步接口 **接口路径**:`POST /api/sync/guns` **认证方式**:Bearer Token(在请求头中添加 `Authorization: Bearer {token}`) **重要说明**: - ✅ 所有充电枪的 `station_id` 统一使用固定值:`88bca8da-cdbf-6587-aecc-75784838c501` **请求格式**: ```json { "guns": [ { "gunCode": "2023121200001001", "gunName": "1号枪", "gunNo": "01", "pileCode": "20231212000010", "additionalInfo": { "webGunId": 30060, "status": "1", "parkNo": "A01" } } ] } ``` **字段说明**: - `gunCode`:充电枪编码(对应 Web 的 pile_connector_code),唯一标识 - `gunName`:充电枪名称(对应 Web 的 name) - `gunNo`:枪号(从 gunCode 提取最后 2 位) - `pileCode`:所属充电桩编码(对应 Web 的 pile_sn) - `additionalInfo`:附加信息(JSON 格式),包含 Web 项目的其他字段 **响应格式**: ```json { "success": true, "message": "同步成功", "results": [ { "gunCode": "2023121200001001", "gunId": "660e8400-e29b-41d4-a716-446655440000", "success": true, "message": "创建成功" } ] } ``` **处理逻辑**: 1. 遍历 `guns` 数组 2. 对于每个充电枪: - 根据 `gunCode` 查询 `t_gun` 表,判断是否已存在 - 根据 `pileCode` 查询 `t_pile` 表,获取 `pile_id` (uuid) - 如果充电桩不存在,记录错误并跳过该充电枪 - **station_id 统一使用固定值**:`88bca8da-cdbf-6587-aecc-75784838c501` - 如果充电枪已存在,执行 UPDATE 操作 - 如果充电枪不存在,执行 INSERT 操作(生成新的 uuid) 3. 返回每个充电枪的处理结果 **注意事项**: - 充电桩必须先于充电枪同步 - gun_code 必须唯一(已有唯一索引) - (pile_id, gun_no) 组合必须唯一(已有唯一索引) - 所有充电枪的 station_id 都使用固定值 `88bca8da-cdbf-6587-aecc-75784838c501` - **需要验证 Authorization 请求头中的 token** - token 无效时返回 401 Unauthorized --- ## 实现建议 ### 技术栈 - **框架**:Spring Boot - **数据库**:PostgreSQL 17 - **ORM**:JPA / MyBatis - **JSON 处理**:Jackson / Gson ### 代码结构建议 ``` src/main/java/com/jcpp/ ├── controller/ │ ├── AuthController.java # 认证接口 Controller │ └── SyncController.java # 同步接口 Controller ├── service/ │ ├── AuthService.java # 认证服务接口 │ ├── PileSyncService.java # 充电桩同步服务接口 │ └── GunSyncService.java # 充电枪同步服务接口 ├── service/impl/ │ ├── AuthServiceImpl.java │ ├── PileSyncServiceImpl.java │ └── GunSyncServiceImpl.java ├── entity/ │ ├── Pile.java # t_pile 实体 │ └── Gun.java # t_gun 实体 ├── repository/ │ ├── PileRepository.java │ └── GunRepository.java ├── dto/ │ ├── LoginRequest.java # 登录请求 │ ├── LoginResponse.java # 登录响应 │ ├── PileSyncDTO.java # 充电桩同步 DTO │ ├── GunSyncDTO.java # 充电枪同步 DTO │ ├── SyncRequest.java # 同步请求 │ └── SyncResponse.java # 同步响应 └── security/ └── JwtTokenProvider.java # JWT Token 生成和验证 ``` ### 关键代码示例 **PileSyncService 接口**: ```java public interface PileSyncService { /** * 同步充电桩数据 * @param piles 充电桩列表 * @return 同步结果 */ SyncResponse syncPiles(List piles); } ``` **处理逻辑伪代码**: ```java @Transactional public SyncResponse syncPiles(List piles) { List results = new ArrayList<>(); // 固定的 station_id UUID FIXED_STATION_ID = UUID.fromString("88bca8da-cdbf-6587-aecc-75784838c501"); for (PileSyncDTO dto : piles) { try { // 1. 查询充电桩是否存在 Pile existingPile = pileRepository.findByPileCode(dto.getPileCode()); if (existingPile != null) { // 更新 existingPile.setPileName(dto.getPileName()); existingPile.setProtocol(dto.getProtocol()); existingPile.setStationId(FIXED_STATION_ID); // 使用固定值 existingPile.setBrand(dto.getBrand()); existingPile.setModel(dto.getModel()); existingPile.setManufacturer(dto.getManufacturer()); existingPile.setType(dto.getType()); existingPile.setAdditionalInfo(dto.getAdditionalInfo()); existingPile.setUpdatedTime(new Date()); pileRepository.save(existingPile); results.add(SyncResult.success(dto.getPileCode(), existingPile.getId(), "更新成功")); } else { // 创建 Pile newPile = new Pile(); newPile.setId(UUID.randomUUID()); newPile.setPileCode(dto.getPileCode()); newPile.setPileName(dto.getPileName()); newPile.setProtocol(dto.getProtocol()); newPile.setStationId(FIXED_STATION_ID); // 使用固定值 newPile.setBrand(dto.getBrand()); newPile.setModel(dto.getModel()); newPile.setManufacturer(dto.getManufacturer()); newPile.setType(dto.getType()); newPile.setAdditionalInfo(dto.getAdditionalInfo()); newPile.setCreatedTime(new Date()); pileRepository.save(newPile); results.add(SyncResult.success(dto.getPileCode(), newPile.getId(), "创建成功")); } } catch (Exception e) { results.add(SyncResult.fail(dto.getPileCode(), e.getMessage())); } } return SyncResponse.build(results); } ``` --- ## 测试建议 ### 1. 单元测试 - 测试 station_id 映射查询 - 测试充电桩创建 - 测试充电桩更新 - 测试充电枪创建 - 测试充电枪更新 ### 2. 集成测试 - 测试完整的同步流程 - 测试异常场景(映射不存在、充电桩不存在等) - 测试并发同步 ### 3. 性能测试 - 测试批量同步性能(100、500、1000 条数据) - 测试数据库连接池配置 --- ## 注意事项 1. **事务处理** - 每批数据使用一个事务 - 单个充电桩失败不影响其他充电桩 2. **错误处理** - 记录详细的错误信息 - 返回明确的错误原因 3. **日志记录** - 记录同步开始和结束时间 - 记录成功和失败的数量 - 记录详细的错误日志 4. **性能优化** - 使用批量查询减少数据库访问 - 合理设置数据库连接池大小 - 考虑使用缓存优化映射查询 5. **安全性** - 添加接口鉴权(JWT Token) - 验证请求数据的合法性 - 防止 SQL 注入 - token 有效期 30 分钟 --- ## 交付物 1. **代码** - Controller、Service、Repository、Entity、DTO 等完整代码 - 单元测试代码 2. **接口文档** - Swagger / OpenAPI 文档 - 接口调用示例 3. **部署说明** - 配置项说明 - 部署步骤 --- ## 联调准备 完成实现后,请提供: 1. 接口地址(如:http://jcpp-server:8080/api/sync) 2. 测试账号(如果需要鉴权) 3. 测试数据示例 **重要提醒**: - 确保 JCPP 数据库中已存在 station_id 为 `88bca8da-cdbf-6587-aecc-75784838c501` 的充电站记录 - 如果不存在,需要先创建该充电站记录 --- ## 问题反馈 如有任何问题或需要澄清的地方,请及时反馈。