Merge branch 'cc_20251104_xinfa' into 'master'

Cc 20251104 xinfa

See merge request hangzhou/java/custom_zxjp!184
This commit is contained in:
zhangchenbiao
2025-11-07 06:48:28 +00:00
23 changed files with 1271 additions and 2 deletions

View File

@@ -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}";
}

View File

@@ -0,0 +1,58 @@
package com.cool.store.enums;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author suzhuhong
* @Date 2025/11/5 16:03
* @Version 1.0
*/
public enum SpecialTagEnum {
ELECTRONIC_PRICE_LIST("电子价目牌"),
ACTIVITY_CAROUSEL("活动轮播"),
ACTIVITY_PACKAGE("活动套餐"),
PROMOTIONAL_VIDEO("宣传视频");
private final String tagName;
SpecialTagEnum(String tagName) {
this.tagName = tagName;
}
public String getTagName() {
return tagName;
}
/**
* 根据标签名称获取枚举值
*/
public static SpecialTagEnum fromTagName(String tagName) {
for (SpecialTagEnum tag : values()) {
if (tag.getTagName().equals(tagName)) {
return tag;
}
}
return null;
}
/**
* 获取所有标签名称列表
*/
public static List<String> getAllTagNames() {
return Arrays.stream(values())
.map(SpecialTagEnum::getTagName)
.collect(Collectors.toList());
}
/**
* 获取电子价目牌标签名称
* @return
*/
public static List<String> getElectronicPriceTagName() {
return Arrays.asList(ELECTRONIC_PRICE_LIST.getTagName());
}
}

View File

@@ -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-是旧版Chromefalse-不是旧版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;
}
}

View File

@@ -0,0 +1,20 @@
package com.cool.store.dto.huoma;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @Author suzhuhong
* @Date 2025/11/6 11:09
* @Version 1.0
*/
@Data
public class AccountTagDTO {
@ApiModelProperty("门店编码")
private String storeNum;
@ApiModelProperty("设备名称")
private String deviceName;
}

View File

@@ -0,0 +1,39 @@
package com.cool.store.dto.huoma;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <p>
* 火码账号DTO
* </p>
*
* @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;
}
}

View File

@@ -0,0 +1,40 @@
package com.cool.store.dto.huoma;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import java.util.List;
/**
* @Author suzhuhong
* @Date 2025/11/5 14:43
* @Version 1.0
*/
@Data
public class ProgramReqDTO {
@ApiModelProperty(name = "门店编码",required = true)
private String storeCode;
@ApiModelProperty(name = "设备名称",required = true)
@NotEmpty(message = "设备名称不能为空")
private String deviceName;
@ApiModelProperty(name = "第几页,",required = true)
private Integer index;
@ApiModelProperty(name ="每页数量",required = true)
private Integer size;
@ApiModelProperty(name = "时间",hidden = true)
private String date;
@ApiModelProperty(name = "排序",hidden = true)
private String sort;
@ApiModelProperty("标签id列表")
private List<Integer> tagIds;
}

View File

@@ -0,0 +1,25 @@
package com.cool.store.dto.huoma;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @Author suzhuhong
* @Date 2025/11/5 14:44
* @Version 1.0
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ProgramResponseDTO {
@ApiModelProperty("节目ID")
private Long id;
@ApiModelProperty("节目名称")
private String name;
@ApiModelProperty("节目缩略图")
private String thumbnail;
}

View File

@@ -0,0 +1,27 @@
package com.cool.store.dto.huoma;
import com.alibaba.fastjson.annotation.JSONField;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import java.util.List;
/**
* @Author suzhuhong
* @Date 2025/11/5 17:06
* @Version 1.0
*/
@Data
public class PublishDTO {
@ApiModelProperty(name = "门店编号",required = true)
@NotEmpty(message = "门店编码不能为空")
private String storeCode;
@ApiModelProperty(name = "设备ID列表",required = true)
@NotEmpty(message = "设备ID列表不能为空")
@JSONField(name = "terminals")
private List<String> deviceIdList;
@ApiModelProperty("节目id")
private Long programId;
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,81 @@
package com.cool.store.dto.huoma;
import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
import com.cool.store.utils.BrowserVersionUtils;
import com.cool.store.utils.StringUtil;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.lang.reflect.Type;
/**
* @Author suzhuhong
* @Date 2025/8/18 16:27
* @Version 1.0
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class StoreXinFaDeviceDetail {
/**
* 设备ID
*/
@ApiModelProperty(value = "设备ID")
private String id;
@ApiModelProperty("设备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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,22 @@
package com.cool.store.dto.huoma;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @Author suzhuhong
* @Date 2025/11/5 9:41
* @Version 1.0
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class TagDetailDTO {
@ApiModelProperty("标签Id")
private Integer id;
@ApiModelProperty("标签名称")
private String name;
}

View File

@@ -0,0 +1,67 @@
package com.cool.store.service.impl.xinfa;
import com.cool.store.dto.huoma.*;
import com.cool.store.enums.SpecialTagEnum;
import com.cool.store.response.ResponseResult;
import com.cool.store.service.xinfa.XinFaBusinessService;
import com.cool.store.service.xinfa.XinFaDeviceService;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author suzhuhong
* @Date 2025/11/5 16:17
* @Version 1.0
*/
@Service
public class XinFaBusinessServiceImpl implements XinFaBusinessService {
@Resource
XinFaDeviceService xinFaDeviceService;
@Override
public List<StoreXinFaDeviceDetail> getStoreXinFaDeviceDetail(String storeNum) {
return xinFaDeviceService.getStoreXinFaDeviceDetail(storeNum);
}
@Override
public List<TagDetailDTO> getAccountAllTags(String storeNum,String deviceName) {
//如果是广告机,不需要展示标签
if (deviceName.contains("广告机")){
return new ArrayList<>();
}
List<TagDetailDTO> accountAllTags = xinFaDeviceService.getAccountAllTags(storeNum);
if (accountAllTags != null){
return accountAllTags.stream()
.filter(tag -> tag.getName() != null &&
SpecialTagEnum.getAllTagNames().contains(tag.getName()))
.sorted(Comparator.comparing(tag -> SpecialTagEnum.getAllTagNames().indexOf(tag.getName())))
.collect(Collectors.toList());
}
return null;
}
@Override
public List<ProgramResponseDTO> getProgramList(ProgramReqDTO programReqDTO) {
//如果没传tag 查所有 根据更新时间倒序排
programReqDTO.setSort("desc");
programReqDTO.setDate("updateTime");
if (CollectionUtils.isEmpty(programReqDTO.getTagIds())){
//设备名称包含 广告机 只查询电子价目表
programReqDTO.setTagIds(xinFaDeviceService.getAccountSpecialTagIds(programReqDTO.getStoreCode(), SpecialTagEnum.getElectronicPriceTagName()));
}
List<ProgramResponseDTO> programList = xinFaDeviceService.getProgramList(programReqDTO);
return programList;
}
@Override
public Boolean publishProgram(PublishDTO publishDTO) {
return xinFaDeviceService.publish(publishDTO);
}
}

View File

@@ -0,0 +1,48 @@
package com.cool.store.service.xinfa;
import com.cool.store.dto.huoma.*;
import com.cool.store.response.ResponseResult;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* @Author suzhuhong
* @Date 2025/11/5 16:15
* @Version 1.0
*/
public interface XinFaBusinessService {
/**
* 获取门店信发设备列表
* @param storeNum
* @return
*/
List<StoreXinFaDeviceDetail> getStoreXinFaDeviceDetail(String storeNum);
/**
* 获取账号下有哪些标签
* @param storeNum
* @return
*/
List<TagDetailDTO> getAccountAllTags(String storeNum,String deviceName);
/**
* 获取账号下有哪些节目、
* 通过门店在哪个账号下 确定账号
* @param programReqDTO
* @return
*/
List<ProgramResponseDTO> getProgramList(ProgramReqDTO programReqDTO);
/**
* 发布信发设备
* @param publishDTO
* @return
*/
Boolean publishProgram(PublishDTO publishDTO);
}

View File

@@ -0,0 +1,491 @@
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.enums.SpecialTagEnum;
import com.cool.store.exception.ServiceException;
import com.cool.store.response.ResponseResult;
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 org.springframework.web.bind.annotation.RequestParam;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;
/**
* @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;
@Value("${huoMa.get.program.url}")
private String huoMaGetProgramUrl;
@Value("${huoMa.get.publish.url}")
private String huoMaGetPublishUrl;
private final Map<String, HuoMaAccountDTO> 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<String, String> 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<StoreEquipmentDTO> getStoreEquipmentDataByStoreNumList(List<String> storeNumList, String token) {
Map<String,List<String>> 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<StoreXinFaDeviceDetail> 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<StoreXinFaDeviceDetail> deviceDetailDetail = getStoreXinFaDeviceDetailDetail(storeNum, token);
if (CollectionUtils.isNotEmpty(deviceDetailDetail)) {
return deviceDetailDetail;
}
}
}
for (Map.Entry<String, HuoMaAccountDTO> entry : accountMap.entrySet()) {
HuoMaAccountDTO huoMaAccountDTO = entry.getValue();
if (!huoMaAccountDTO.getIsQuery()) {
String token = getStoreToken(huoMaAccountDTO.getAccount(), huoMaAccountDTO.getPassword());
List<StoreXinFaDeviceDetail> 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<StoreXinFaDeviceDetail> getStoreXinFaDeviceDetailDetail(String storeNum, String token) {
Integer storeIdByStoreNum = getStoreIdByStoreNum(storeNum, token);
return getStoreXinFaDeviceDetailByPointId(storeIdByStoreNum, token);
}
public List<TagDetailDTO> getAccountAllTags(String storeNum){
String source = redisUtilPool.hashGet(RedisConstant.HUOMA_STORE_DEVICE_RESOURCE_KEY, storeNum);
//获取标签 必须要知道门店属于哪个账号下 获取对应账号下的标签 如果获取不到 直接返回空
if (StringUtils.isEmpty(source)){
log.info("门店没有找到对应的账号,storeNum: {}", storeNum);
return new ArrayList<>();
}
HuoMaAccountDTO huoMaAccountDTO = accountMap.get(source);
if (Objects.nonNull(huoMaAccountDTO)){
huoMaAccountDTO.setIsQuery(true);
String token = getStoreToken(huoMaAccountDTO.getAccount(), huoMaAccountDTO.getPassword());
TagDTO tagDTO = new TagDTO("DEFAULT", 0, 30,"program");
String responseBody = null;
try{
responseBody = sendPostRequestByToken(JSONObject.toJSONString(tagDTO), huoMaGetTagUrl,token);
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(responseBody);
// 直接转换整个数组
return mapper.convertValue(
rootNode.path("data").path("content"),
mapper.getTypeFactory().constructCollectionType(List.class, TagDetailDTO.class)
);
}catch (Exception e){
log.error("getAccountAllTags解析获取data失败,url:{},responseBody:{}",huoMaTokenUrl, responseBody);
}
}
return new ArrayList<>();
}
/**
* 获取账号下指定标签的id 只需要一下四个
* @param storeNum
* @return
*/
public List<Integer> getAccountSpecialTagIds( String storeNum,List<String> tagList) {
List<TagDetailDTO> accountAllTags = getAccountAllTags(storeNum);
if (accountAllTags != null) {
return accountAllTags.stream()
.filter(tag -> tag.getName() != null &&
tagList.contains(tag.getName()))
.map(TagDetailDTO::getId)
.collect(Collectors.toList());
}
return new ArrayList<>();
}
public List<ProgramResponseDTO> getProgramList(ProgramReqDTO programReqDTO) {
if (StringUtils.isEmpty(programReqDTO.getStoreCode())){
log.info("门店没有找到对应的账号,storeNum: {}", programReqDTO.getStoreCode());
return new ArrayList<>();
}
//先查询门店是属于哪个账号下
String source = redisUtilPool.hashGet(RedisConstant.HUOMA_STORE_DEVICE_RESOURCE_KEY, programReqDTO.getStoreCode());
//获取标签 必须要知道门店属于哪个账号下 获取对应账号下的标签 如果获取不到 直接返回空
if (StringUtils.isEmpty(source)){
log.info("门店没有找到对应的账号,storeNum: {}", programReqDTO.getStoreCode());
return new ArrayList<>();
}
HuoMaAccountDTO huoMaAccountDTO = accountMap.get(source);
if (huoMaAccountDTO!=null){
String token = getStoreToken(huoMaAccountDTO.getAccount(), huoMaAccountDTO.getPassword());
String responseBody = null;
try{
responseBody = sendPostRequestByToken(JSONObject.toJSONString(programReqDTO), huoMaGetProgramUrl,token);
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(responseBody);
// 直接转换整个数组
return mapper.convertValue(
rootNode.path("data").path("content"),
mapper.getTypeFactory().constructCollectionType(List.class, ProgramResponseDTO.class)
);
}catch (Exception e){
log.error("getProgramList 解析获取data失败,url:{},responseBody:{}",huoMaTokenUrl, responseBody);
}
}
return new ArrayList<>();
}
public Boolean publish(PublishDTO publishDTO){
if (StringUtils.isEmpty(publishDTO.getStoreCode())){
log.info("门店没有找到对应的账号,发布失败,storeNum: {}", publishDTO.getStoreCode());
return Boolean.FALSE;
}
String source = redisUtilPool.hashGet(RedisConstant.HUOMA_STORE_DEVICE_RESOURCE_KEY, publishDTO.getStoreCode());
//获取标签 必须要知道门店属于哪个账号下 获取对应账号下的标签 如果获取不到 直接返回空
if (StringUtils.isEmpty(source)){
log.info("门店没有找到对应的账号,storeNum: {}", publishDTO.getStoreCode());
return Boolean.FALSE;
}
HuoMaAccountDTO huoMaAccountDTO = accountMap.get(source);
if (huoMaAccountDTO!=null){
String token = getStoreToken(huoMaAccountDTO.getAccount(), huoMaAccountDTO.getPassword());
String responseBody = null;
try{
responseBody = sendPostRequestByToken(JSONObject.toJSONString(publishDTO), huoMaGetPublishUrl,token);
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(responseBody);
int code = rootNode.get("code").asInt();
if (code != 0) {
throw new RuntimeException("发布失败");
}
// 直接转换整个数组
log.info("发布成功 deviceId:{},storeCode:{}",JSONObject.toJSONString(publishDTO.getDeviceIdList()), publishDTO.getStoreCode() );
return Boolean.TRUE;
}catch (Exception e){
log.error("发布失败, url:{}, responseBody:{}", huoMaTokenUrl, responseBody);
}
}
return Boolean.FALSE;
}
public List<StoreXinFaDeviceDetail> 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());
}
}
}

View File

@@ -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;
}

View File

@@ -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.*;
import com.cool.store.dto.wechat.CallbackMessageDTO;
import com.cool.store.dto.wechat.WechatTemplateMessageDTO;
import com.cool.store.entity.*;
@@ -41,6 +42,8 @@ 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.XinFaBusinessService;
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 +608,34 @@ public class PCTestController {
return ApiResponse.success(true);
}
@Resource
XinFaBusinessService xinFaBusinessService;
@ApiOperation("测试门店设备信息")
@GetMapping("/getStoreXinFaDeviceDetail")
public ResponseResult<List<StoreXinFaDeviceDetail>> getStoreXinFaDeviceDetail(@RequestParam("storeNum")String storeNum) {
return ResponseResult.success(xinFaBusinessService.getStoreXinFaDeviceDetail(storeNum));
}
@ApiOperation("测试标签信息")
@GetMapping("/getAccountAllTags")
public ResponseResult<List<TagDetailDTO>> getAccountAllTags(@RequestParam("storeNum")String storeNum,
@RequestParam("deviceName")String deviceName) {
List<TagDetailDTO> accountAllTags = xinFaBusinessService.getAccountAllTags(storeNum,deviceName);
return ResponseResult.success(accountAllTags);
}
@ApiOperation("获取节目列表")
@PostMapping("/getProgramList")
public ResponseResult<List<ProgramResponseDTO>> getProgramList(@RequestBody ProgramReqDTO programReqDTO) {
List<ProgramResponseDTO> accountAllTags = xinFaBusinessService.getProgramList(programReqDTO);
return ResponseResult.success(accountAllTags);
}
@ApiOperation("发布/上架")
@PostMapping("/publishProgram")
public ResponseResult<Boolean> publishProgram(@RequestBody PublishDTO publishDTO) {
Boolean publishStatus = xinFaBusinessService.publishProgram(publishDTO);
return ResponseResult.success(publishStatus);
}
}

View File

@@ -0,0 +1,54 @@
package com.cool.store.controller.webc;
import com.cool.store.dto.huoma.*;
import com.cool.store.response.ResponseResult;
import com.cool.store.service.xinfa.XinFaBusinessService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* @Author suzhuhong
* @Date 2025/11/5 17:49
* @Version 1.0
*/
@RestController
@RequestMapping("/mini/xinfa")
@Api(tags = "小程序-信发")
@Slf4j
public class MiniXinFaController {
@Resource
XinFaBusinessService xinFaBusinessService;
@ApiOperation("测试门店设备信息")
@GetMapping("/getStoreXinFaDeviceDetail")
public ResponseResult<List<StoreXinFaDeviceDetail>> getStoreXinFaDeviceDetail(@RequestParam("storeNum")String storeNum) {
return ResponseResult.success(xinFaBusinessService.getStoreXinFaDeviceDetail(storeNum));
}
@ApiOperation("测试标签信息")
@PostMapping("/getAccountAllTags")
public ResponseResult<List<TagDetailDTO>> getAccountAllTags(@RequestBody AccountTagDTO tagDTO) {
List<TagDetailDTO> accountAllTags = xinFaBusinessService.getAccountAllTags(tagDTO.getStoreNum(), tagDTO.getDeviceName());
return ResponseResult.success(accountAllTags);
}
@ApiOperation("获取节目列表")
@PostMapping("/getProgramList")
public ResponseResult<List<ProgramResponseDTO>> getProgramList(@RequestBody ProgramReqDTO programReqDTO) {
List<ProgramResponseDTO> accountAllTags = xinFaBusinessService.getProgramList(programReqDTO);
return ResponseResult.success(accountAllTags);
}
@ApiOperation("发布/上架")
@PostMapping("/publishProgram")
public ResponseResult<Boolean> publishProgram(@RequestBody PublishDTO publishDTO) {
Boolean publishStatus = xinFaBusinessService.publishProgram(publishDTO);
return ResponseResult.success(publishStatus);
}
}

View File

@@ -141,4 +141,18 @@ hqt.token.client.secret=rYe9Cwug5LwQNIBJAiW0a7weF9CAhYCD
wechat.mp.appId=wx4a18ef8bb41aa55c
wechat.mp.appSecret=793904b58f4ecdead3bbe4312c5f5c45
#xiaochengxu appid
wechat.miniapp.appId=wxd77a2761c1911ee1
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.get.tag.url = https://www.huomayunping.com/api/tag/search
huoMa.get.program.url = https://www.huomayunping.com/api/program/search
huoMa.get.publish.url = https://www.huomayunping.com/api/channelPublish/target/v2/quick-publish
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

View File

@@ -144,4 +144,18 @@ hqt.token.client.secret=rYe9Cwug5LwQNIBJAiW0a7weF9CAhYCD
wechat.mp.appId=wx4a18ef8bb41aa55c
wechat.mp.appSecret=793904b58f4ecdead3bbe4312c5f5c45
#xiaochengxu appid
wechat.miniapp.appId=wxd77a2761c1911ee1
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.get.tag.url = https://www.huomayunping.com/api/tag/search
huoMa.get.program.url = https://www.huomayunping.com/api/program/search
huoMa.get.publish.url = https://www.huomayunping.com/api/channelPublish/target/v2/quick-publish
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

View File

@@ -154,3 +154,18 @@ 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.get.program.url = https://www.huomayunping.com/api/program/search
huoMa.get.publish.url = https://www.huomayunping.com/api/channelPublish/target/v2/quick-publish
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