فهرست منبع

接入微走订单推送

lk 1 روز پیش
والد
کامیت
0df3859e71

+ 149 - 0
fs-service/src/main/java/com/fs/erp/domain/WeizouApiPushOrderParam.java

@@ -0,0 +1,149 @@
+package com.fs.erp.domain;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.util.List;
+
+
+/**
+ * weizou订单推送实体类
+ */
+@Data
+@Accessors(chain = true)
+public class WeizouApiPushOrderParam {
+
+    /**
+     * 来源系统 (云联固定17)
+     */
+    private Integer isFromOutSys = 17;
+
+    /**
+     * 状态 (固定值2 默认到审核)
+     */
+    private Integer status = 2;
+
+    /**
+     * 支付状态 0未收款 1已收款 2部分收款 按实际情况传
+     */
+    @NotNull(message = "支付状态不能为空")
+    private Integer paid;
+
+    /**
+     * 收货人姓名
+     */
+    @NotNull(message = "收货人姓名不能为空")
+    private String realName;
+
+    /**
+     * 收货人电话
+     */
+    @NotNull(message = "收货人电话不能为空")
+    private String phone;
+
+    /**
+     * 详细地址
+     */
+    @NotNull(message = "详细地址不能为空")
+    private String userAddress;
+
+    /**
+     * 运费金额
+     */
+    private BigDecimal freightPrice;
+
+    /**
+     * 支付方式
+     * 网上付款(在线支付) 01 邮政快递, 02 顺丰快递 ,06 中通快递 ,07 圆通快递
+     * 货到付款(托收) 03 邮政托收, 04 顺丰托收 , 08 中通托收 , 05 圆通托收
+     */
+    @NotNull(message = "支付方式不能为空")
+    private String payType;
+
+    /**
+     * 已付金额
+     */
+    private BigDecimal payPrice;
+
+    /**
+     * 付款方式 01 网上付款, 02 货到付款
+     */
+    @NotNull(message = "付款方式不能为空")
+    private String paymentType;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 制单人 (正式环境待提供)
+     */
+    private String createName = "wz_5dd9126f39b84ae096a0e18ea313559a";
+
+    /**
+     * 收货人所在省
+     */
+    @NotNull(message = "收货人所在省不能为空")
+    private String province;
+
+    /**
+     * 收货人所在市
+     */
+    @NotNull(message = "收货人所在市不能为空")
+    private String city;
+
+    /**
+     * 收货人所在区
+     */
+    @NotNull(message = "收货人所在区不能为空")
+    private String district;
+
+    /**
+     * 地区邮编
+     */
+    private String postNumber;
+
+    /**
+     * 订单标签
+     */
+    private String orderLabel;
+
+    /**
+     * 定金
+     */
+    private BigDecimal precollection;
+
+    /**
+     * 优惠金额
+     */
+    private BigDecimal discountsAmount;
+
+    /**
+     * 快递公司 顺丰快递 邮政快递 中通快递 圆通快递
+     */
+    @NotNull(message = "快递公司不能为空")
+    private String deliveryType;
+
+    /**
+     * 订单媒体
+     */
+    private String orderMedia;
+
+    /**
+     * 额外订单号(对应平台的订单号)
+     */
+    @NotNull(message = "额外订单号不能为空")
+    private String extendOrderId;
+
+    /**
+     * 表体商品列表
+     */
+    @NotNull(message = "商品列表不能为空")
+    @Valid
+    private List<WeizouOrderEntry> entryList;
+}
+

+ 35 - 0
fs-service/src/main/java/com/fs/erp/domain/WeizouOrderEntry.java

@@ -0,0 +1,35 @@
+package com.fs.erp.domain;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.math.BigDecimal;
+
+@Data
+@Accessors(chain = true)
+public class WeizouOrderEntry {
+    /**
+     * 商品代码
+     */
+    private String skuNumber;
+
+    /**
+     * 市场价
+     */
+    private BigDecimal retailPrice;
+
+    /**
+     * 销售价
+     */
+    private BigDecimal taxPrice;
+
+    /**
+     * 价格合计
+     */
+    private BigDecimal taxTotalAmount;
+
+    /**
+     * 商品数量
+     */
+    private Integer count;
+}

+ 207 - 0
fs-service/src/main/java/com/fs/erp/utils/WeizouApiClient.java

@@ -0,0 +1,207 @@
+package com.fs.erp.utils;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fs.erp.domain.WeizouApiPushOrderParam;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 微走云联客服订单推送工具类 (Java 8)
+ *
+ * 接口文档:https://my.feishu.cn/docx/Stn6dvLZlon81mx8A65cEkgAnRf
+ *
+ * 使用前需要补充:
+ * 1. 实际的 APP_ID 和 APP_SECRET
+ * 2. 正式的接口地址(文档中标记为“待更新”)
+ * 3. 请求参数的具体字段
+ */
+@Slf4j
+@Component
+public class WeizouApiClient {
+
+    // 1. 声明静态变量
+    private static RedisTemplate redisTemplate;
+
+    // 2. 注入实例到非静态变量
+    @Autowired
+    @Qualifier("redisTemplate")
+    private RedisTemplate redisTemplateInstance;
+
+    // 3. @PostConstruct 将实例赋值给静态变量
+    @PostConstruct
+    public void initStatic() {
+        redisTemplate = this.redisTemplateInstance;
+        log.info("RedisTemplate 静态初始化完成");
+    }
+
+    private static final String BASE_URL = "https://test-api.weizou.com:9702"; // TODO: 替换为文档中的正式环境地址
+
+    // TODO: 替换为平台提供的实际应用ID和密钥
+    private static final String APP_ID = "005fcddb9b664eeca80a13b62e823b63";
+    private static final String APP_SECRET = "a6cd645383a3405d842ce00da106ded0";
+
+    private static final String WEIZOU_API_CLIENT_KEY = "weizou:api:client:key";
+
+    private static final OkHttpClient httpClient = new OkHttpClient.Builder()
+            .connectTimeout(10, TimeUnit.SECONDS)
+            .writeTimeout(10, TimeUnit.SECONDS)
+            .readTimeout(30, TimeUnit.SECONDS)
+            .build();
+
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+
+    // 存储当前有效的token(实际生产环境建议用Redis等分布式缓存)
+    private static String accessToken;
+    private static long tokenExpireTime; // 过期时间戳(毫秒)
+
+    /**
+     * 获取有效的访问令牌(自动处理缓存和刷新)
+     */
+    public static String getAccessToken() throws IOException {
+        // 如果token有效则直接返回
+        Map<String, Object> tokenMap = redisTemplate.opsForHash().entries(WEIZOU_API_CLIENT_KEY);
+        if (!tokenMap.isEmpty()){
+            String token = (String) tokenMap.get("client_token");
+            String expireTime = (String) tokenMap.get("expires_in");
+            if (token != null && expireTime != null) {
+                long expireTimeL = Long.parseLong(expireTime);
+                if (System.currentTimeMillis() < expireTimeL) {
+                    return token;
+                }
+            }
+        }
+
+        // 否则重新获取
+        return refreshAccessToken();
+    }
+
+    /**
+     * 刷新访问令牌
+     * POST /auth/oauth2/client_token
+     */
+    private static synchronized String refreshAccessToken() throws IOException {
+        // 双重检查,防止并发刷新
+        Map<String, Object> tokenMap = redisTemplate.opsForHash().entries(WEIZOU_API_CLIENT_KEY);
+        if (!tokenMap.isEmpty()){
+            String token = (String) tokenMap.get("client_token");
+            Long expireTime = (Long) tokenMap.get("expires_in");
+            if (token != null && expireTime != null) {
+                if (System.currentTimeMillis() < expireTime) {
+                    return token;
+                }
+            }
+        }
+
+        Request request = new Request.Builder()
+                .url(BASE_URL + "/auth/oauth2/client_token"+ "?client_id=" + APP_ID +
+                        "&client_secret=" + APP_SECRET)
+//                .post(requestBody)
+                .addHeader("Content-Type", "application/json")
+                .build();
+
+        try (Response response = httpClient.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                throw new IOException("获取token失败,HTTP状态码: " + response.code());
+            }
+
+            String responseBody = response.body().string();
+            JsonNode jsonNode = objectMapper.readTree(responseBody);
+
+            // TODO: 根据实际返回格式解析token和过期时间
+            JsonNode jsonResult = jsonNode.get("result");
+            String token = jsonResult.get("client_token").asText();
+            Long expiresIn = jsonResult.get("expires_in").asLong(); // 有效期(秒)
+
+            // 计算过期时间(提前5分钟过期,避免边界问题)
+            Long expireTime = System.currentTimeMillis() + (expiresIn - 60) * 1000L;
+            Map<String, String> hash = new HashMap<>();
+            hash.put("client_token", token);
+            hash.put("expires_in", String.valueOf(expireTime));
+            redisTemplate.opsForHash().putAll(WEIZOU_API_CLIENT_KEY, hash);
+            redisTemplate.expire(WEIZOU_API_CLIENT_KEY, expiresIn, TimeUnit.SECONDS);
+            return token;
+        }
+    }
+
+    /**
+     * 推送客服订单
+     * POST /cs/api/storeOrder/sync
+     *
+     * @param orderData 订单数据(根据实际接口文档构造)
+     * @return 接口返回结果
+     */
+    public static String pushOrder(WeizouApiPushOrderParam orderData) throws IOException {
+        String token = getAccessToken();
+
+        String orderJson = objectMapper.writeValueAsString(orderData);
+
+        Request request = new Request.Builder()
+                .url(BASE_URL + "/cs/api/storeOrder/sync")
+                .post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), orderJson))
+                .addHeader("Content-Type", "application/json")
+                .addHeader("token", token) // 假设使用Bearer Token
+                // TODO: 根据文档添加其他必要的Headers
+                .build();
+
+        try (Response response = httpClient.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                throw new IOException("订单推送失败,HTTP状态码: " + response.code());
+            }
+            return response.body().string();
+        }
+    }
+
+    // ========== 内部辅助类 ==========
+
+    /**
+     * Token请求参数(根据实际文档调整)
+     */
+    public static class TokenRequest {
+        private String client_id;     // 或 app_id
+        private String client_secret; // 或 app_secret
+//        private String grant_type = "client_credentials"; // OAuth2标准参数
+
+        public TokenRequest(String clientId, String clientSecret) {
+            this.client_id = clientId;
+            this.client_secret = clientSecret;
+        }
+
+        // Getters and setters
+        public String getClient_id() { return client_id; }
+        public void setClient_id(String client_id) { this.client_id = client_id; }
+        public String getClient_secret() { return client_secret; }
+        public void setClient_secret(String client_secret) { this.client_secret = client_secret; }
+//        public String getGrant_type() { return grant_type; }
+//        public void setGrant_type(String grant_type) { this.grant_type = grant_type; }
+    }
+
+    /**
+     * Token响应(根据实际文档调整)
+     */
+    public static class TokenResponse {
+        private String access_token;
+        private int expires_in;
+        private String token_type;
+
+        // Getters and setters
+        public String getAccess_token() { return access_token; }
+        public void setAccess_token(String access_token) { this.access_token = access_token; }
+        public int getExpires_in() { return expires_in; }
+        public void setExpires_in(int expires_in) { this.expires_in = expires_in; }
+        public String getToken_type() { return token_type; }
+        public void setToken_type(String token_type) { this.token_type = token_type; }
+    }
+}