diff --git a/coolstore-partner-common/src/main/java/com/cool/store/constants/RedisConstant.java b/coolstore-partner-common/src/main/java/com/cool/store/constants/RedisConstant.java
index 29f07024f..7dbe943e4 100644
--- a/coolstore-partner-common/src/main/java/com/cool/store/constants/RedisConstant.java
+++ b/coolstore-partner-common/src/main/java/com/cool/store/constants/RedisConstant.java
@@ -283,4 +283,10 @@ public class RedisConstant {
public static final String SUBMIT_BUILD_KEY = "submit_build_key_";
public static final String GET_AI_MODULE = "get_ai_module_";
+
+ public static final String HUOMA_STORE_DEVICE_RESOURCE_KEY = "huoma_store_device_resource";
+
+ public static final String HUO_MA_STORE_ID = "huo_ma_store_id";
+
+ public static final String HUO_MA_TOKEN= "huo_ma_token:{0}";
}
diff --git a/coolstore-partner-common/src/main/java/com/cool/store/utils/BrowserVersionUtils.java b/coolstore-partner-common/src/main/java/com/cool/store/utils/BrowserVersionUtils.java
new file mode 100644
index 000000000..663a1cd9a
--- /dev/null
+++ b/coolstore-partner-common/src/main/java/com/cool/store/utils/BrowserVersionUtils.java
@@ -0,0 +1,62 @@
+package com.cool.store.utils;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @Author suzhuhong
+ * @Date 2025/11/4 17:34
+ * @Version 1.0
+ */
+public class BrowserVersionUtils {
+
+
+ /**
+ * 检测是否为旧版Chrome浏览器(版本小于60)
+ * @param userAgent 浏览器User-Agent字符串
+ * @return true-是旧版Chrome,false-不是旧版Chrome
+ */
+ public static boolean isOldChromeBrowser(String userAgent) {
+ if (userAgent == null || userAgent.isEmpty()) {
+ return false;
+ }
+
+ // 检查是否是Chrome浏览器
+ if (!userAgent.contains("Chrome")) {
+ return false; // 不是Chrome浏览器
+ }
+
+ // 提取Chrome版本号
+ Integer chromeVersion = extractChromeVersion(userAgent);
+
+ if (chromeVersion == null) {
+ return false; // 无法提取版本号
+ }
+
+ // 判断版本是否小于60
+ return chromeVersion < 60;
+ }
+
+ /**
+ * 从User-Agent中提取Chrome主版本号
+ * @param userAgent 浏览器User-Agent字符串
+ * @return Chrome主版本号,如果无法提取返回null
+ */
+ public static Integer extractChromeVersion(String userAgent) {
+ // 正则表达式匹配 Chrome/版本号 模式
+ Pattern pattern = Pattern.compile("Chrome/(\\d+)");
+ Matcher matcher = pattern.matcher(userAgent);
+
+ if (matcher.find()) {
+ try {
+ return Integer.parseInt(matcher.group(1));
+ } catch (NumberFormatException e) {
+ System.err.println("版本号格式错误: " + matcher.group(1));
+ return null;
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/HuoMaAccountDTO.java b/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/HuoMaAccountDTO.java
new file mode 100644
index 000000000..4ccaccdf9
--- /dev/null
+++ b/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/HuoMaAccountDTO.java
@@ -0,0 +1,39 @@
+package com.cool.store.dto.huoma;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ *
+ * 火码账号DTO
+ *
+ *
+ * @author wangff
+ * @since 2025/9/23
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class HuoMaAccountDTO {
+ /**
+ * 账号
+ */
+ private String account;
+
+ /**
+ * 密码
+ */
+ private String password;
+
+ /**
+ * 是否已查询
+ */
+ private Boolean isQuery;
+
+ public HuoMaAccountDTO(String account, String password) {
+ this.account = account;
+ this.password = password;
+ this.isQuery = false;
+ }
+}
diff --git a/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/StoreEquipmentDTO.java b/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/StoreEquipmentDTO.java
new file mode 100644
index 000000000..cfae3c530
--- /dev/null
+++ b/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/StoreEquipmentDTO.java
@@ -0,0 +1,59 @@
+package com.cool.store.dto.huoma;
+
+import com.cool.store.utils.BrowserVersionUtils;
+import lombok.Data;
+
+import java.util.Objects;
+
+/**
+ * @Author: WangShuo
+ * @Date: 2025/08/13/16:24
+ * @Version 1.0
+ * @注释:
+ */
+@Data
+public class StoreEquipmentDTO {
+ /**
+ * 已授权登录数
+ */
+ private Integer activeCount;
+
+ /**
+ * 总设备
+ */
+ private Integer terminalCount;
+
+ /**
+ * 网点ID
+ */
+ private Integer pointId;
+
+ /**
+ * 网点号
+ */
+ private String pointCode;
+
+ /**
+ * 签到数
+ */
+ private Integer signCount;
+
+ /**
+ * 在线
+ */
+
+ private Integer connectCount;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ StoreEquipmentDTO that = (StoreEquipmentDTO) o;
+ return Objects.equals(pointCode, that.pointCode);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(pointCode);
+ }
+}
diff --git a/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/StoreRequestDTO.java b/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/StoreRequestDTO.java
new file mode 100644
index 000000000..5a22c8095
--- /dev/null
+++ b/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/StoreRequestDTO.java
@@ -0,0 +1,40 @@
+package com.cool.store.dto.huoma;
+
+import lombok.Data;
+
+/**
+ * @Author suzhuhong
+ * @Date 2025/8/18 16:00
+ * @Version 1.0
+ */
+@Data
+public class StoreRequestDTO {
+
+ private String reportCode;
+
+ private Integer index;
+
+ private Integer size;
+
+ private Params params;
+
+ public StoreRequestDTO(String reportCode, Integer index, Integer size, String shopCode) {
+ this.reportCode = reportCode;
+ this.index = index;
+ this.size = size;
+ this.params = new Params(shopCode);
+ }
+
+ @Data
+ static class Params{
+ private String inputText_2;
+
+ public Params(String inputText_2) {
+ this.inputText_2 = inputText_2;
+ }
+ }
+
+
+
+
+}
diff --git a/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/StoreXinFaDetailRequestDTO.java b/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/StoreXinFaDetailRequestDTO.java
new file mode 100644
index 000000000..4e30a38ef
--- /dev/null
+++ b/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/StoreXinFaDetailRequestDTO.java
@@ -0,0 +1,24 @@
+package com.cool.store.dto.huoma;
+
+import lombok.Data;
+
+/**
+ * @Author suzhuhong
+ * @Date 2025/8/18 16:38
+ * @Version 1.0
+ */
+@Data
+public class StoreXinFaDetailRequestDTO {
+
+ private Integer index;
+
+ private Integer size;
+
+ private Integer pointId;
+
+ public StoreXinFaDetailRequestDTO(Integer index, Integer size, Integer pointId) {
+ this.index = index;
+ this.size = size;
+ this.pointId = pointId;
+ }
+}
diff --git a/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/StoreXinFaDeviceDetail.java b/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/StoreXinFaDeviceDetail.java
new file mode 100644
index 000000000..776d2de3c
--- /dev/null
+++ b/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/StoreXinFaDeviceDetail.java
@@ -0,0 +1,74 @@
+package com.cool.store.dto.huoma;
+
+import com.cool.store.utils.BrowserVersionUtils;
+import com.cool.store.utils.StringUtil;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @Author suzhuhong
+ * @Date 2025/8/18 16:27
+ * @Version 1.0
+ */
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class StoreXinFaDeviceDetail {
+
+ /**
+ * 设备ID
+ */
+ @ApiModelProperty(value = "设备ID")
+ private String deviceId;
+
+ /**
+ * 设备名称
+ */
+ @ApiModelProperty(value = "设备名称")
+ private String name;
+
+ /**
+ * 设备连接状态 0:未连接 1:已连接
+ */
+ @ApiModelProperty(value = "设备连接状态 false:未连接 true:已连接")
+ private Boolean isConnect;
+
+ /**
+ * 设备总内存
+ */
+ @ApiModelProperty(value = "设备总内存")
+ private String totalRam;
+
+ /**
+ * 设备可用内存
+ */
+ @ApiModelProperty(value = "设备可用内存")
+ private String availRam;
+
+ /**
+ * 设备浏览器信息
+ */
+ @ApiModelProperty(value = "设备浏览器信息")
+ private String userAgent;
+
+ /**
+ * 内用内存小于300M 或者 浏览器版本小于60
+ */
+ @ApiModelProperty(value = "内用内存小于300M 或者 浏览器版本小于60")
+ private Boolean flag;
+
+ public boolean getFlag() {
+ if (StringUtil.isEmpty(availRam)||StringUtil.isEmpty(userAgent)){
+ return false;
+ }
+ try {
+ Boolean isOldChromeBrowser = BrowserVersionUtils.isOldChromeBrowser(userAgent);
+ long availableMemoryMB = Long.parseLong(availRam) / (1024 * 1024);
+ return availableMemoryMB < 300 || isOldChromeBrowser;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+}
diff --git a/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/TagDTO.java b/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/TagDTO.java
new file mode 100644
index 000000000..6752e601a
--- /dev/null
+++ b/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/TagDTO.java
@@ -0,0 +1,29 @@
+package com.cool.store.dto.huoma;
+
+import io.swagger.models.auth.In;
+import lombok.Data;
+
+/**
+ * @Author suzhuhong
+ * @Date 2025/11/5 9:33
+ * @Version 1.0
+ */
+@Data
+public class TagDTO {
+
+ private String channelType;
+
+ private Integer index;
+
+ private Integer size;
+
+ private String type;
+
+ public TagDTO(String channelType, Integer index, Integer size, String type) {
+ this.channelType = channelType;
+ this.index = index;
+ this.size = size;
+ this.type = type;
+ }
+
+}
diff --git a/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/TagDetailDTO.java b/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/TagDetailDTO.java
new file mode 100644
index 000000000..896b07378
--- /dev/null
+++ b/coolstore-partner-model/src/main/java/com/cool/store/dto/huoma/TagDetailDTO.java
@@ -0,0 +1,17 @@
+package com.cool.store.dto.huoma;
+
+import lombok.Data;
+
+/**
+ * @Author suzhuhong
+ * @Date 2025/11/5 9:41
+ * @Version 1.0
+ */
+@Data
+public class TagDetailDTO {
+
+ private Integer id;
+
+ private String name;
+
+}
diff --git a/coolstore-partner-service/src/main/java/com/cool/store/service/xinfa/XinFaDeviceService.java b/coolstore-partner-service/src/main/java/com/cool/store/service/xinfa/XinFaDeviceService.java
new file mode 100644
index 000000000..b73142e09
--- /dev/null
+++ b/coolstore-partner-service/src/main/java/com/cool/store/service/xinfa/XinFaDeviceService.java
@@ -0,0 +1,383 @@
+package com.cool.store.service.xinfa;
+
+import com.alibaba.fastjson.JSONObject;
+import com.cool.store.constants.RedisConstant;
+import com.cool.store.dto.huoma.*;
+import com.cool.store.enums.ErrorCodeEnum;
+import com.cool.store.exception.ServiceException;
+import com.cool.store.utils.RedisUtilPool;
+import com.cool.store.utils.poi.constant.Constants;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.*;
+
+/**
+ * @Author suzhuhong
+ * @Date 2025/11/4 15:47
+ * @Version 1.0
+ */
+@Service
+@Slf4j
+public class XinFaDeviceService {
+
+ @Value("${huoMa.direct.stores.account}")
+ private String huoMaDirectStoresAccount;
+ @Value("${huoMa.direct.stores.password}")
+ private String huoMaDirectStoresPassword;
+ @Value("${huoMa.franchise.stores.account}")
+ private String huoMaFranchiseStoresAccount;
+ @Value("${huoMa.franchise.stores.password}")
+ private String huoMaFranchiseStoresPassword;
+ @Value("${huoMa.restaurant.stores.account}")
+ private String huoMaRestaurantStoresAccount;
+ @Value("${huoMa.restaurant.stores.password}")
+ private String huoMaRestaurantStoresPassword;
+ @Resource
+ RedisUtilPool redisUtilPool;
+ @Value("${huoMa.token.url}")
+ private String huoMaTokenUrl;
+ @Value("${huoMa.get.point.terminal.url}")
+ private String huoMaGetPointTerminalUrl;
+ @Value("${huoMa.id.url}")
+ private String huoMaGetStoreIdUrl;
+ @Value("${huoMa.store.device.detail.url}")
+ private String huoMaGetStoreXinFaDeviceDetailUrl ;
+ @Value("${huoMa.get.tag.url }")
+ private String huoMaGetTagUrl;
+
+
+ private final Map accountMap = new HashMap<>();
+
+ @PostConstruct
+ public void initAccountMap() {
+ accountMap.put("restaurant", new HuoMaAccountDTO(huoMaRestaurantStoresAccount, huoMaRestaurantStoresPassword));
+ accountMap.put("direct", new HuoMaAccountDTO(huoMaDirectStoresAccount, huoMaDirectStoresPassword));
+ accountMap.put("franchise", new HuoMaAccountDTO(huoMaFranchiseStoresAccount, huoMaFranchiseStoresPassword));
+ }
+
+ public String getStoreToken(String account, String password) {
+ String key = MessageFormat.format(RedisConstant.HUO_MA_TOKEN, account);
+ String accessToken = redisUtilPool.getString(key);
+ if (accessToken != null) {
+ return accessToken;
+ }
+ Map requestBody = new HashMap<>();
+ requestBody.put("account", account);
+ requestBody.put("password", password);
+ String responseBody = sendPostRequest(JSONObject.toJSONString(requestBody), huoMaTokenUrl);
+ try{
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode rootNode = mapper.readTree(responseBody);
+ String token = rootNode.path("data").path("token").asText();
+ //缓存60秒
+ redisUtilPool.setString(key, token,60*60);
+ return token;
+ }catch (Exception e){
+ log.error("解析获取token失败,url:{},responseBody:{}",huoMaTokenUrl, responseBody);
+ }
+ return null;
+ }
+
+
+ public List getStoreEquipmentDataByStoreNumList(List storeNumList, String token) {
+ Map> requestBody = new HashMap<>();
+ requestBody.put("codeList", storeNumList);
+ String responseBody = sendPostRequestByToken(JSONObject.toJSONString(requestBody), huoMaGetPointTerminalUrl,token);
+ try{
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode rootNode = mapper.readTree(responseBody);
+ return mapper.convertValue(rootNode.get("data"),
+ mapper.getTypeFactory().constructCollectionType(List.class, StoreEquipmentDTO.class));
+ }catch (Exception e){
+ log.error("解析获取data失败,url:{},responseBody:{}",huoMaTokenUrl, responseBody);
+ }
+ return Collections.emptyList();
+ }
+
+
+
+ public Integer getStoreIdByStoreNum(String storeNum, String token) {
+ String houMaStoreId = redisUtilPool.hashGet(RedisConstant.HUO_MA_STORE_ID, storeNum);
+ if (houMaStoreId != null) {
+ try {
+ return Integer.valueOf(houMaStoreId);
+ } catch (NumberFormatException e) {
+ // 如果缓存中的数据格式不正确,继续执行正常逻辑
+ log.warn("Redis缓存中的门店ID格式不正确,storeNum: {}", storeNum);
+ }
+ }
+ StoreRequestDTO requestBody = new StoreRequestDTO("point_report", 0, 10, storeNum);
+ String responseBody = sendPostRequestByToken(JSONObject.toJSONString(requestBody), huoMaGetStoreIdUrl,token);
+ try{
+ Integer storeId = extractIdsFromResponse(responseBody);
+ redisUtilPool.hashSet(RedisConstant.HUO_MA_STORE_ID, storeNum, storeId.toString());
+ return storeId;
+ }catch (Exception e){
+ log.error("解析获取data失败,url:{},responseBody:{}",huoMaTokenUrl, responseBody);
+ }
+ return null;
+ }
+
+
+ public List getStoreXinFaDeviceDetail(String storeNum) {
+ String source = redisUtilPool.hashGet(RedisConstant.HUOMA_STORE_DEVICE_RESOURCE_KEY, storeNum);
+ if (StringUtils.isNotBlank(source)) {
+ HuoMaAccountDTO huoMaAccountDTO = accountMap.get(source);
+ if (Objects.nonNull(huoMaAccountDTO)) {
+ String token = getStoreToken(huoMaAccountDTO.getAccount(), huoMaAccountDTO.getPassword());
+ List deviceDetailDetail = getStoreXinFaDeviceDetailDetail(storeNum, token);
+ if (CollectionUtils.isNotEmpty(deviceDetailDetail)) {
+ return deviceDetailDetail;
+ }
+ }
+ }
+ for (Map.Entry entry : accountMap.entrySet()) {
+ HuoMaAccountDTO huoMaAccountDTO = entry.getValue();
+ if (!huoMaAccountDTO.getIsQuery()) {
+ String token = getStoreToken(huoMaAccountDTO.getAccount(), huoMaAccountDTO.getPassword());
+ List deviceDetailDetail = getStoreXinFaDeviceDetailDetail(storeNum, token);
+ if (CollectionUtils.isNotEmpty(deviceDetailDetail)) {
+ redisUtilPool.hashSet(RedisConstant.HUOMA_STORE_DEVICE_RESOURCE_KEY, storeNum, entry.getKey(), Constants.REFRESH_TOKEN_EXPIRE);
+ return deviceDetailDetail;
+ }
+ }
+ }
+ return Collections.emptyList();
+ }
+
+
+ public List getStoreXinFaDeviceDetailDetail(String storeNum, String token) {
+ Integer storeIdByStoreNum = getStoreIdByStoreNum(storeNum, token);
+ return getStoreXinFaDeviceDetailByPointId(storeIdByStoreNum, token);
+ }
+
+
+ public List getAccountAllTags(String storeNum){
+ String source = redisUtilPool.hashGet(RedisConstant.HUOMA_STORE_DEVICE_RESOURCE_KEY, storeNum);
+ //获取标签 必须要知道门店属于哪个账号下 获取对应账号下的标签 如果获取不到 直接返回空
+ if (StringUtils.isEmpty(source)){
+ return new ArrayList<>();
+ }
+ HuoMaAccountDTO huoMaAccountDTO = accountMap.get(source);
+ if (Objects.nonNull(huoMaAccountDTO)){
+ huoMaAccountDTO.setIsQuery(true);
+ String token = getStoreToken(huoMaAccountDTO.getAccount(), huoMaAccountDTO.getPassword());
+ return getAccountAllTags(token);
+ }
+
+
+ }
+
+
+ public List getStoreXinFaDeviceDetailByPointId(Integer pointId, String token) {
+ if (pointId != null){
+ StoreXinFaDetailRequestDTO storeXinFaDetailRequestDTO = new StoreXinFaDetailRequestDTO(0, 10, pointId);
+ String responseBody = null;
+ try{
+ responseBody = sendPostRequestByToken(JSONObject.toJSONString(storeXinFaDetailRequestDTO), huoMaGetStoreXinFaDeviceDetailUrl,token);
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode rootNode = mapper.readTree(responseBody);
+
+ // 直接转换整个数组
+ return mapper.convertValue(
+ rootNode.path("data").path("content"),
+ mapper.getTypeFactory().constructCollectionType(List.class, StoreXinFaDeviceDetail.class)
+ );
+ }catch (Exception e){
+ log.error("getStoreXinFaDeviceDetailByPointId解析获取data失败,url:{},responseBody:{}",huoMaTokenUrl, responseBody);
+ }
+ return null;
+ }
+ return null;
+ }
+
+ private Integer extractIdsFromResponse(String jsonResponse) throws IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode rootNode = mapper.readTree(jsonResponse);
+ // 遍历dataList数组提取ID
+ JsonNode dataList = rootNode.path("data").path("dataList");
+ for (JsonNode item : dataList) {
+ if (item.has("ID")) {
+ //取出第一个id
+ return item.get("ID").asInt();
+ }
+ }
+ return null;
+ }
+
+ private String sendPostRequest(String requestBody, String requestUrl) {
+ log.info("开始发送请求,url:{},requestBody:{}", requestUrl, requestBody);
+
+ int maxRetries = 3;
+ for (int i = 0; i < maxRetries; i++) {
+ try {
+ Request request = new Request.Builder()
+ .url(requestUrl)
+ .post(RequestBody.create(MediaType.parse("application/json"), requestBody))
+ .build();
+
+ return sendPost(requestUrl, request);
+ } catch (Exception e) {
+ log.warn("请求失败,第{}次重试,错误: {}", i + 1, e.getMessage());
+ if (i == maxRetries - 1) {
+ throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR, "请求重试失败: " + e.getMessage());
+ }
+ // 重试前等待
+ try {
+ Thread.sleep(1000 * (i + 1));
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR, "重试被中断");
+ }
+ }
+ }
+ throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR, "请求重试失败");
+ }
+
+ private String sendPostRequestByToken(String requestBody, String requestUrl, String token) {
+ log.info("开始发送请求,url:{},requestBody:{}", requestUrl, requestBody);
+
+ int maxRetries = 3;
+ for (int i = 0; i < maxRetries; i++) {
+ try {
+ Request request = new Request.Builder()
+ .url(requestUrl)
+ .post(RequestBody.create(MediaType.parse("application/json"), requestBody))
+ .addHeader("token", token)
+ .build();
+
+ String result = sendPost(requestUrl, request);
+
+ // 检查是否token失效
+ if (isTokenExpired(result)) {
+ log.warn("Token已失效,正在清除缓存并重试,第{}次", i + 1);
+ // 直接清除对应账号的token缓存
+ token = clearAccountTokenCacheForToken(token);
+
+ // 抛出异常触发重试
+ throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR, "Token失效,需要重试");
+ }
+
+ return result;
+ } catch (ServiceException e) {
+ log.warn("请求失败,第{}次重试,错误: {}", i + 1, e.getMessage());
+ if (i == maxRetries - 1) {
+ throw e;
+ }
+ try {
+ Thread.sleep(1000 * (i + 1));
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR, "重试被中断");
+ }
+ } catch (Exception e) {
+ log.warn("请求异常,第{}次重试,错误: {}", i + 1, e.getMessage());
+ if (i == maxRetries - 1) {
+ throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR, "请求重试失败: " + e.getMessage());
+ }
+ try {
+ Thread.sleep(1000 * (20*i + 1));
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR, "重试被中断");
+ }
+ }
+ }
+ throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR, "请求重试失败");
+ }
+
+ private boolean isTokenExpired(String responseBody) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode rootNode = mapper.readTree(responseBody);
+ int code = rootNode.path("code").asInt(-1);
+ String msg = rootNode.path("msg").asText("");
+
+ return code == 501;
+ } catch (Exception e) {
+ log.error("检查token失效状态时发生错误: {}", e.getMessage());
+ return false;
+ }
+ }
+
+ private String clearAccountTokenCacheForToken(String token) {
+ try {
+ // 遍历可能的账号,找到对应的缓存并删除
+ String[] accounts = {
+ huoMaDirectStoresAccount,
+ huoMaFranchiseStoresAccount,
+ huoMaRestaurantStoresAccount
+ };
+
+ for (String account : accounts) {
+ if (account != null) {
+ String key = MessageFormat.format(RedisConstant.HUO_MA_TOKEN, account);
+ String cachedToken = redisUtilPool.getString(key);
+ if (token.equals(cachedToken)) {
+ redisUtilPool.delKey(key);
+ log.info("已清除账号 {} 的token缓存", account);
+
+ // 根据账号类型获取对应的密码并重新获取token
+ String password = getPasswordForAccount(account);
+ if (password != null) {
+ String newToken = getStoreToken(account, password);
+ if (newToken != null) {
+ log.info("已为账号 {} 重新获取新的token", account);
+ return newToken;
+ }
+ }
+ break;
+ }
+ }
+ }
+ } catch (Exception e) {
+ log.error("清除token缓存或重新获取token失败: {}", e.getMessage());
+ }
+ return null;
+ }
+
+
+ private String getPasswordForAccount(String account) {
+ if (account.equals(huoMaDirectStoresAccount)) {
+ return huoMaDirectStoresPassword;
+ } else if (account.equals(huoMaFranchiseStoresAccount)) {
+ return huoMaFranchiseStoresPassword;
+ } else if (account.equals(huoMaRestaurantStoresAccount)) {
+ return huoMaRestaurantStoresPassword;
+ }
+ return null;
+ }
+
+ @NotNull
+ private static String sendPost(String requestUrl, Request request) {
+ try (Response response = new OkHttpClient().newCall(request).execute()) {
+ log.info("发起请求 time:{}", System.currentTimeMillis());
+ if (!response.isSuccessful()) {
+ log.info("HTTP请求失败,msg: " + response.message());
+ throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR,
+ "HTTP请求失败,状态码: " + response.code());
+ }
+ String responseBody = response.body().string();
+ log.info("请求成功responseBody:{}", JSONObject.toJSONString(responseBody));
+ return responseBody;
+ } catch (ServiceException e) {
+ throw e;
+ } catch (Exception e) {
+ log.error("API调用异常 - URL: {}, 错误: {}", requestUrl, e.getMessage(), e);
+ throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR, "接口调用异常: " + e.getMessage());
+ }
+ }
+
+}
diff --git a/coolstore-partner-service/src/main/java/com/cool/store/utils/poi/constant/Constants.java b/coolstore-partner-service/src/main/java/com/cool/store/utils/poi/constant/Constants.java
index b08d231e6..7c5cdc771 100644
--- a/coolstore-partner-service/src/main/java/com/cool/store/utils/poi/constant/Constants.java
+++ b/coolstore-partner-service/src/main/java/com/cool/store/utils/poi/constant/Constants.java
@@ -220,4 +220,6 @@ public class Constants
public static final String WANG_LEI_JOB_NUMBER = "19060164";
+ public static final int REFRESH_TOKEN_EXPIRE = 60*60*24*30;
+
}
diff --git a/coolstore-partner-web/src/main/java/com/cool/store/controller/webb/PCTestController.java b/coolstore-partner-web/src/main/java/com/cool/store/controller/webb/PCTestController.java
index a21f93dc2..d65e62b9e 100644
--- a/coolstore-partner-web/src/main/java/com/cool/store/controller/webb/PCTestController.java
+++ b/coolstore-partner-web/src/main/java/com/cool/store/controller/webb/PCTestController.java
@@ -8,6 +8,7 @@ import com.cool.store.dto.FoodTokenDTO;
import com.cool.store.dto.GetAccessTokenDTO;
import com.cool.store.dto.HqtTokenDTO;
import com.cool.store.dto.ModifyPasswordDTO;
+import com.cool.store.dto.huoma.StoreXinFaDeviceDetail;
import com.cool.store.dto.wechat.CallbackMessageDTO;
import com.cool.store.dto.wechat.WechatTemplateMessageDTO;
import com.cool.store.entity.*;
@@ -41,6 +42,7 @@ import com.cool.store.service.impl.CommonService;
import com.cool.store.service.impl.OrderSysInfoServiceImpl;
import com.cool.store.service.impl.UserAuthMappingServiceImpl;
import com.cool.store.service.wechat.WechatTemplateService;
+import com.cool.store.service.xinfa.XinFaDeviceService;
import com.cool.store.utils.CoolDateUtils;
import com.cool.store.utils.RedisConstantUtil;
import com.cool.store.utils.RedisUtilPool;
@@ -605,5 +607,13 @@ public class PCTestController {
return ApiResponse.success(true);
}
+ @Resource
+ XinFaDeviceService xinFaDeviceService;
+ @ApiOperation("测试门店设备信息")
+ @GetMapping("/getStoreXinFaDeviceDetail")
+ public ResponseResult> getStoreXinFaDeviceDetail(@RequestParam("shopId")String storeNum) {
+ return ResponseResult.success(xinFaDeviceService.getStoreXinFaDeviceDetail(storeNum));
+ }
+
}
diff --git a/coolstore-partner-web/src/main/resources/application-ab.properties b/coolstore-partner-web/src/main/resources/application-ab.properties
index 3733c2acc..c230187e3 100644
--- a/coolstore-partner-web/src/main/resources/application-ab.properties
+++ b/coolstore-partner-web/src/main/resources/application-ab.properties
@@ -141,4 +141,15 @@ hqt.token.client.secret=rYe9Cwug5LwQNIBJAiW0a7weF9CAhYCD
wechat.mp.appId=wx4a18ef8bb41aa55c
wechat.mp.appSecret=793904b58f4ecdead3bbe4312c5f5c45
#xiaochengxu appid
-wechat.miniapp.appId=wxd77a2761c1911ee1
\ No newline at end of file
+wechat.miniapp.appId=wxd77a2761c1911ee1
+
+huoMa.token.url = https://www.huoMayunping.com/api/SAASLogin/merchant
+huoMa.id.url = https://www.huomayunping.com/api/reportCenter/executeSql
+huoMa.store.device.detail.url = https://www.huomayunping.com/api/terminal/search
+huoMa.get.point.terminal.url = https://www.huoMayunping.com/api/terminal/getPointTerminalInfos
+huoMa.direct.stores.account = 15370309163
+huoMa.direct.stores.password = Zx@123456.
+huoMa.franchise.stores.account = 13563273279
+huoMa.franchise.stores.password = Zx@123456.
+huoMa.restaurant.stores.account = 18656552865
+huoMa.restaurant.stores.password = ZX123456
diff --git a/coolstore-partner-web/src/main/resources/application-test.properties b/coolstore-partner-web/src/main/resources/application-test.properties
index 079a324c6..7d2261003 100644
--- a/coolstore-partner-web/src/main/resources/application-test.properties
+++ b/coolstore-partner-web/src/main/resources/application-test.properties
@@ -154,3 +154,16 @@ wechat.miniapp.appId=wxd77a2761c1911ee1
zx.iot.appId=p-ts3PhNyf
zx.iot.appSecret=X4cVmfxM+
+
+
+huoMa.token.url = https://www.huoMayunping.com/api/SAASLogin/merchant
+huoMa.id.url = https://www.huomayunping.com/api/reportCenter/executeSql
+huoMa.store.device.detail.url = https://www.huomayunping.com/api/terminal/search
+huoMa.get.point.terminal.url = https://www.huoMayunping.com/api/terminal/getPointTerminalInfos
+huoMa.get.tag.url = https://www.huomayunping.com/api/tag/search
+huoMa.direct.stores.account = 15370309163
+huoMa.direct.stores.password = Zx@123456.
+huoMa.franchise.stores.account = 13563273279
+huoMa.franchise.stores.password = Zx@123456.
+huoMa.restaurant.stores.account = 18656552865
+huoMa.restaurant.stores.password = ZX123456