Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package me.chanjar.weixin.channel.api;

import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgParam;
import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgResponse;
import me.chanjar.weixin.common.error.WxErrorException;

/**
* 视频号小店 商家客服服务
*
* @author <a href="https://github.com/github-copilot">GitHub Copilot</a>
*/
public interface WxChannelKfService {

/**
* 上传多媒体资源.
*
* @param openId 用户 open_id
* @param msgType 文件类型,仅支持:video/file/image
* @param file 文件字节内容
* @return cos_url
*
* @throws WxErrorException 异常
*/
String uploadMedia(String openId, String msgType, byte[] file) throws WxErrorException;

/**
* 上传多媒体资源.
*
* @param openId 用户 open_id
* @param msgType 文件类型,仅支持:video/file/image
* @param fileName 文件名
* @param file 文件字节内容
* @return cos_url
*
* @throws WxErrorException 异常
*/
String uploadMedia(String openId, String msgType, String fileName, byte[] file) throws WxErrorException;

/**
* 发送客服消息.
*
* @param param 请求参数
* @return 发送结果
*
* @throws WxErrorException 异常
*/
WxChannelKfSendMsgResponse sendMessage(WxChannelKfSendMsgParam param) throws WxErrorException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,11 @@ public interface WxChannelService extends BaseWxChannelService {
*/
WxChannelLiveDashboardService getLiveDashboardService();

/**
* 商家客服服务
*
* @return 商家客服服务
*/
WxChannelKfService getKfService();

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public abstract class BaseWxChannelServiceImpl<H, P> implements WxChannelService
private WxChannelVipService vipService = null;
private WxChannelCompassFinderService compassFinderService = null;
private WxChannelLiveDashboardService liveDashboardService = null;
private WxChannelKfService kfService = null;

protected WxChannelConfig config;
private int retrySleepMillis = 1000;
Expand Down Expand Up @@ -473,4 +474,12 @@ public synchronized WxChannelLiveDashboardService getLiveDashboardService() {
return liveDashboardService;
}

@Override
public synchronized WxChannelKfService getKfService() {
if (kfService == null) {
kfService = new WxChannelKfServiceImpl(this);
}
return kfService;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package me.chanjar.weixin.channel.api.impl;

import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.channel.api.WxChannelKfService;
import me.chanjar.weixin.channel.bean.kf.WxChannelKfCosUploadResponse;
import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgParam;
import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgResponse;
import me.chanjar.weixin.channel.util.ResponseUtils;
import me.chanjar.weixin.common.bean.CommonUploadParam;
import me.chanjar.weixin.common.error.WxErrorException;

import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Kf.COS_UPLOAD_URL;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Kf.SEND_MSG_URL;

/**
* 视频号小店 商家客服服务实现
*
* @author <a href="https://github.com/github-copilot">GitHub Copilot</a>
*/
@Slf4j
public class WxChannelKfServiceImpl implements WxChannelKfService {

/** 微信商店服务 */
private final BaseWxChannelServiceImpl<?, ?> shopService;

public WxChannelKfServiceImpl(BaseWxChannelServiceImpl<?, ?> shopService) {
this.shopService = shopService;
}

@Override
public String uploadMedia(String openId, String msgType, byte[] file) throws WxErrorException {
return uploadMedia(openId, msgType, null, file);
}

@Override
public String uploadMedia(String openId, String msgType, String fileName, byte[] file) throws WxErrorException {
CommonUploadParam uploadParam = CommonUploadParam.fromBytes("file", fileName, file)
.addFormField("open_id", openId)
.addFormField("msg_type", msgType);
String resJson = shopService.upload(COS_UPLOAD_URL, uploadParam);
WxChannelKfCosUploadResponse response = ResponseUtils.decode(resJson, WxChannelKfCosUploadResponse.class);
return response.getCosUrl();
}

@Override
public WxChannelKfSendMsgResponse sendMessage(WxChannelKfSendMsgParam param) throws WxErrorException {
String resJson = shopService.post(SEND_MSG_URL, param);
return ResponseUtils.decode(resJson, WxChannelKfSendMsgResponse.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package me.chanjar.weixin.channel.bean.kf;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;

import java.io.Serializable;

/**
* 上传多媒体资源返回结果
*
* @author <a href="https://github.com/github-copilot">GitHub Copilot</a>
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class WxChannelKfCosUploadResponse extends WxChannelBaseResponse implements Serializable {

private static final long serialVersionUID = -8073026558742450133L;

/** 多媒体 cos_url */
@JsonProperty("cos_url")
private String cosUrl;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package me.chanjar.weixin.channel.bean.kf;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
* 发送客服消息请求参数
*
* @author <a href="https://github.com/github-copilot">GitHub Copilot</a>
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(Include.NON_NULL)
public class WxChannelKfSendMsgParam implements Serializable {

private static final long serialVersionUID = -7384287911696365032L;

/** 唯一任务ID,填写后按任务ID去重 */
@JsonProperty("request_id")
private String requestId;

/** 用户 open_id */
@JsonProperty("open_id")
private String openId;

/** 消息类型 */
@JsonProperty("msg_type")
private String msgType;

/** 文本消息 */
@JsonProperty("text")
private TextMessage text;

/** 图片消息 */
@JsonProperty("image")
private CosUrlMessage image;

/** 视频消息 */
@JsonProperty("video")
private CosUrlMessage video;

/** 文件消息 */
@JsonProperty("file")
private CosUrlMessage file;

/** 商品卡片消息 */
@JsonProperty("product_share")
private ProductShareMessage productShare;

/** 订单卡片消息 */
@JsonProperty("order_share")
private OrderShareMessage orderShare;

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class TextMessage implements Serializable {

private static final long serialVersionUID = -5001585611550636499L;

@JsonProperty("content")
private String content;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class CosUrlMessage implements Serializable {

private static final long serialVersionUID = 8403720861098936947L;

@JsonProperty("cos_url")
private String cosUrl;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class ProductShareMessage implements Serializable {

private static final long serialVersionUID = -3049552399099249795L;

@JsonProperty("product_id")
private String productId;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public static class OrderShareMessage implements Serializable {

private static final long serialVersionUID = 7136546635145180607L;

@JsonProperty("order_id")
private String orderId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package me.chanjar.weixin.channel.bean.kf;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;

import java.io.Serializable;

/**
* 发送客服消息返回结果
*
* @author <a href="https://github.com/github-copilot">GitHub Copilot</a>
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class WxChannelKfSendMsgResponse extends WxChannelBaseResponse implements Serializable {

private static final long serialVersionUID = -4994877385473101709L;

/** 消息ID */
@JsonProperty("msg_id")
private String msgId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,15 @@ public interface Order {
String DECODE_SENSITIVE_INFO_URL = "https://api.weixin.qq.com/channels/ec/order/sensitiveinfo/decode";
}

/** 商家客服相关接口 */
public interface Kf {

/** 上传多媒体资源 */
String COS_UPLOAD_URL = "https://api.weixin.qq.com/channels/ec/commkf/cosupload";
/** 发送消息 */
String SEND_MSG_URL = "https://api.weixin.qq.com/channels/ec/commkf/sendmsg";
Comment on lines +224 to +226
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the documented KF endpoints

When clients call either new KF method, these constants send the request to /channels/ec/commkf/..., but the WeChat store KF APIs are exposed under /channels/ec/kf/cosupload and /channels/ec/kf/sendmsg. As written, both upload and send-message calls are routed to the wrong API path and will fail before the new request/response models can be used.

Useful? React with 👍 / 👎.

}

/** 售后相关接口 */
public interface AfterSale {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package me.chanjar.weixin.channel.bean.kf;

import me.chanjar.weixin.channel.util.JsonUtils;
import org.testng.annotations.Test;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;

/**
* @author <a href="https://github.com/github-copilot">GitHub Copilot</a>
*/
public class WxChannelKfBeanTest {

@Test
public void testSendMsgParamEncode() {
WxChannelKfSendMsgParam param = new WxChannelKfSendMsgParam();
param.setRequestId("63abd34b-656b-4082-b364-5f74226e1a20");
param.setOpenId("o7eep4jVQelr2eyoDSmE1xxxxxx");
param.setMsgType("text");
param.setText(new WxChannelKfSendMsgParam.TextMessage("测试消息123"));

String json = JsonUtils.encode(param);
assertNotNull(json);
assertTrue(json.contains("\"request_id\":\"63abd34b-656b-4082-b364-5f74226e1a20\""));
assertTrue(json.contains("\"open_id\":\"o7eep4jVQelr2eyoDSmE1xxxxxx\""));
assertTrue(json.contains("\"msg_type\":\"text\""));
assertTrue(json.contains("\"text\":{\"content\":\"测试消息123\"}"));
}

@Test
public void testSendMsgResponseDecode() {
String json = "{\"msg_id\":\"3886839959369302016\",\"errmsg\":\"ok\",\"errcode\":0}";
WxChannelKfSendMsgResponse response = JsonUtils.decode(json, WxChannelKfSendMsgResponse.class);
assertNotNull(response);
assertEquals(response.getMsgId(), "3886839959369302016");
assertEquals(response.getErrCode(), 0);
assertEquals(response.getErrMsg(), "ok");
}

@Test
public void testCosUploadResponseDecode() {
String json = "{\"cos_url\":\"https://channels.weixin.qq.com/shop/commkf/downloadmedia?encrypted_param=xxxxx&timestamp=xxxxx&openid=xxxxxx&msg_type=7\",\"errmsg\":\"ok\",\"errcode\":0}";
WxChannelKfCosUploadResponse response = JsonUtils.decode(json, WxChannelKfCosUploadResponse.class);
assertNotNull(response);
assertTrue(response.getCosUrl().contains("channels.weixin.qq.com/shop/commkf/downloadmedia"));
assertEquals(response.getErrCode(), 0);
assertEquals(response.getErrMsg(), "ok");
}
}