diff --git a/doc/充电桩数据同步.md b/doc/充电桩数据同步.md new file mode 100644 index 000000000..139d098b6 --- /dev/null +++ b/doc/充电桩数据同步.md @@ -0,0 +1,111 @@ +充电桩数据首先是在web项目生成的,现在需要把web项目生成的充电桩数据同步到JCPP项目中,用于登录鉴权等操作 + +下面是web项目中充电桩相关表结构(注意使用的是mysql 5.7) + +~~~mysql +CREATE TABLE `pile_basic_info` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(32) DEFAULT NULL COMMENT '别名', + `sn` varchar(20) DEFAULT NULL COMMENT '桩号', + `business_type` char(5) DEFAULT NULL COMMENT '经营类型(1-运营桩;2-个人桩)', + `secret_key` varchar(10) DEFAULT NULL COMMENT '个人桩密钥', + `software_protocol` varchar(20) DEFAULT NULL COMMENT '软件协议(yunkuaichongV150--云快充V1.5;yunkuaichongV160--云快充V1.6;yonglianV1--永联;youdianV1--友电)', + `production_date` datetime DEFAULT NULL COMMENT '生产日期', + `licence_id` int(11) DEFAULT NULL COMMENT '证书编号', + `model_id` int(11) DEFAULT NULL COMMENT '充电桩型号', + `sim_id` int(11) DEFAULT NULL COMMENT 'sim卡id', + `iccid` varchar(50) DEFAULT NULL COMMENT 'sim卡iccid', + `merchant_id` int(11) DEFAULT NULL COMMENT '运营商id', + `station_id` int(11) DEFAULT NULL COMMENT '充电站id', + `longitude` varchar(30) DEFAULT NULL COMMENT '经度', + `latitude` varchar(30) DEFAULT NULL COMMENT '纬度', + `vin_flag` char(5) DEFAULT NULL COMMENT '是否支持汽车VIN码识别', + `fault_reason` varchar(255) DEFAULT NULL COMMENT '故障原因', + `create_by` varchar(20) DEFAULT NULL COMMENT '创建人', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(20) DEFAULT NULL COMMENT '更新人', + `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` char(5) DEFAULT '0' COMMENT '删除标识(0-正常;1-删除)', + `remark` varchar(255) DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_sn` (`sn`) USING BTREE, + KEY `idx_station_id` (`station_id`) USING BTREE, + KEY `idx_iccid` (`iccid`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=6845 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='充电桩基本信息表'; + +CREATE TABLE `pile_connector_info` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(20) DEFAULT NULL COMMENT '名称', + `pile_sn` varchar(20) DEFAULT NULL COMMENT '所属充电桩sn', + `pile_connector_code` varchar(20) DEFAULT NULL COMMENT '充电枪编号,由充电桩SN+01生成', + `status` varchar(5) DEFAULT '0' COMMENT '状态 0:离网 (默认);1:空闲;2:占用(未充电);3:占用(充电中);4:占用(预约锁定) ;255:故障 ', + `park_no` varchar(5) DEFAULT NULL COMMENT '车位号(推送联联平台所用字段)', + `create_by` varchar(20) DEFAULT NULL COMMENT '创建人', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(20) DEFAULT NULL COMMENT '更新人', + `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` char(1) DEFAULT '0' COMMENT '删除标识(0-正常;1-删除)', + PRIMARY KEY (`id`) USING BTREE, + KEY `idx_pile_sn` (`pile_sn`) USING BTREE, + KEY `idx_code` (`pile_connector_code`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=30061 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='充电桩枪口信息表'; +~~~ + + + +下面是JCPP项目充电桩相关表结构(注意使用的是postgreSQL 17) + +~~~ +CREATE TABLE "public"."t_pile" ( + "id" uuid NOT NULL, + "created_time" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_time" timestamp(6), + "additional_info" jsonb, + "pile_name" varchar(255) COLLATE "pg_catalog"."default" NOT NULL, + "pile_code" varchar(255) COLLATE "pg_catalog"."default" NOT NULL, + "protocol" varchar(255) COLLATE "pg_catalog"."default" NOT NULL, + "station_id" uuid NOT NULL, + "brand" varchar(255) COLLATE "pg_catalog"."default", + "model" varchar(255) COLLATE "pg_catalog"."default", + "manufacturer" varchar(255) COLLATE "pg_catalog"."default", + "type" varchar(16) COLLATE "pg_catalog"."default" NOT NULL, + "version" int4 DEFAULT 1, + CONSTRAINT "pile_pkey" PRIMARY KEY ("id") +) +; + +ALTER TABLE "public"."t_pile" + OWNER TO "postgres"; + +CREATE UNIQUE INDEX "uni_pile_code" ON "public"."t_pile" USING btree ( + "pile_code" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST +); + +CREATE TABLE "public"."t_gun" ( + "id" uuid NOT NULL, + "created_time" timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_time" timestamp(6), + "additional_info" varchar(255) COLLATE "pg_catalog"."default", + "gun_no" varchar(255) COLLATE "pg_catalog"."default" NOT NULL, + "gun_name" varchar(255) COLLATE "pg_catalog"."default" NOT NULL, + "gun_code" varchar(255) COLLATE "pg_catalog"."default" NOT NULL, + "station_id" uuid NOT NULL, + "pile_id" uuid NOT NULL, + "version" int4 DEFAULT 1, + CONSTRAINT "t_gun_pkey" PRIMARY KEY ("id") +) +; + +ALTER TABLE "public"."t_gun" + OWNER TO "postgres"; + +CREATE UNIQUE INDEX "uni_gun_code" ON "public"."t_gun" USING btree ( + "gun_code" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST +); + +CREATE UNIQUE INDEX "uni_gun_pile_gun_no" ON "public"."t_gun" USING btree ( + "pile_id" "pg_catalog"."uuid_ops" ASC NULLS LAST, + "gun_no" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST +); +~~~ + diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/jcpp/service/IJcppAuthService.java b/jsowell-pile/src/main/java/com/jsowell/pile/jcpp/service/IJcppAuthService.java new file mode 100644 index 000000000..96e995125 --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/jcpp/service/IJcppAuthService.java @@ -0,0 +1,23 @@ +package com.jsowell.pile.jcpp.service; + +/** + * JCPP 认证服务接口 + * + * @author jsowell + */ +public interface IJcppAuthService { + + /** + * 获取 JCPP 访问令牌 + * 如果 Redis 中有缓存且未过期,直接返回 + * 否则调用登录接口获取新的 token + * + * @return 访问令牌 + */ + String getAccessToken(); + + /** + * 清除缓存的令牌(用于强制刷新) + */ + void clearToken(); +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/jcpp/service/impl/JcppAuthServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/jcpp/service/impl/JcppAuthServiceImpl.java new file mode 100644 index 000000000..e125ad63b --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/jcpp/service/impl/JcppAuthServiceImpl.java @@ -0,0 +1,124 @@ +package com.jsowell.pile.jcpp.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.jsowell.pile.jcpp.service.IJcppAuthService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.concurrent.TimeUnit; + +/** + * JCPP 认证服务实现 + * + * @author jsowell + */ +@Slf4j +@Service +public class JcppAuthServiceImpl implements IJcppAuthService { + + private static final String JCPP_TOKEN_KEY = "jcpp:auth:token"; + private static final long TOKEN_EXPIRE_MINUTES = 30L; + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + @Autowired + private RestTemplate restTemplate; + + @Value("${jcpp.sync.api-url:http://localhost:8180/api/sync}") + private String jcppApiUrl; + + @Value("${jcpp.auth.username:sanbing}") + private String username; + + @Value("${jcpp.auth.password:password123}") + private String password; + + /** + * 获取 JCPP 访问令牌 + */ + @Override + public String getAccessToken() { + // 1. 尝试从 Redis 获取缓存的 token + String cachedToken = stringRedisTemplate.opsForValue().get(JCPP_TOKEN_KEY); + if (cachedToken != null && !cachedToken.isEmpty()) { + log.debug("使用缓存的 JCPP token"); + return cachedToken; + } + + // 2. 缓存中没有,调用登录接口获取新的 token + log.info("缓存中没有 token,调用登录接口获取"); + String token = login(); + + // 3. 将 token 缓存到 Redis,有效期 30 分钟 + if (token != null && !token.isEmpty()) { + stringRedisTemplate.opsForValue().set(JCPP_TOKEN_KEY, token, TOKEN_EXPIRE_MINUTES, TimeUnit.MINUTES); + log.info("JCPP token 已缓存,有效期 {} 分钟", TOKEN_EXPIRE_MINUTES); + } + + return token; + } + + /** + * 清除缓存的令牌 + */ + @Override + public void clearToken() { + stringRedisTemplate.delete(JCPP_TOKEN_KEY); + log.info("已清除缓存的 JCPP token"); + } + + /** + * 调用 JCPP 登录接口 + */ + private String login() { + // 构建登录 URL(从 api-url 中提取基础 URL) + String baseUrl = jcppApiUrl.replace("/api/sync", ""); + String loginUrl = baseUrl + "/api/auth/login"; + + try { + // 构建请求体 + JSONObject requestBody = new JSONObject(); + requestBody.put("username", username); + requestBody.put("password", password); + + // 设置请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity entity = new HttpEntity<>(requestBody.toJSONString(), headers); + + log.info("调用 JCPP 登录接口: {}", loginUrl); + + // 发送请求 + ResponseEntity response = restTemplate.postForEntity(loginUrl, entity, String.class); + + if (response.getStatusCode() == HttpStatus.OK) { + // 解析响应,提取 token + JSONObject responseBody = JSON.parseObject(response.getBody()); + String token = responseBody.getString("token"); + + if (token != null && !token.isEmpty()) { + log.info("JCPP 登录成功,获取到 token"); + return token; + } else { + log.error("JCPP 登录响应中没有 token: {}", response.getBody()); + throw new RuntimeException("登录响应中没有 token"); + } + } else { + log.error("JCPP 登录失败,状态码: ", response.getStatusCode()); + throw new RuntimeException("登录失败,状态码: " + response.getStatusCode()); + } + + } catch (Exception e) { + log.error("调用 JCPP 登录接口异常", e); + throw new RuntimeException("登录失败: " + e.getMessage(), e); + } + } +}