feat:外部接口接入
This commit is contained in:
@@ -236,7 +236,9 @@ public enum ErrorCodeEnum {
|
||||
UPDATE_INVESTMENT_MANAGER_FAIL(131005,"当前用户已经为该门店招商经理",null),
|
||||
CONFIRM_THE_APPROVER(131006,"您提交的铺位暂时找不到选址审批人,请联系系统管理员配置选址审批权限后再提交铺位审批",null),
|
||||
|
||||
TALLY_BOOK_NOT_EXIST(180001, "记账本数据不存在", null)
|
||||
TALLY_BOOK_NOT_EXIST(180001, "记账本数据不存在", null),
|
||||
|
||||
THIRD_API_ERROR(151001,"第三方服务异常->{0}",null),
|
||||
;
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.*;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
@@ -32,6 +34,13 @@ public class CoolDateUtils {
|
||||
public static final String DATE_FORMAT_SEC_5 = "yyyy.MM.dd HH:mm";
|
||||
public static final String DATE_FORMAT_SEC_6 = "yyyy.MM.dd";
|
||||
public static final String DATE_FORMAT_SEC_7 = "yyyy/MM/dd HH:mm";
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
private static final DateTimeFormatter getDateFormatter(String format){
|
||||
return DateTimeFormatter.ofPattern(format);
|
||||
}
|
||||
|
||||
/**
|
||||
* 几天后的当前
|
||||
* @param d
|
||||
@@ -128,4 +137,26 @@ public class CoolDateUtils {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前日期字符串 (yyyy-MM-dd)
|
||||
*/
|
||||
public static String getCurrentDate() {
|
||||
return LocalDate.now().format(getDateFormatter(DATE_FORMAT_DAY));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上月同一天的日期字符串 (yyyy-MM-dd)
|
||||
* 如果上个月没有同一天(如当前是3月31日,但2月没有31日),则返回上个月的最后一天
|
||||
*/
|
||||
public static String getSameDayLastMonth() {
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDate lastMonthSameDay = today.minusMonths(1);
|
||||
|
||||
// 处理跨月情况(如3月31日,2月没有31日)
|
||||
if (lastMonthSameDay.getDayOfMonth() != today.getDayOfMonth()) {
|
||||
lastMonthSameDay = lastMonthSameDay.with(TemporalAdjusters.lastDayOfMonth());
|
||||
}
|
||||
|
||||
return lastMonthSameDay.format(getDateFormatter(DATE_FORMAT_DAY));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.cool.store.utils;
|
||||
import java.util.BitSet;
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/4/1 20:39
|
||||
* @Version 1.0
|
||||
*/
|
||||
|
||||
public class GeoHashUtils {
|
||||
private static final String BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz";
|
||||
private static final int[] BITS = {16, 8, 4, 2, 1};
|
||||
|
||||
public static String encode(double lat, double lon, int precision) {
|
||||
BitSet latBits = getBits(lat, -90, 90);
|
||||
BitSet lonBits = getBits(lon, -180, 180);
|
||||
|
||||
StringBuilder hash = new StringBuilder();
|
||||
for (int i = 0; i < precision; i++) {
|
||||
int index = 0;
|
||||
for (int j = 0; j < 5; j++) {
|
||||
boolean lonBit = lonBits.get(i * 5 + j);
|
||||
boolean latBit = latBits.get(i * 5 + j);
|
||||
index = (index << 1) | (lonBit ? 1 : 0);
|
||||
index = (index << 1) | (latBit ? 1 : 0);
|
||||
}
|
||||
hash.append(BASE32.charAt(index));
|
||||
}
|
||||
return hash.toString();
|
||||
}
|
||||
|
||||
private static BitSet getBits(double val, double min, double max) {
|
||||
BitSet bits = new BitSet();
|
||||
for (int i = 0; i < 30; i++) {
|
||||
double mid = (min + max) / 2;
|
||||
if (val >= mid) {
|
||||
bits.set(i);
|
||||
min = mid;
|
||||
} else {
|
||||
max = mid;
|
||||
}
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.cool.store.utils;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 19:39
|
||||
* @Version 1.0
|
||||
*/
|
||||
|
||||
public class JsonUtils {
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
public static String toJson(Object obj) {
|
||||
return gson.toJson(obj);
|
||||
}
|
||||
|
||||
public static Map<String, Object> parseJsonToMap(String json) {
|
||||
return gson.fromJson(json, new TypeToken<Map<String, Object>>() {}.getType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.cool.store.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 19:32
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
public class SignatureUtils {
|
||||
|
||||
/**
|
||||
* 生成 HmacSHA256 签名
|
||||
* @param data 待签名字符串
|
||||
* @param secret 密钥
|
||||
* @return 签名(Hex小写)
|
||||
*/
|
||||
public static String hmacSha256(String data, String secret) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
||||
mac.init(secretKey);
|
||||
byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
||||
return bytesToHex(hash).toLowerCase();
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
throw new RuntimeException("HmacSHA256签名失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成待签名字符串(参数按字母排序 + appkey + timestamp)
|
||||
* @param params 请求参数Map(需提前过滤空值)
|
||||
* @param appKey 应用Key
|
||||
* @param timestamp 时间戳(毫秒)
|
||||
*/
|
||||
public static String buildSignString(Map<String, Object> params, String appKey, long timestamp) {
|
||||
TreeMap<String, Object> sortedParams = new TreeMap<>(params);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// 拼接排序后的参数
|
||||
sortedParams.forEach((key, value) ->{
|
||||
if (Objects.isNull(value)||(value instanceof Double &&((Double) value).intValue()==0)){
|
||||
log.info("0或者空值不参与签名");
|
||||
}else {
|
||||
if (value instanceof Double){
|
||||
sb.append(key).append("=").append(((Double) value).intValue()).append("&");
|
||||
}else {
|
||||
sb.append(key).append("=").append(value).append("&");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
sb.append("appkey=").append(appKey).append("×tamp=").append(timestamp);
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.cool.store.request.bigdata;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/27 17:49
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class ProfitDataRequest {
|
||||
|
||||
private String store_code;
|
||||
|
||||
private String bill_date;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.cool.store.request.oppty;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/4/1 20:30
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class BerthOperationRequest {
|
||||
@ApiModelProperty(" 操作类型: 1(新增), 2(更新), 3(删除)")
|
||||
private String opType;
|
||||
@ApiModelProperty("机会点编号")
|
||||
private String code;
|
||||
@ApiModelProperty("关注人用户编号")
|
||||
private String userId;
|
||||
@ApiModelProperty("关注用户手机号")
|
||||
private String mobile;
|
||||
@ApiModelProperty("关注人用户名")
|
||||
private String userName;
|
||||
@ApiModelProperty("铺位ID")
|
||||
private Integer berthId;
|
||||
@ApiModelProperty("铺位名称")
|
||||
private String name;
|
||||
@ApiModelProperty("铺位地址")
|
||||
private String address;
|
||||
@ApiModelProperty("铺位经纬度")
|
||||
private String location;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.cool.store.request.oppty;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/4/1 16:15
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class CityRequest {
|
||||
|
||||
|
||||
@ApiModelProperty("父ID")
|
||||
private Integer pid;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.cool.store.request.oppty;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 18:50
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class FollowRequest {
|
||||
|
||||
private Integer opType;
|
||||
|
||||
private String code;
|
||||
|
||||
private String userId;
|
||||
|
||||
private String mobile;
|
||||
|
||||
private String userName;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.cool.store.request.oppty;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/4/1 20:05
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class InspectionRequest {
|
||||
private String code;
|
||||
|
||||
private String userId;
|
||||
|
||||
private String mobile;
|
||||
|
||||
private String userName;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.cool.store.request.oppty;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 18:47
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class OpportunityBaseRequest {
|
||||
|
||||
@NotNull(message = "notice不能为空")
|
||||
private Integer notice;
|
||||
|
||||
@NotNull(message = "timestamp不能为空")
|
||||
private Long timestamp;
|
||||
|
||||
@NotBlank(message = "key不能为空")
|
||||
private String key;
|
||||
|
||||
private String sign;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.cool.store.request.oppty;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 17:23
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class OpportunityDetailRequest{
|
||||
|
||||
private String code;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.cool.store.request.oppty;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 14:38
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
|
||||
public class OpportunityListRequest{
|
||||
private String latLng;
|
||||
private String code;
|
||||
private Integer province;
|
||||
private Integer city;
|
||||
private Integer district;
|
||||
private String userId;
|
||||
private Integer orderType = 1;
|
||||
private Integer inspectionStatus ;
|
||||
private Integer pageNum = 1;
|
||||
private Integer pageSize = 20;
|
||||
|
||||
|
||||
public String getLatLng() {
|
||||
return latLng;
|
||||
}
|
||||
|
||||
public void setLatLng(String latLng) {
|
||||
this.latLng = latLng;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Integer getProvince() {
|
||||
return province;
|
||||
}
|
||||
|
||||
public void setProvince(Integer province) {
|
||||
this.province = province;
|
||||
}
|
||||
|
||||
public Integer getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public void setCity(Integer city) {
|
||||
this.city = city;
|
||||
}
|
||||
|
||||
public Integer getDistrict() {
|
||||
return district;
|
||||
}
|
||||
|
||||
public void setDistrict(Integer district) {
|
||||
this.district = district;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public Integer getOrderType() {
|
||||
return orderType;
|
||||
}
|
||||
|
||||
public void setOrderType(Integer orderType) {
|
||||
this.orderType = orderType;
|
||||
}
|
||||
|
||||
public Integer getInspectionStatus() {
|
||||
return inspectionStatus;
|
||||
}
|
||||
|
||||
public void setInspectionStatus(Integer inspectionStatus) {
|
||||
this.inspectionStatus = inspectionStatus;
|
||||
}
|
||||
|
||||
public Integer getPageNum() {
|
||||
return pageNum;
|
||||
}
|
||||
|
||||
public void setPageNum(Integer pageNum) {
|
||||
this.pageNum = pageNum;
|
||||
}
|
||||
|
||||
public Integer getPageSize() {
|
||||
return pageSize;
|
||||
}
|
||||
|
||||
public void setPageSize(Integer pageSize) {
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.cool.store.request.oppty;
|
||||
|
||||
import lombok.Data;
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/4/1 20:31
|
||||
* @Version 1.0
|
||||
*/
|
||||
|
||||
|
||||
@Data
|
||||
public class StoreOperationRequest {
|
||||
/**
|
||||
* 操作类型:1-新开门店,2-更新门店,3-闭店
|
||||
*/
|
||||
private String opType;
|
||||
|
||||
/**
|
||||
* 机会点编号
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 门店ID
|
||||
*/
|
||||
private Integer storeId;
|
||||
|
||||
/**
|
||||
* 门店编号
|
||||
*/
|
||||
private String storeSn;
|
||||
|
||||
/**
|
||||
* 门店模式:1-加盟,2-强管
|
||||
*/
|
||||
private Integer storeModel;
|
||||
|
||||
/**
|
||||
* 门店品牌(默认值:正新鸡排)
|
||||
*/
|
||||
private String storeBrand = "正新鸡排";
|
||||
|
||||
/**
|
||||
* 门店名称
|
||||
*/
|
||||
private String storeName;
|
||||
|
||||
/**
|
||||
* 门店地址
|
||||
*/
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 门店经纬度geohash
|
||||
*/
|
||||
private String location;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.cool.store.request.oppty;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 18:53
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class UserRequest {
|
||||
|
||||
private String userId;
|
||||
|
||||
private String mobile;
|
||||
|
||||
private String userName;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.cool.store.response.bigdata;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/4/1 13:42
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class ActDataResponse {
|
||||
|
||||
private String act_total_amt;
|
||||
|
||||
private String Store_code;
|
||||
|
||||
private String act_amt;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.cool.store.response.bigdata;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/4/1 10:49
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class ApiResponse<T> {
|
||||
private String msg;
|
||||
private int code;
|
||||
private T data;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.cool.store.response.bigdata;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/27 17:46
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class ProfitDataResponse {
|
||||
private String store_code;
|
||||
private Double profit_total_amt;
|
||||
private Double profit_amt;
|
||||
private Double profit_rate;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.cool.store.response.bigdata;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/4/1 13:25
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class ProfitRateResponse {
|
||||
private String profit_amt ;
|
||||
private String store_code ;
|
||||
private String profit_rate;
|
||||
private String bill_date;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.cool.store.response.oppty;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 14:33
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class BerthInfoResponse {
|
||||
/**
|
||||
* 铺位ID
|
||||
*/
|
||||
private Integer berthId;
|
||||
/**
|
||||
* 铺位名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 铺位地址
|
||||
*/
|
||||
private String address;
|
||||
/**
|
||||
* 铺位经纬度
|
||||
*/
|
||||
private String geohash;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.cool.store.response.oppty;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 14:29
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class BrandResponse {
|
||||
/**
|
||||
* 品牌编号
|
||||
*/
|
||||
private String brandCode;
|
||||
/**
|
||||
* 品牌名称
|
||||
*/
|
||||
private String brandName;
|
||||
/**
|
||||
* 品牌Logo base64编码
|
||||
*/
|
||||
private String brandLogo;
|
||||
/**
|
||||
* 该品牌门店所在位置
|
||||
*/
|
||||
private String geohash;
|
||||
/**
|
||||
* 品牌数量
|
||||
*/
|
||||
private Integer count;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.cool.store.response.oppty;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/4/1 16:16
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class CityResponse {
|
||||
|
||||
private Integer code;
|
||||
private Integer name;
|
||||
private Integer pid;
|
||||
private String location;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.cool.store.response.oppty;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/4/1 10:49
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class OpportunityApiResponse<T> {
|
||||
private String msg;
|
||||
private int code;
|
||||
private Boolean success;
|
||||
private T data;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.cool.store.response.oppty;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
import org.apache.catalina.User;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 17:24
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class OpportunityDetailResponse {
|
||||
|
||||
private String code; // 机会点编号
|
||||
private Integer level; // 等级
|
||||
private String name; // 名称
|
||||
private String address; // 地址
|
||||
private String geohash; // GeoHash
|
||||
private Integer province; // 省份编码
|
||||
private Integer city; // 城市编码
|
||||
private Integer district; // 区县编码
|
||||
private UserResponse bdUser; // 拓展人信息
|
||||
private List<UserResponse> atUsers; // 关注人列表
|
||||
private List<BrandResponse> brandStats; // 周边品牌
|
||||
private StoreInfoResponse storeInfo; // 门店信息
|
||||
private List<BerthInfoResponse> berthInfo;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.cool.store.response.oppty;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/4/1 18:40
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class OpportunityInfoPageResponse {
|
||||
|
||||
|
||||
private PageResponse page;
|
||||
|
||||
private List<OpportunityInfoResponse> pageData;
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.cool.store.response.oppty;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 14:40
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class OpportunityInfoResponse {
|
||||
private String code;
|
||||
private Integer level;
|
||||
private String name;
|
||||
private String address;
|
||||
private String geohash;
|
||||
private Integer province;
|
||||
private Integer city;
|
||||
private Integer district;
|
||||
private Integer berthNum;
|
||||
private List<UserResponse> atUser;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.cool.store.response.oppty;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 14:27
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class PageResponse {
|
||||
|
||||
private Integer currentPage;
|
||||
private Integer pageSize;
|
||||
private Integer total;
|
||||
private Integer count;
|
||||
private Boolean first;
|
||||
private Boolean last;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.cool.store.response.oppty;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 14:31
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class StoreInfoResponse {
|
||||
/**
|
||||
* 门店ID
|
||||
*/
|
||||
private Integer storeId;
|
||||
/**
|
||||
* 门店编号
|
||||
*/
|
||||
private String storeSn;
|
||||
/**
|
||||
* 门店模式:1:加盟 2:强管
|
||||
*/
|
||||
private Integer storeModel;
|
||||
/**
|
||||
* 门店品牌 预留,不传默认正新鸡排
|
||||
*/
|
||||
private String storeBrand;
|
||||
/**
|
||||
* 门店名称
|
||||
*/
|
||||
private String storeName;
|
||||
/**
|
||||
* 门店地址
|
||||
*/
|
||||
private String address;
|
||||
/**
|
||||
* 门店经纬度
|
||||
*/
|
||||
private String geohash;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.cool.store.response.oppty;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 14:28
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class UserResponse {
|
||||
private String userId;
|
||||
private String mobile;
|
||||
private String userName;
|
||||
private String atTime;
|
||||
|
||||
private String inspectionStatus;
|
||||
|
||||
private String inspectionTime;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.cool.store.config.rest;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/4/1 10:54
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
return mapper;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.cool.store.config.rest;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import javax.net.ssl.*;
|
||||
import java.security.KeyStore;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/4/1 9:56
|
||||
* @Version 1.0
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class OkHttpConfig {
|
||||
|
||||
private static final String TRUSTED_DOMAIN = "zhengxinfood.com";
|
||||
private static final int CONNECT_TIMEOUT = 30;
|
||||
private static final int READ_TIMEOUT = 30;
|
||||
private static final int WRITE_TIMEOUT = 30;
|
||||
|
||||
@Bean
|
||||
public OkHttpClient okHttpClient() {
|
||||
return createCustomTrustOkHttpClient(TRUSTED_DOMAIN);
|
||||
}
|
||||
|
||||
private OkHttpClient createCustomTrustOkHttpClient(String trustedDomain) {
|
||||
try {
|
||||
// 获取系统默认TrustManager
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
||||
TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init((KeyStore) null);
|
||||
TrustManager[] defaultTrustManagers = trustManagerFactory.getTrustManagers();
|
||||
X509TrustManager defaultTrustManager = (X509TrustManager) defaultTrustManagers[0];
|
||||
|
||||
// 创建自定义TrustManager
|
||||
X509TrustManager customTrustManager = new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException {
|
||||
defaultTrustManager.checkClientTrusted(chain, authType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException {
|
||||
try {
|
||||
defaultTrustManager.checkServerTrusted(chain, authType);
|
||||
} catch (CertificateException e) {
|
||||
if (!isCertificateForDomain(chain[0], trustedDomain)) {
|
||||
throw new CertificateException("证书不匹配目标域名: " + trustedDomain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return defaultTrustManager.getAcceptedIssuers();
|
||||
}
|
||||
};
|
||||
|
||||
// 配置SSLContext
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, new TrustManager[]{customTrustManager}, new SecureRandom());
|
||||
|
||||
return new OkHttpClient.Builder()
|
||||
.sslSocketFactory(sslContext.getSocketFactory(), customTrustManager)
|
||||
.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
|
||||
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
|
||||
.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("创建OkHttpClient失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCertificateForDomain(X509Certificate certificate, String domain) {
|
||||
try {
|
||||
// 检查CN (Common Name)
|
||||
String principal = certificate.getSubjectX500Principal().getName();
|
||||
if (principal.contains("CN=" + domain) || principal.contains("CN=*." + domain)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查SAN (Subject Alternative Names)
|
||||
Collection<List<?>> sans = certificate.getSubjectAlternativeNames();
|
||||
if (sans != null) {
|
||||
for (List<?> san : sans) {
|
||||
// 类型2是DNS名称
|
||||
if (san.get(0).equals(2)) {
|
||||
String sanValue = (String) san.get(1);
|
||||
if (sanValue.equals(domain) || sanValue.endsWith("." + domain)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.cool.store.service;
|
||||
|
||||
import com.cool.store.request.bigdata.ProfitDataRequest;
|
||||
import com.cool.store.request.oppty.CityRequest;
|
||||
import com.cool.store.response.bigdata.ActDataResponse;
|
||||
import com.cool.store.response.bigdata.ProfitDataResponse;
|
||||
import com.cool.store.response.bigdata.ProfitRateResponse;
|
||||
import com.cool.store.response.oppty.CityResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/27 17:37
|
||||
* @Version 1.0
|
||||
*/
|
||||
public interface ThirdBigDataService {
|
||||
|
||||
|
||||
/**
|
||||
* 查询昨日毛利
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
ProfitDataResponse getProfitData(ProfitDataRequest request);
|
||||
|
||||
/**
|
||||
* 查询当月毛利
|
||||
* @param requestBody
|
||||
* @return
|
||||
*/
|
||||
ProfitDataResponse getCurMonthProfitData(ProfitDataRequest requestBody);
|
||||
|
||||
/**
|
||||
* 上月毛利
|
||||
* @param requestBody
|
||||
* @return
|
||||
*/
|
||||
ProfitDataResponse getPreMonthProfitData(ProfitDataRequest requestBody);
|
||||
|
||||
|
||||
/**
|
||||
*getMonthProfitTrend
|
||||
* @param requestBody
|
||||
* @return
|
||||
*/
|
||||
List<ProfitRateResponse> getMonthProfitTrend(ProfitDataRequest requestBody);
|
||||
|
||||
/**
|
||||
* 实收数据
|
||||
* @param requestBody
|
||||
* @return
|
||||
*/
|
||||
ActDataResponse getActData(ProfitDataRequest requestBody);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.cool.store.service;
|
||||
|
||||
import com.cool.store.request.oppty.*;
|
||||
import com.cool.store.response.oppty.CityResponse;
|
||||
import com.cool.store.response.oppty.OpportunityDetailResponse;
|
||||
import com.cool.store.response.oppty.OpportunityInfoPageResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 14:50
|
||||
* @Version 1.0
|
||||
* 正新 第三方机会点服务
|
||||
*/
|
||||
public interface ThirdOpportunityService {
|
||||
|
||||
|
||||
/**
|
||||
* 机会点列表 我的机会点列表
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
OpportunityInfoPageResponse listOpportunities(OpportunityListRequest request);
|
||||
|
||||
|
||||
/**
|
||||
* 查询机会点详情
|
||||
* @param opportunityDetailRequest
|
||||
* @return
|
||||
*/
|
||||
OpportunityDetailResponse getOpportunitiesDetail(OpportunityDetailRequest opportunityDetailRequest);
|
||||
|
||||
/**
|
||||
* 关注机会点
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
String followOpportunity(FollowRequest request);
|
||||
|
||||
/**
|
||||
* 考察机会点
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
String inspectionOpportunity(InspectionRequest request);
|
||||
|
||||
/**
|
||||
* 铺位关注机会点
|
||||
* @param requestBody
|
||||
* @return
|
||||
*/
|
||||
String berthOperation(BerthOperationRequest requestBody);
|
||||
|
||||
/**
|
||||
* 门店关注机会点
|
||||
* @param requestBody
|
||||
* @return
|
||||
*/
|
||||
String storeOperation(StoreOperationRequest requestBody);
|
||||
|
||||
/**
|
||||
* 省市区列表
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
List<CityResponse> cityList(CityRequest request);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package com.cool.store.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.cool.store.enums.ErrorCodeEnum;
|
||||
import com.cool.store.exception.ServiceException;
|
||||
import com.cool.store.request.bigdata.ProfitDataRequest;
|
||||
import com.cool.store.request.oppty.CityRequest;
|
||||
import com.cool.store.response.bigdata.ActDataResponse;
|
||||
import com.cool.store.response.bigdata.ApiResponse;
|
||||
import com.cool.store.response.bigdata.ProfitDataResponse;
|
||||
import com.cool.store.response.bigdata.ProfitRateResponse;
|
||||
import com.cool.store.response.oppty.CityResponse;
|
||||
import com.cool.store.response.oppty.OpportunityApiResponse;
|
||||
import com.cool.store.response.oppty.OpportunityDetailResponse;
|
||||
import com.cool.store.service.ThirdBigDataService;
|
||||
import com.cool.store.utils.JsonUtils;
|
||||
import com.cool.store.utils.SignatureUtils;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/27 17:37
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ThirdBigDataServiceImpl implements ThirdBigDataService {
|
||||
|
||||
@Value("${zx.big.data.url}")
|
||||
private String apiUrl;
|
||||
|
||||
@Value("${zx.big.data.appKey}")
|
||||
private String appKey;
|
||||
|
||||
@Value("${zx.big.data.appSecret}")
|
||||
private String appSecret;
|
||||
|
||||
@Resource
|
||||
ObjectMapper objectMapper;
|
||||
|
||||
@Resource
|
||||
OkHttpClient okHttpClient;
|
||||
|
||||
|
||||
@Override
|
||||
public ProfitDataResponse getProfitData(ProfitDataRequest requestBody) {
|
||||
// 1. 发送POST请求
|
||||
String url = apiUrl + "api/web/v1/act/yesd_profit";
|
||||
return executeApiCall(url, requestBody, ProfitDataResponse.class);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ProfitDataResponse getCurMonthProfitData(ProfitDataRequest requestBody) {
|
||||
// 1. 发送POST请求
|
||||
String url = apiUrl + "api/web/v1/act/curr_month_profit";
|
||||
return executeApiCall(url, requestBody, ProfitDataResponse.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProfitDataResponse getPreMonthProfitData(ProfitDataRequest requestBody) {
|
||||
// 1. 发送POST请求
|
||||
String url = apiUrl + "api/web/v1/act/prev_month_profit";
|
||||
return executeApiCall(url, requestBody, ProfitDataResponse.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProfitRateResponse> getMonthProfitTrend(ProfitDataRequest requestBody) {
|
||||
// 1. 发送POST请求
|
||||
String url = apiUrl + "api/web/v1/act/month_profit_trend";
|
||||
return executeApiCall(url, requestBody, List.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActDataResponse getActData(ProfitDataRequest requestBody) {
|
||||
// 1. 发送POST请求
|
||||
String url = apiUrl + "api/web/v1/income/store_act";
|
||||
return executeApiCall(url, requestBody, ActDataResponse.class);
|
||||
}
|
||||
|
||||
|
||||
private <T> T executeApiCall(String url, Object requestBody, Class<T> responseType) {
|
||||
// 1. 打印请求前日志
|
||||
logRequest(url, requestBody);
|
||||
|
||||
try {
|
||||
Request request = buildRequest(requestBody, url);
|
||||
|
||||
try (Response response = okHttpClient.newCall(request).execute()) {
|
||||
// 2. 获取原始响应内容
|
||||
String responseBody = response.body().string();
|
||||
|
||||
// 3. 打印响应日志
|
||||
logResponse(url, response.code(), responseBody);
|
||||
|
||||
if (!response.isSuccessful()) {
|
||||
throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR,
|
||||
"HTTP请求失败,状态码: " + response.code());
|
||||
}
|
||||
|
||||
// 4. 解析响应
|
||||
JavaType javaType = objectMapper.getTypeFactory()
|
||||
.constructParametricType(OpportunityApiResponse.class, responseType);
|
||||
|
||||
OpportunityApiResponse<T> apiResponse = objectMapper.readValue(responseBody, javaType);
|
||||
|
||||
if (apiResponse.getCode() != 0) {
|
||||
throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR,
|
||||
"业务逻辑错误: " + apiResponse.getMsg());
|
||||
}
|
||||
|
||||
return apiResponse.getData();
|
||||
}
|
||||
} catch (ServiceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("API调用异常 - URL: {}, 错误: {}", url, e.getMessage(), e);
|
||||
throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR, "接口调用异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Request buildRequest(Object requestBody, String url) {
|
||||
Map<String, Object> params = JsonUtils.parseJsonToMap(JSONObject.toJSONString(requestBody));
|
||||
long timestamp = System.currentTimeMillis();
|
||||
|
||||
String signString = SignatureUtils.buildSignString(params, appKey, timestamp);
|
||||
String sign = SignatureUtils.hmacSha256(signString, appSecret);
|
||||
|
||||
log.debug("签名生成 - 原始字符串: {}, 签名结果: {}", signString, sign);
|
||||
|
||||
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json"),
|
||||
JSONObject.toJSONString(requestBody)
|
||||
);
|
||||
|
||||
return new Request.Builder()
|
||||
.url(url)
|
||||
.post(body)
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.addHeader("Accept", "application/json")
|
||||
.addHeader("sign", sign)
|
||||
.addHeader("timestamp", String.valueOf(timestamp))
|
||||
.addHeader("appkey", appKey)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录请求日志
|
||||
*/
|
||||
private void logRequest(String url, Object requestBody) {
|
||||
if (log.isInfoEnabled()) {
|
||||
try {
|
||||
log.info("\n======= 请求开始 =======\n" +
|
||||
"API地址: {}\n" +
|
||||
"请求参数: {}\n" +
|
||||
"======= 请求结束 =======",
|
||||
url,
|
||||
objectMapper.writerWithDefaultPrettyPrinter()
|
||||
.writeValueAsString(requestBody));
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("日志JSON序列化失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录响应日志
|
||||
*/
|
||||
private void logResponse(String url, int statusCode, String responseBody) {
|
||||
if (log.isInfoEnabled()) {
|
||||
try {
|
||||
// 尝试美化JSON输出
|
||||
Object json = objectMapper.readValue(responseBody, Object.class);
|
||||
String prettyResponse = objectMapper.writerWithDefaultPrettyPrinter()
|
||||
.writeValueAsString(json);
|
||||
|
||||
log.info("\n======= 响应开始 =======\n" +
|
||||
"API地址: {}\n" +
|
||||
"HTTP状态码: {}\n" +
|
||||
"响应内容: {}\n" +
|
||||
"======= 响应结束 =======",
|
||||
url,
|
||||
statusCode,
|
||||
prettyResponse);
|
||||
} catch (Exception e) {
|
||||
// 非JSON响应或解析失败时直接输出原始内容
|
||||
log.info("\n======= 响应开始 =======\n" +
|
||||
"API地址: {}\n" +
|
||||
"HTTP状态码: {}\n" +
|
||||
"原始响应: {}\n" +
|
||||
"======= 响应结束 =======",
|
||||
url,
|
||||
statusCode,
|
||||
responseBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package com.cool.store.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.cool.store.enums.ErrorCodeEnum;
|
||||
import com.cool.store.exception.ServiceException;
|
||||
import com.cool.store.request.oppty.*;
|
||||
import com.cool.store.response.oppty.*;
|
||||
import com.cool.store.service.ThirdOpportunityService;
|
||||
import com.cool.store.utils.JsonUtils;
|
||||
import com.cool.store.utils.SignatureUtils;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/3/26 15:20
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ThirdOpportunityServiceImpl implements ThirdOpportunityService {
|
||||
|
||||
@Value("${zx.opportunity.url}")
|
||||
private String apiUrl;
|
||||
|
||||
@Value("${third.party.appKey}")
|
||||
private String appKey;
|
||||
|
||||
@Value("${third.party.appSecret}")
|
||||
private String appSecret;
|
||||
|
||||
@Resource
|
||||
OkHttpClient okHttpClient;
|
||||
|
||||
@Resource
|
||||
ObjectMapper objectMapper;
|
||||
|
||||
|
||||
@Override
|
||||
public OpportunityInfoPageResponse listOpportunities(OpportunityListRequest requestBody) {
|
||||
// 1. 发送POST请求
|
||||
String url = apiUrl+"open/oppty/v1/listOppty";
|
||||
return executeApiCall(url, requestBody, OpportunityInfoPageResponse.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpportunityDetailResponse getOpportunitiesDetail(OpportunityDetailRequest requestBody) {
|
||||
String url = apiUrl + "open/oppty/v1/opptyDetail";
|
||||
return executeApiCall(url, requestBody, OpportunityDetailResponse.class);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String followOpportunity(FollowRequest requestBody) {
|
||||
String url = apiUrl+"open/oppty/v1/saveAtUser";
|
||||
return executeApiCall(url, requestBody, String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String inspectionOpportunity(InspectionRequest requestBody) {
|
||||
String url = apiUrl+"open/oppty/v1/inspection";
|
||||
return executeApiCall(url, requestBody, String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String berthOperation(BerthOperationRequest requestBody) {
|
||||
String url = apiUrl+"open/oppty/v1/addBerthInfo";
|
||||
return executeApiCall(url, requestBody, String.class);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String storeOperation(StoreOperationRequest requestBody) {
|
||||
String url = apiUrl+"open/oppty/v1/addStore";
|
||||
return executeApiCall(url, requestBody, String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CityResponse> cityList(CityRequest requestBody) {
|
||||
// 1. 发送POST请求
|
||||
String url = apiUrl+"open/oppty/v1/listCity";
|
||||
return executeApiCall(url, requestBody, List.class);
|
||||
}
|
||||
|
||||
|
||||
private <T> T executeApiCall(String url, Object requestBody, Class<T> responseType) {
|
||||
// 1. 打印请求前日志
|
||||
logRequest(url, requestBody);
|
||||
|
||||
try {
|
||||
Request request = buildRequest(requestBody, url);
|
||||
|
||||
try (Response response = okHttpClient.newCall(request).execute()) {
|
||||
// 2. 获取原始响应内容
|
||||
String responseBody = response.body().string();
|
||||
|
||||
// 3. 打印响应日志
|
||||
logResponse(url, response.code(), responseBody);
|
||||
|
||||
if (!response.isSuccessful()) {
|
||||
throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR,
|
||||
"HTTP请求失败,状态码: " + response.code());
|
||||
}
|
||||
|
||||
// 4. 解析响应
|
||||
JavaType javaType = objectMapper.getTypeFactory()
|
||||
.constructParametricType(OpportunityApiResponse.class, responseType);
|
||||
|
||||
OpportunityApiResponse<T> apiResponse = objectMapper.readValue(responseBody, javaType);
|
||||
|
||||
if (apiResponse.getCode() != 200) {
|
||||
throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR,
|
||||
"业务逻辑错误: " + apiResponse.getMsg());
|
||||
}
|
||||
|
||||
return apiResponse.getData();
|
||||
}
|
||||
} catch (ServiceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("API调用异常 - URL: {}, 错误: {}", url, e.getMessage(), e);
|
||||
throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR, "接口调用异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Request buildRequest(Object requestBody, String url) {
|
||||
Map<String, Object> params = JsonUtils.parseJsonToMap(JSONObject.toJSONString(requestBody));
|
||||
long timestamp = System.currentTimeMillis() / 1000;
|
||||
|
||||
String signString = SignatureUtils.buildSignString(params, appKey, timestamp);
|
||||
String sign = SignatureUtils.hmacSha256(signString, appSecret);
|
||||
|
||||
log.debug("签名生成 - 原始字符串: {}, 签名结果: {}", signString, sign);
|
||||
|
||||
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json"),
|
||||
JSONObject.toJSONString(requestBody)
|
||||
);
|
||||
|
||||
return new Request.Builder()
|
||||
.url(url)
|
||||
.post(body)
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.addHeader("Accept", "application/json")
|
||||
.addHeader("sign", sign)
|
||||
.addHeader("timestamp", String.valueOf(timestamp))
|
||||
.addHeader("appkey", appKey)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录请求日志
|
||||
*/
|
||||
private void logRequest(String url, Object requestBody) {
|
||||
if (log.isInfoEnabled()) {
|
||||
try {
|
||||
log.info("\n======= 请求开始 =======\n" +
|
||||
"API地址: {}\n" +
|
||||
"请求参数: {}\n" +
|
||||
"======= 请求结束 =======",
|
||||
url,
|
||||
objectMapper.writerWithDefaultPrettyPrinter()
|
||||
.writeValueAsString(requestBody));
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("日志JSON序列化失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录响应日志
|
||||
*/
|
||||
private void logResponse(String url, int statusCode, String responseBody) {
|
||||
if (log.isInfoEnabled()) {
|
||||
try {
|
||||
// 尝试美化JSON输出
|
||||
Object json = objectMapper.readValue(responseBody, Object.class);
|
||||
String prettyResponse = objectMapper.writerWithDefaultPrettyPrinter()
|
||||
.writeValueAsString(json);
|
||||
|
||||
log.info("\n======= 响应开始 =======\n" +
|
||||
"API地址: {}\n" +
|
||||
"HTTP状态码: {}\n" +
|
||||
"响应内容: {}\n" +
|
||||
"======= 响应结束 =======",
|
||||
url,
|
||||
statusCode,
|
||||
prettyResponse);
|
||||
} catch (Exception e) {
|
||||
// 非JSON响应或解析失败时直接输出原始内容
|
||||
log.info("\n======= 响应开始 =======\n" +
|
||||
"API地址: {}\n" +
|
||||
"HTTP状态码: {}\n" +
|
||||
"原始响应: {}\n" +
|
||||
"======= 响应结束 =======",
|
||||
url,
|
||||
statusCode,
|
||||
responseBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.cool.store.controller.webc;
|
||||
|
||||
import com.cool.store.request.bigdata.ProfitDataRequest;
|
||||
import com.cool.store.request.oppty.*;
|
||||
import com.cool.store.response.ResponseResult;
|
||||
import com.cool.store.response.bigdata.ActDataResponse;
|
||||
import com.cool.store.response.bigdata.ProfitDataResponse;
|
||||
import com.cool.store.response.bigdata.ProfitRateResponse;
|
||||
import com.cool.store.response.oppty.CityResponse;
|
||||
import com.cool.store.response.oppty.OpportunityDetailResponse;
|
||||
import com.cool.store.response.oppty.OpportunityInfoPageResponse;
|
||||
import com.cool.store.service.ThirdBigDataService;
|
||||
import com.cool.store.service.ThirdOpportunityService;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/4/1 20:49
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/mini/third/api")
|
||||
public class ThirdApiController {
|
||||
|
||||
@Resource
|
||||
ThirdOpportunityService thirdOpportunityService;
|
||||
@Resource
|
||||
ThirdBigDataService thirdBigDataService;
|
||||
|
||||
@PostMapping("/list")
|
||||
@ApiOperation("机会点列表/我的机会点")
|
||||
public ResponseResult<OpportunityInfoPageResponse> listOpportunities(@Valid @RequestBody OpportunityListRequest request) {
|
||||
return ResponseResult.success(thirdOpportunityService.listOpportunities(request));
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/getOpportunitiesDetail")
|
||||
@ApiOperation("机会点详情")
|
||||
public ResponseResult<OpportunityDetailResponse> getOpportunitiesDetail(@Valid @RequestBody OpportunityDetailRequest request) {
|
||||
return ResponseResult.success(thirdOpportunityService.getOpportunitiesDetail(request));
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/followOpportunity")
|
||||
@ApiOperation("关注/取消关注 机会点")
|
||||
public ResponseResult<String> followOpportunity(@Valid @RequestBody FollowRequest request) {
|
||||
return ResponseResult.success(thirdOpportunityService.followOpportunity(request));
|
||||
}
|
||||
|
||||
@PostMapping("/inspectionOpportunity")
|
||||
@ApiOperation("考察机会点")
|
||||
public ResponseResult<String> inspectionOpportunity(@Valid @RequestBody InspectionRequest request) {
|
||||
return ResponseResult.success(thirdOpportunityService.inspectionOpportunity(request));
|
||||
}
|
||||
|
||||
@PostMapping("/berthOperation")
|
||||
@ApiOperation("铺位关注机会点")
|
||||
public ResponseResult<String> berthOperation(@Valid @RequestBody BerthOperationRequest request) {
|
||||
return ResponseResult.success(thirdOpportunityService.berthOperation(request));
|
||||
}
|
||||
|
||||
@PostMapping("/storeOperation")
|
||||
@ApiOperation("门店关注机会点")
|
||||
public ResponseResult<String> storeOperation(@Valid @RequestBody StoreOperationRequest request) {
|
||||
return ResponseResult.success(thirdOpportunityService.storeOperation(request));
|
||||
}
|
||||
|
||||
@PostMapping("/cityList")
|
||||
@ApiOperation("省市区列表 查询省传0")
|
||||
public ResponseResult<List<CityResponse>> listOpportunities(@Valid @RequestBody CityRequest request) {
|
||||
// 调用第三方接口
|
||||
return ResponseResult.success(thirdOpportunityService.cityList(request));
|
||||
}
|
||||
|
||||
@PostMapping("/getProfitData")
|
||||
@ApiOperation("昨天毛利")
|
||||
public ResponseResult<ProfitDataResponse> listOpportunities(@Valid @RequestBody ProfitDataRequest request) {
|
||||
// 调用第三方接口
|
||||
return ResponseResult.success(thirdBigDataService.getProfitData(request));
|
||||
}
|
||||
|
||||
@PostMapping("/getCurMonthProfitData")
|
||||
@ApiOperation("本月毛利")
|
||||
public ResponseResult<ProfitDataResponse> getCurMonthProfitData(@Valid @RequestBody ProfitDataRequest request) {
|
||||
// 调用第三方接口
|
||||
return ResponseResult.success(thirdBigDataService.getCurMonthProfitData(request));
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/getPreMonthProfitData")
|
||||
@ApiOperation("上月毛利")
|
||||
public ResponseResult<ProfitDataResponse> getPreMonthProfitData(@Valid @RequestBody ProfitDataRequest request) {
|
||||
// 调用第三方接口
|
||||
return ResponseResult.success(thirdBigDataService.getPreMonthProfitData(request));
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/getMonthProfitTrend")
|
||||
@ApiOperation("毛利趋势")
|
||||
public ResponseResult<List<ProfitRateResponse>> getMonthProfitTrend(@Valid @RequestBody ProfitDataRequest request) {
|
||||
// 调用第三方接口
|
||||
return ResponseResult.success(thirdBigDataService.getMonthProfitTrend(request));
|
||||
}
|
||||
|
||||
@PostMapping("/getActData")
|
||||
@ApiOperation("门店实收")
|
||||
public ResponseResult<ActDataResponse> getActData(@Valid @RequestBody ProfitDataRequest request) {
|
||||
// 调用第三方接口
|
||||
return ResponseResult.success(thirdBigDataService.getActData(request));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -28,7 +28,7 @@ isv.domain=https://abstore-isv.coolstore.cn/isv
|
||||
#rocketmq \u914D\u7F6E
|
||||
rocketmq.accessKey=LTAI5tJbgtyoHUvofTaeP1RP
|
||||
rocketmq.secretKey=myg755iCx0j4PyQkHMaUVeOr0bw1tA
|
||||
rocketmq.nameSrvAdder=http://MQ_INST_1947409023213164_BX3sLZnA.cn-hangzhou.mq-internal.aliyuncs.com:8080
|
||||
rocketmq.nameSrvAdder=http://MQ_INST_1947409023213164_BX3sLZnA.cn-hangzhou.mq.aliyuncs.com:80
|
||||
rocketmq.topic=simple_message
|
||||
|
||||
#oss配置
|
||||
@@ -82,3 +82,13 @@ enterprise.dingCorpId=wpayJeDAAAhGIFgUJpJN-zg39JuNbYhg
|
||||
|
||||
##qywx.task.notice.url1=https://tstore-api.coolstore.cn/notice?corpId=%s&appType=%s&target=%s
|
||||
qywx.task.notice.url2=https://tstore-h5.coolstore.cn/?corpId=%s&appType=%s#/notice?target=%s¬iceType=zx&corpId=%s&appType=%s&eid=%s
|
||||
|
||||
|
||||
third.party.appKey=IGSAEQoakR2HEaYx
|
||||
third.party.appSecret=aPsA99K1obFeFm3m
|
||||
zx.opportunity.url=https://snp.wenmatech.com/
|
||||
|
||||
#大数据地址
|
||||
zx.big.data.url=https://ds.zhengxinfood.com/
|
||||
zx.big.data.appKey=ff203b5567744feaaae49fb86f58c5bf
|
||||
zx.big.data.appSecret=35b8b9a400b4430fa022190be0913cd6
|
||||
|
||||
Reference in New Issue
Block a user