updGpsInfoList = new ArrayList<>();
+ LocalDateTime now = LocalDateTime.now();
+ for (String id : updIdList) {
+ SurvGpsInfo updGpsInfo = new SurvGpsInfo();
+ updGpsInfo.setId(id);
+ updGpsInfo.setLastSyncTime(now);
+ updGpsInfoList.add(updGpsInfo);
+ }
+ gpsInfoService.updateBatchById(updGpsInfoList);
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage());
+ e.printStackTrace();
+ } finally {
+ XxlJobHelper.log("===================GPS记录任务完成=====================");
+ log.warn("===================GPS记录任务完成=====================");
+ }
+ }
+}
diff --git a/src/main/java/com/lanhai/util/PlainIotUtils.java b/src/main/java/com/lanhai/util/PlainIotUtils.java
new file mode 100644
index 0000000..063da6b
--- /dev/null
+++ b/src/main/java/com/lanhai/util/PlainIotUtils.java
@@ -0,0 +1,266 @@
+package com.lanhai.util;
+
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.lanhai.entity.SurvConfig;
+import com.lanhai.entity.SurvDeviceDeploy;
+import com.lanhai.o.vo.HttpResponseVo;
+import com.lanhai.o.vo.VOIotAccess;
+import com.lanhai.service.ISurvSurvConfigService;
+import com.xxl.job.core.context.XxlJobHelper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * IoT厂家工具类(模板)
+ *
+ * 使用方式:复制此类,改类名,改下面4个常量,按需修改各方法
+ */
+@Slf4j
+public class PlainIotUtils {
+
+ // ==================== 改这里 ====================
+ private static final String PROVIDER_CODE = "xxx";
+ private static final String PROVIDER_NAME = "XXX厂家";
+ private static final String ACCESS_CODE = "XXX_ACCESS";
+ private static final String DOMAIN = "https://api.xxx.com";
+
+ // ==================== 常量 ====================
+ private static final String TOKEN_KEY_PREFIX = "iot:token:";
+ private static final String TOKEN_EXPIRE_PREFIX = "iot:token:expire:";
+ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ private static final RedisTemplate redisTemplate = SpringContextUtil.getBean(StringRedisTemplate.class);
+ private static final ISurvSurvConfigService configService = SpringContextUtil.getBean(ISurvSurvConfigService.class);
+
+ // ============================================================
+ // 公开方法
+ // ============================================================
+
+ /**
+ * 获取Token(对外入口,含缓存逻辑)
+ *
+ * @param deploy 设备部署信息
+ * @return Token
+ */
+ public String getToken(SurvDeviceDeploy deploy) {
+ VOIotAccess access = getAccess(deploy);
+ if (access == null) {
+ log.warn("{}: 设备{}密钥未配置", PROVIDER_NAME, deploy.getId());
+ return null;
+ }
+
+ // 1. 查缓存
+ String token = getAppToken(PROVIDER_CODE, access.getAppId());
+ if (StringUtils.isNotBlank(token)) {
+ return token;
+ }
+
+ // 2. 缓存失效,远程获取
+ log.warn("{}: 缓存失效,重新获取Token", PROVIDER_NAME);
+ token = fetchToken(access);
+ if (StringUtils.isNotBlank(token)) {
+ return token;
+ }
+
+ log.error("{}: Token获取最终失败", PROVIDER_NAME);
+ return null;
+ }
+
+ /**
+ * 获取设备实时数据
+ */
+ public JSONObject getDeviceData(SurvDeviceDeploy deploy) {
+ String token = getToken(deploy);
+ if (StringUtils.isBlank(token)) {
+ log.warn("{}: 设备{}获取Token失败", PROVIDER_NAME, deploy.getId());
+ return null;
+ }
+
+ String url = DOMAIN + "/api/device/data";
+
+ Map headers = new HashMap<>();
+ headers.put("Authorization", token);
+
+ Map params = new HashMap<>();
+ params.put("deviceAddrs", deploy.getDeployCode());
+ params.put("rc", UUID.randomUUID().toString().replace("-", ""));
+
+ String result = HttpReqestUtils.getExchangeImgs(url, params, headers);
+ XxlJobHelper.log("========= {} 实时数据返回: {} =========", PROVIDER_NAME, result);
+ log.info("{} 实时数据返回: {}", PROVIDER_NAME, result);
+
+ if (StringUtils.isNotBlank(result)) {
+ return JSONUtil.toBean(result, JSONObject.class);
+ }
+ return null;
+ }
+
+ // ============================================================
+ // 缓存方法(可复用)
+ // ============================================================
+
+ /**
+ * 从缓存获取Token
+ *
+ * @param providerCode 厂家代号
+ * @param appKey 应用AppKey
+ * @return Token,不存在或已过期返回null
+ */
+ public static String getAppToken(String providerCode, String appKey) {
+ if (StringUtils.isAnyBlank(providerCode, appKey)) {
+ return null;
+ }
+
+ String tokenKey = TOKEN_KEY_PREFIX + providerCode + ":" + appKey;
+ String expireKey = TOKEN_EXPIRE_PREFIX + providerCode + ":" + appKey;
+
+ // 检查key是否存在
+ if (Boolean.FALSE.equals(redisTemplate.hasKey(tokenKey))) {
+ return null;
+ }
+
+ String token = CommonToolUtils.getString(redisTemplate.opsForValue().get(tokenKey), "");
+ String expireTimeStr = CommonToolUtils.getString(redisTemplate.opsForValue().get(expireKey), "");
+
+ if (StringUtils.isAnyBlank(token, expireTimeStr)) {
+ return null;
+ }
+
+ // 检查是否过期
+ try {
+ LocalDateTime expireTime = LocalDateTime.parse(expireTimeStr, FORMATTER);
+ if (LocalDateTime.now().isBefore(expireTime)) {
+ log.debug("{}: 命中缓存Token", providerCode);
+ return token;
+ }
+ } catch (DateTimeParseException e) {
+ log.warn("Token过期时间解析失败: providerCode={}, expireTimeStr={}", providerCode, expireTimeStr, e);
+ }
+
+ return null;
+ }
+
+ /**
+ * 缓存Token
+ *
+ * @param providerCode 厂家代号
+ * @param appKey 应用AppKey
+ * @param token Token值
+ * @param expireTime 过期时间(格式:yyyy-MM-dd HH:mm:ss)
+ * @return 是否成功
+ */
+ public static boolean setAppToken(String providerCode, String appKey, String token, String expireTime) {
+ if (StringUtils.isAnyBlank(providerCode, appKey, token, expireTime)) {
+ log.warn("setAppToken参数不完整");
+ return false;
+ }
+
+ String tokenKey = TOKEN_KEY_PREFIX + providerCode + ":" + appKey;
+ String expireKey = TOKEN_EXPIRE_PREFIX + providerCode + ":" + appKey;
+
+ redisTemplate.opsForValue().set(tokenKey, token);
+ redisTemplate.opsForValue().set(expireKey, expireTime);
+
+ log.info("Token缓存成功: providerCode={}, appKey={}, expireTime={}", providerCode, appKey, expireTime);
+ return true;
+ }
+
+ // ============================================================
+ // 私有方法
+ // ============================================================
+
+ /**
+ * 获取应用凭证
+ */
+ private VOIotAccess getAccess(SurvDeviceDeploy deploy) {
+ if (deploy == null) {
+ log.warn("getAccess: deploy为null");
+ return null;
+ }
+
+ SurvConfig config = null;
+
+ // 优先使用设备绑定的配置
+ if (StringUtils.isNotBlank(deploy.getSurvConfigId())) {
+ config = configService.getConfigById(deploy.getSurvConfigId());
+ } else {
+ // 否则按租户+对接识别码查询
+ config = configService.getOneByTypeWithTenant(deploy.getTenantId(), ACCESS_CODE);
+ }
+
+ if (config == null) {
+ log.warn("未找到配置: tenantId={}, configId={}, accessCode={}",
+ deploy.getTenantId(), deploy.getSurvConfigId(), ACCESS_CODE);
+ return null;
+ }
+
+ VOIotAccess access = new VOIotAccess();
+ access.setAppId(config.getConfigKey());
+ access.setAppSecret(config.getConfigValue());
+ return access;
+ }
+
+ /**
+ * 远程获取Token并缓存
+ */
+ private String fetchToken(VOIotAccess access) {
+ if (access == null || StringUtils.isAnyBlank(access.getAppId(), access.getAppSecret())) {
+ log.warn("fetchToken: 凭证不完整");
+ return null;
+ }
+
+ String url = DOMAIN + "/api/getToken";
+
+ Map params = new HashMap<>();
+ params.put("loginName", access.getAppId());
+ params.put("password", access.getAppSecret());
+
+ HttpResponseVo response = HttpReqestUtils.getExchange(url, params, new HashMap<>());
+
+ if (!response.isOk()) {
+ log.error("{} Token请求失败: httpCode", PROVIDER_NAME);
+ return null;
+ }
+
+ JSONObject respJson = response.getData();
+ if (respJson == null) {
+ log.error("{} Token响应为空", PROVIDER_NAME);
+ return null;
+ }
+
+ JSONObject data = respJson.getJSONObject("data");
+ if (data == null) {
+ log.error("{} Token响应data为空, response={}", PROVIDER_NAME, respJson);
+ return null;
+ }
+
+ String token = data.getString("token");
+ Long expireSeconds = data.getLong("expiration");
+
+ if (StringUtils.isBlank(token)) {
+ log.error("{} Token获取失败: {}", PROVIDER_NAME, respJson.getString("msg"));
+ return null;
+ }
+
+ if (expireSeconds == null || expireSeconds <= 0) {
+ log.warn("{} Token过期时间为空或无效,使用默认2小时", PROVIDER_NAME);
+ expireSeconds = 7200L;
+ }
+
+ // 缓存Token
+ String expireTimeStr = LocalDateTime.now().plusSeconds(expireSeconds).format(FORMATTER);
+ setAppToken(PROVIDER_CODE, access.getAppId(), token, expireTimeStr);
+
+ return token;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/lanhai/util/SzDxUtils.java b/src/main/java/com/lanhai/util/SzDxUtils.java
new file mode 100644
index 0000000..51f7462
--- /dev/null
+++ b/src/main/java/com/lanhai/util/SzDxUtils.java
@@ -0,0 +1,327 @@
+package com.lanhai.util;
+
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.lanhai.entity.SurvConfig;
+import com.lanhai.entity.SurvDeviceDeploy;
+import com.lanhai.entity.SurvGpsInfo;
+import com.lanhai.entity.SurvGpsRecord;
+import com.lanhai.o.iot.szdx.ResultDeviceData;
+import com.lanhai.o.iot.szdx.ResultDeviceDataKeys;
+import com.lanhai.o.vo.HttpResponseVo;
+import com.lanhai.o.vo.VOIotAccess;
+import com.lanhai.service.ISurvGpsRecordService;
+import com.lanhai.service.ISurvSurvConfigService;
+import com.xxl.job.core.context.XxlJobHelper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.StringRedisTemplate;
+
+import java.lang.reflect.Field;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * IoT厂家工具类(模板)
+ *
+ * 使用方式:复制此类,改类名,改下面4个常量,按需修改各方法
+ */
+@Slf4j
+public class SzDxUtils {
+
+ // ==================== 改这里 ====================
+ private static final String PROVIDER_CODE = "DiXinSoft";
+ private static final String PROVIDER_NAME = "深圳帝馨";
+ private static final String ACCESS_CODE = "dxsoft_access";
+ private static final String DOMAIN = "http://api.18gps.net/GetDateServices.asmx";
+
+ // ==================== 常量 ====================
+ private static final String TOKEN_KEY_PREFIX = "iot:token:";
+ private static final String TOKEN_EXPIRE_PREFIX = "iot:token:expire:";
+ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ private static final RedisTemplate redisTemplate = SpringContextUtil.getBean(StringRedisTemplate.class);
+ private static final ISurvSurvConfigService configService = SpringContextUtil.getBean(ISurvSurvConfigService.class);
+ private static final ISurvGpsRecordService gpsRecordService = SpringContextUtil.getBean(ISurvGpsRecordService.class);
+ // ============================================================
+ // 公开方法
+ // ============================================================
+
+ /**
+ * 获取Token(对外入口,含缓存逻辑)
+ *
+ * @param gpsInfo 设备部署信息
+ * @return Token
+ */
+ public static String getToken(SurvGpsInfo gpsInfo) {
+ VOIotAccess access = getAccess(gpsInfo);
+ if (access == null) {
+ log.warn("{}: 设备{}密钥未配置", PROVIDER_NAME, gpsInfo.getId());
+ return null;
+ }
+
+ // 1. 查缓存
+ String token = getAppToken(PROVIDER_CODE, access.getAppId());
+ if (StringUtils.isNotBlank(token)) {
+ return token;
+ }
+
+ // 2. 缓存失效,远程获取
+ log.warn("{}: 缓存失效,重新获取Token", PROVIDER_NAME);
+ token = fetchToken(access);
+ if (StringUtils.isNotBlank(token)) {
+ return token;
+ }
+
+ log.error("{}: Token获取最终失败", PROVIDER_NAME);
+ return null;
+ }
+
+ /**
+ * 获取设备实时数据
+ */
+ public static ResultDeviceData getDeviceData(SurvGpsInfo gpsInfo) {
+ String token = getToken(gpsInfo);
+ if (StringUtils.isBlank(token)) {
+ log.warn("{}: 设备{}获取Token失败", PROVIDER_NAME, gpsInfo.getId());
+ return null;
+ }
+
+ String url = DOMAIN + "/GetDate?mapType=GAODE&mds={mds}&method=getUserAndGpsInfoByIDsUtc&option=cn&user_id={user_id}";
+
+ Map headers = new HashMap<>();
+ Map params = new HashMap<>();
+ params.put("mds", token);
+ params.put("user_id", gpsInfo.getGpsDeviceId());
+
+ String result = HttpReqestUtils.getExchangeImgs(url, params, headers);
+ //转utf-8编码
+ result = new String(
+ result.getBytes(StandardCharsets.ISO_8859_1),
+ StandardCharsets.UTF_8
+ );
+
+ XxlJobHelper.log("========= {} 实时数据返回: {} =========", PROVIDER_NAME, result);
+ log.info("{} 实时数据返回: {}", PROVIDER_NAME, result);
+
+ if (StringUtils.isNotBlank(result)) {
+ return JSONUtil.toBean(result, ResultDeviceData.class);
+ }
+ return null;
+ }
+
+ // ============================================================
+ // 缓存方法(可复用)
+ // ============================================================
+
+ /**
+ * 从缓存获取Token
+ *
+ * @param providerCode 厂家代号
+ * @param appKey 应用AppKey
+ * @return Token,不存在或已过期返回null
+ */
+ public static String getAppToken(String providerCode, String appKey) {
+ if (StringUtils.isAnyBlank(providerCode, appKey)) {
+ return null;
+ }
+
+ String tokenKey = TOKEN_KEY_PREFIX + providerCode + ":" + appKey;
+ String expireKey = TOKEN_EXPIRE_PREFIX + providerCode + ":" + appKey;
+
+ // 检查key是否存在
+ if (Boolean.FALSE.equals(redisTemplate.hasKey(tokenKey))) {
+ return null;
+ }
+
+ String token = CommonToolUtils.getString(redisTemplate.opsForValue().get(tokenKey), "");
+ String expireTimeStr = CommonToolUtils.getString(redisTemplate.opsForValue().get(expireKey), "");
+
+ if (StringUtils.isAnyBlank(token, expireTimeStr)) {
+ return null;
+ }
+
+ // 检查是否过期
+ try {
+ LocalDateTime expireTime = LocalDateTime.parse(expireTimeStr, FORMATTER);
+ if (LocalDateTime.now().isBefore(expireTime)) {
+ log.debug("{}: 命中缓存Token", providerCode);
+ return token;
+ }
+ } catch (DateTimeParseException e) {
+ log.warn("Token过期时间解析失败: providerCode={}, expireTimeStr={}", providerCode, expireTimeStr, e);
+ }
+
+ return null;
+ }
+
+ /**
+ * 缓存Token
+ *
+ * @param providerCode 厂家代号
+ * @param appKey 应用AppKey
+ * @param token Token值
+ * @param expireTime 过期时间(格式:yyyy-MM-dd HH:mm:ss)
+ * @return 是否成功
+ */
+ public static boolean setAppToken(String providerCode, String appKey, String token, String expireTime) {
+ if (StringUtils.isAnyBlank(providerCode, appKey, token, expireTime)) {
+ log.warn("setAppToken参数不完整");
+ return false;
+ }
+
+ String tokenKey = TOKEN_KEY_PREFIX + providerCode + ":" + appKey;
+ String expireKey = TOKEN_EXPIRE_PREFIX + providerCode + ":" + appKey;
+
+ redisTemplate.opsForValue().set(tokenKey, token);
+ redisTemplate.opsForValue().set(expireKey, expireTime);
+
+ log.info("Token缓存成功: providerCode={}, appKey={}, expireTime={}", providerCode, appKey, expireTime);
+ return true;
+ }
+
+ // ============================================================
+ // 私有方法
+ // ============================================================
+
+ /**
+ * 获取应用凭证
+ */
+ private static VOIotAccess getAccess(SurvGpsInfo gpsInfo) {
+ if (gpsInfo == null) {
+ log.warn("getAccess: gpsInfo为null");
+ return null;
+ }
+
+ SurvConfig config = null;
+
+ // 优先使用设备绑定的配置
+ config = configService.getConfigById(gpsInfo.getSurvConfigId());
+
+ if (config == null) {
+ log.warn("未找到配置: tenantId={}, configId={}, accessCode={}",
+ gpsInfo.getTenantId(), gpsInfo.getSurvConfigId(), ACCESS_CODE);
+ return null;
+ }
+
+ VOIotAccess access = new VOIotAccess();
+ access.setAppId(config.getConfigKey());
+ access.setAppSecret(config.getConfigValue());
+ return access;
+ }
+
+ /**
+ * 远程获取Token并缓存
+ */
+ private static String fetchToken(VOIotAccess access) {
+ if (access == null || StringUtils.isAnyBlank(access.getAppId(), access.getAppSecret())) {
+ log.warn("fetchToken: 凭证不完整");
+ return null;
+ }
+
+ String url = DOMAIN + "/loginSystem?LoginName={loginName}&LoginPassword={password}&LoginType=ENTERPRISE&language=cn&timeZone=+08&apply=APP";
+
+ Map params = new HashMap<>();
+ params.put("loginName", access.getAppId());
+ params.put("password", access.getAppSecret());
+
+ String response = HttpReqestUtils.getExchangeImgs(url, params, new HashMap<>());
+
+
+
+ if (StringUtils.isBlank(response)) {
+ log.error("{} Token响应为空", PROVIDER_NAME);
+ return null;
+ }
+
+ JSONObject respJson = JSONObject.parseObject(response);
+
+ String token = respJson.getString("mds");
+ Long expireSeconds = 20 * 60L;//20分钟有效期
+
+ if (StringUtils.isBlank(token)) {
+ log.error("{} Token获取失败: {}", PROVIDER_NAME, respJson.getString("msg"));
+ return null;
+ }
+
+ if (expireSeconds == null || expireSeconds <= 0) {
+ log.warn("{} Token过期时间为空或无效,使用默认20分钟", PROVIDER_NAME);
+ expireSeconds = 20 * 60L;
+ }
+
+ // 缓存Token
+ String expireTimeStr = LocalDateTime.now().plusSeconds(expireSeconds).format(FORMATTER);
+ setAppToken(PROVIDER_CODE, access.getAppId(), token, expireTimeStr);
+
+ return token;
+ }
+
+
+ public static boolean processDeviceData(SurvGpsInfo survGpsInfo){
+ boolean isSuccess = false;
+ if(survGpsInfo != null){
+ ResultDeviceData resultDeviceData = getDeviceData(survGpsInfo);
+ if(resultDeviceData != null){
+ if(resultDeviceData.getRecords()!=null){
+ Field[] fields = ResultDeviceDataKeys.class.getDeclaredFields();
+ List