客服模块
This commit is contained in:
56
urbanLifelineServ/common/common-wechat/pom.xml
Normal file
56
urbanLifelineServ/common/common-wechat/pom.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.xyzh</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-wechat</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<!-- 用于获取微信客服的系统配置 -->
|
||||
<dependency>
|
||||
<groupId>org.xyzh.apis</groupId>
|
||||
<artifactId>api-system</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.xyzh.common.wechat.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 微信客服配置
|
||||
* @filename WeChatConfig.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Data
|
||||
public class WeChatKefuConfig {
|
||||
|
||||
private String corpId;
|
||||
|
||||
private String secret;
|
||||
|
||||
private String token;
|
||||
|
||||
private String encodingAesKey;
|
||||
|
||||
private String openKfid;
|
||||
|
||||
private String welcomeTemplate;
|
||||
|
||||
private String accessToken;
|
||||
|
||||
private Long accessTokenExpireTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.xyzh.common.wechat.config;
|
||||
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.xyzh.api.system.service.SysConfigService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* @description 微信初始化配置,从系统配置中加载微信客服参数
|
||||
* @filename WeChatInit.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Component
|
||||
public class WeChatKefuInit {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(WeChatKefuInit.class);
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "system", check = false)
|
||||
private SysConfigService sysConfigService;
|
||||
|
||||
private static WeChatKefuConfig weChatConfig;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
logger.info("初始化微信客服配置...");
|
||||
weChatConfig = new WeChatKefuConfig();
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
public void loadConfig() {
|
||||
try {
|
||||
weChatConfig.setCorpId(getConfigValue("wechat.kefu.corpId"));
|
||||
weChatConfig.setSecret(getConfigValue("wechat.kefu.secret"));
|
||||
weChatConfig.setToken(getConfigValue("wechat.kefu.token"));
|
||||
weChatConfig.setEncodingAesKey(getConfigValue("wechat.kefu.encodingAesKey"));
|
||||
weChatConfig.setOpenKfid(getConfigValue("wechat.kefu.openKfid"));
|
||||
weChatConfig.setWelcomeTemplate(getConfigValue("wechat.kefu.welcomeTemplate"));
|
||||
logger.info("微信客服配置加载完成: corpId={}, openKfid={}", weChatConfig.getCorpId(), weChatConfig.getOpenKfid());
|
||||
} catch (Exception e) {
|
||||
logger.error("加载微信客服配置失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getConfigValue(String key) {
|
||||
if (sysConfigService == null) {
|
||||
logger.warn("SysConfigService 未注入,跳过配置加载: {}", key);
|
||||
return null;
|
||||
}
|
||||
ResultDomain<String> result = sysConfigService.getConfigValueByKey(key);
|
||||
if (result != null && result.getSuccess()) {
|
||||
return result.getData();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static WeChatKefuConfig getConfig() {
|
||||
return weChatConfig;
|
||||
}
|
||||
|
||||
public void refreshConfig() {
|
||||
loadConfig();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.xyzh.common.wechat.handler;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.xyzh.common.wechat.pojo.kefu.KefuCallback;
|
||||
|
||||
/**
|
||||
* @description 客服回调默认处理器
|
||||
* @filename DefaultKefuCallbackHandler.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
public class DefaultKefuCallbackHandler implements KefuCallbackHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DefaultKefuCallbackHandler.class);
|
||||
|
||||
@Override
|
||||
public void onEnterSession(KefuCallback callback) {
|
||||
logger.info("用户进入会话: externalUserid={}, openKfid={}, welcomeCode={}",
|
||||
callback.getExternalUserid(), callback.getOpenKfid(), callback.getWelcomeCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMsgSendFail(KefuCallback callback) {
|
||||
logger.warn("消息发送失败: failMsgid={}, failType={}",
|
||||
callback.getFailMsgid(), callback.getFailType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserRecallMsg(KefuCallback callback) {
|
||||
logger.info("用户撤回消息: msgid={}", callback.getMsgid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServicerStatusChange(KefuCallback callback) {
|
||||
logger.info("接待人员状态变更: openKfid={}", callback.getOpenKfid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionStatusChange(KefuCallback callback) {
|
||||
logger.info("会话状态变更: openKfid={}, externalUserid={}",
|
||||
callback.getOpenKfid(), callback.getExternalUserid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextMessage(KefuCallback callback) {
|
||||
logger.info("收到文本消息: externalUserid={}, content={}",
|
||||
callback.getExternalUserid(), callback.getContent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImageMessage(KefuCallback callback) {
|
||||
logger.info("收到图片消息: externalUserid={}, mediaId={}",
|
||||
callback.getExternalUserid(), callback.getMediaId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoiceMessage(KefuCallback callback) {
|
||||
logger.info("收到语音消息: externalUserid={}, mediaId={}",
|
||||
callback.getExternalUserid(), callback.getMediaId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoMessage(KefuCallback callback) {
|
||||
logger.info("收到视频消息: externalUserid={}, mediaId={}",
|
||||
callback.getExternalUserid(), callback.getMediaId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileMessage(KefuCallback callback) {
|
||||
logger.info("收到文件消息: externalUserid={}, mediaId={}",
|
||||
callback.getExternalUserid(), callback.getMediaId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationMessage(KefuCallback callback) {
|
||||
logger.info("收到位置消息: externalUserid={}", callback.getExternalUserid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLinkMessage(KefuCallback callback) {
|
||||
logger.info("收到链接消息: externalUserid={}", callback.getExternalUserid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBusinessCardMessage(KefuCallback callback) {
|
||||
logger.info("收到名片消息: externalUserid={}", callback.getExternalUserid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMiniprogramMessage(KefuCallback callback) {
|
||||
logger.info("收到小程序消息: externalUserid={}", callback.getExternalUserid());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.xyzh.common.wechat.handler;
|
||||
|
||||
import org.xyzh.common.wechat.pojo.kefu.KefuCallback;
|
||||
|
||||
/**
|
||||
* @description 客服回调处理接口
|
||||
* @filename KefuCallbackHandler.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
public interface KefuCallbackHandler {
|
||||
|
||||
void onEnterSession(KefuCallback callback);
|
||||
|
||||
void onMsgSendFail(KefuCallback callback);
|
||||
|
||||
void onUserRecallMsg(KefuCallback callback);
|
||||
|
||||
void onServicerStatusChange(KefuCallback callback);
|
||||
|
||||
void onSessionStatusChange(KefuCallback callback);
|
||||
|
||||
void onTextMessage(KefuCallback callback);
|
||||
|
||||
void onImageMessage(KefuCallback callback);
|
||||
|
||||
void onVoiceMessage(KefuCallback callback);
|
||||
|
||||
void onVideoMessage(KefuCallback callback);
|
||||
|
||||
void onFileMessage(KefuCallback callback);
|
||||
|
||||
void onLocationMessage(KefuCallback callback);
|
||||
|
||||
void onLinkMessage(KefuCallback callback);
|
||||
|
||||
void onBusinessCardMessage(KefuCallback callback);
|
||||
|
||||
void onMiniprogramMessage(KefuCallback callback);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.xyzh.common.wechat.kefu.account;
|
||||
|
||||
/**
|
||||
* @description 客服账号管理 Handler 接口
|
||||
* 业务模块可实现此接口处理账号变更事件
|
||||
* @filename KefuAccountHandler.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
public interface KefuAccountHandler {
|
||||
|
||||
/**
|
||||
* 客服账号添加成功
|
||||
*/
|
||||
default void onAccountAdded(String openKfid, String name) {}
|
||||
|
||||
/**
|
||||
* 客服账号删除成功
|
||||
*/
|
||||
default void onAccountDeleted(String openKfid) {}
|
||||
|
||||
/**
|
||||
* 客服账号修改成功
|
||||
*/
|
||||
default void onAccountUpdated(String openKfid, String name) {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package org.xyzh.common.wechat.kefu.account;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.xyzh.common.wechat.kefu.core.KefuAccessTokenManager;
|
||||
import org.xyzh.common.wechat.pojo.kefu.KefuAccount;
|
||||
import org.xyzh.common.wechat.pojo.kefu.WeChatResponse;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
/**
|
||||
* @description 客服账号管理服务
|
||||
* - 添加客服账号
|
||||
* - 删除客服账号
|
||||
* - 修改客服账号
|
||||
* - 获取客服账号列表
|
||||
* - 获取客服账号链接
|
||||
* @filename KefuAccountService.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Service
|
||||
public class KefuAccountService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(KefuAccountService.class);
|
||||
private static final String BASE_URL = "https://qyapi.weixin.qq.com/cgi-bin";
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
private final KefuAccessTokenManager tokenManager;
|
||||
private KefuAccountHandler handler;
|
||||
|
||||
public KefuAccountService(KefuAccessTokenManager tokenManager) {
|
||||
this.tokenManager = tokenManager;
|
||||
}
|
||||
|
||||
public void setHandler(KefuAccountHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加客服账号
|
||||
*/
|
||||
public String addAccount(String name, String mediaId) {
|
||||
String accessToken = tokenManager.getAccessToken();
|
||||
String url = BASE_URL + "/kf/account/add?access_token=" + accessToken;
|
||||
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("name", name);
|
||||
if (mediaId != null) {
|
||||
body.put("media_id", mediaId);
|
||||
}
|
||||
|
||||
String response = postJson(url, body.toJSONString());
|
||||
JSONObject result = JSON.parseObject(response);
|
||||
|
||||
if (result.getIntValue("errcode") == 0) {
|
||||
String openKfid = result.getString("open_kfid");
|
||||
logger.info("添加客服账号成功: name={}, openKfid={}", name, openKfid);
|
||||
if (handler != null) {
|
||||
handler.onAccountAdded(openKfid, name);
|
||||
}
|
||||
return openKfid;
|
||||
}
|
||||
|
||||
logger.error("添加客服账号失败: {}", response);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除客服账号
|
||||
*/
|
||||
public boolean deleteAccount(String openKfid) {
|
||||
String accessToken = tokenManager.getAccessToken();
|
||||
String url = BASE_URL + "/kf/account/del?access_token=" + accessToken;
|
||||
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("open_kfid", openKfid);
|
||||
|
||||
String response = postJson(url, body.toJSONString());
|
||||
JSONObject result = JSON.parseObject(response);
|
||||
|
||||
boolean success = result.getIntValue("errcode") == 0;
|
||||
if (success) {
|
||||
logger.info("删除客服账号成功: openKfid={}", openKfid);
|
||||
if (handler != null) {
|
||||
handler.onAccountDeleted(openKfid);
|
||||
}
|
||||
} else {
|
||||
logger.error("删除客服账号失败: {}", response);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改客服账号
|
||||
*/
|
||||
public boolean updateAccount(String openKfid, String name, String mediaId) {
|
||||
String accessToken = tokenManager.getAccessToken();
|
||||
String url = BASE_URL + "/kf/account/update?access_token=" + accessToken;
|
||||
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("open_kfid", openKfid);
|
||||
if (name != null) {
|
||||
body.put("name", name);
|
||||
}
|
||||
if (mediaId != null) {
|
||||
body.put("media_id", mediaId);
|
||||
}
|
||||
|
||||
String response = postJson(url, body.toJSONString());
|
||||
JSONObject result = JSON.parseObject(response);
|
||||
|
||||
boolean success = result.getIntValue("errcode") == 0;
|
||||
if (success) {
|
||||
logger.info("修改客服账号成功: openKfid={}", openKfid);
|
||||
if (handler != null) {
|
||||
handler.onAccountUpdated(openKfid, name);
|
||||
}
|
||||
} else {
|
||||
logger.error("修改客服账号失败: {}", response);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客服账号列表
|
||||
*/
|
||||
public List<KefuAccount> getAccountList() {
|
||||
String accessToken = tokenManager.getAccessToken();
|
||||
String url = BASE_URL + "/kf/account/list?access_token=" + accessToken;
|
||||
|
||||
String response = restTemplate.getForObject(url, String.class);
|
||||
JSONObject result = JSON.parseObject(response);
|
||||
|
||||
if (result.getIntValue("errcode") == 0) {
|
||||
return result.getList("account_list", KefuAccount.class);
|
||||
}
|
||||
|
||||
logger.error("获取客服账号列表失败: {}", response);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客服账号链接
|
||||
*/
|
||||
public String getAccountLink(String openKfid, String scene) {
|
||||
String accessToken = tokenManager.getAccessToken();
|
||||
String url = BASE_URL + "/kf/add_contact_way?access_token=" + accessToken;
|
||||
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("open_kfid", openKfid);
|
||||
if (scene != null) {
|
||||
body.put("scene", scene);
|
||||
}
|
||||
|
||||
String response = postJson(url, body.toJSONString());
|
||||
JSONObject result = JSON.parseObject(response);
|
||||
|
||||
if (result.getIntValue("errcode") == 0) {
|
||||
return result.getString("url");
|
||||
}
|
||||
|
||||
logger.error("获取客服账号链接失败: {}", response);
|
||||
return null;
|
||||
}
|
||||
|
||||
private String postJson(String url, String json) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<String> entity = new HttpEntity<>(json, headers);
|
||||
return restTemplate.postForObject(url, entity, String.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package org.xyzh.common.wechat.kefu.core;
|
||||
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.xyzh.api.system.service.SysConfigService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* @description 微信客服 AccessToken 管理器
|
||||
* 负责获取和刷新 access_token
|
||||
* @filename KefuAccessTokenManager.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Component
|
||||
public class KefuAccessTokenManager {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(KefuAccessTokenManager.class);
|
||||
private static final String BASE_URL = "https://qyapi.weixin.qq.com/cgi-bin";
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "system", check = false)
|
||||
private SysConfigService sysConfigService;
|
||||
|
||||
private String corpId;
|
||||
private String secret;
|
||||
private String accessToken;
|
||||
private Long accessTokenExpireTime;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
public void loadConfig() {
|
||||
corpId = getConfigValue("wechat.kefu.corpId");
|
||||
secret = getConfigValue("wechat.kefu.secret");
|
||||
logger.info("微信客服配置加载完成: corpId={}", corpId);
|
||||
}
|
||||
|
||||
private String getConfigValue(String key) {
|
||||
if (sysConfigService == null) {
|
||||
logger.warn("SysConfigService 未注入,跳过配置加载: {}", key);
|
||||
return null;
|
||||
}
|
||||
ResultDomain<String> result = sysConfigService.getConfigValueByKey(key);
|
||||
if (result != null && result.getSuccess()) {
|
||||
return result.getData();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 access_token,如果过期自动刷新
|
||||
*/
|
||||
public String getAccessToken() {
|
||||
if (accessToken != null && accessTokenExpireTime != null
|
||||
&& System.currentTimeMillis() < accessTokenExpireTime) {
|
||||
return accessToken;
|
||||
}
|
||||
return refreshAccessToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新 access_token
|
||||
*/
|
||||
public String refreshAccessToken() {
|
||||
if (corpId == null || secret == null) {
|
||||
logger.error("微信配置不完整,无法获取access_token");
|
||||
return null;
|
||||
}
|
||||
|
||||
String url = BASE_URL + "/gettoken?corpid=" + corpId + "&corpsecret=" + secret;
|
||||
|
||||
try {
|
||||
String response = restTemplate.getForObject(url, String.class);
|
||||
JSONObject result = JSON.parseObject(response);
|
||||
|
||||
if (result.getIntValue("errcode") == 0) {
|
||||
accessToken = result.getString("access_token");
|
||||
int expiresIn = result.getIntValue("expires_in");
|
||||
accessTokenExpireTime = System.currentTimeMillis() + (expiresIn - 200) * 1000L;
|
||||
logger.info("获取access_token成功,有效期: {}秒", expiresIn);
|
||||
return accessToken;
|
||||
} else {
|
||||
logger.error("获取access_token失败: {}", response);
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("获取access_token异常", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getCorpId() {
|
||||
return corpId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.xyzh.common.wechat.kefu.info;
|
||||
|
||||
import org.xyzh.common.wechat.pojo.kefu.KefuCustomer;
|
||||
|
||||
/**
|
||||
* @description 基础信息获取 Handler 接口
|
||||
* 业务模块可实现此接口处理客户信息获取事件
|
||||
* @filename KefuInfoHandler.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
public interface KefuInfoHandler {
|
||||
|
||||
/**
|
||||
* 客户信息获取成功
|
||||
*/
|
||||
default void onCustomerInfoFetched(KefuCustomer customer) {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.xyzh.common.wechat.kefu.info;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.xyzh.common.wechat.kefu.core.KefuAccessTokenManager;
|
||||
import org.xyzh.common.wechat.pojo.kefu.KefuCustomer;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
/**
|
||||
* @description 其他基础信息获取服务
|
||||
* - 获取客户基础信息
|
||||
* - 获取企业状态信息
|
||||
* @filename KefuInfoService.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Service
|
||||
public class KefuInfoService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(KefuInfoService.class);
|
||||
private static final String BASE_URL = "https://qyapi.weixin.qq.com/cgi-bin";
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
private final KefuAccessTokenManager tokenManager;
|
||||
private KefuInfoHandler handler;
|
||||
|
||||
public KefuInfoService(KefuAccessTokenManager tokenManager) {
|
||||
this.tokenManager = tokenManager;
|
||||
}
|
||||
|
||||
public void setHandler(KefuInfoHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户基础信息
|
||||
*/
|
||||
public List<KefuCustomer> getCustomerInfo(List<String> externalUseridList) {
|
||||
String accessToken = tokenManager.getAccessToken();
|
||||
String url = BASE_URL + "/kf/customer/batchget?access_token=" + accessToken;
|
||||
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("external_userid_list", externalUseridList);
|
||||
|
||||
String response = postJson(url, body.toJSONString());
|
||||
JSONObject result = JSON.parseObject(response);
|
||||
|
||||
if (result.getIntValue("errcode") == 0) {
|
||||
List<KefuCustomer> customers = result.getList("customer_list", KefuCustomer.class);
|
||||
if (handler != null && customers != null) {
|
||||
for (KefuCustomer customer : customers) {
|
||||
handler.onCustomerInfoFetched(customer);
|
||||
}
|
||||
}
|
||||
return customers;
|
||||
}
|
||||
|
||||
logger.error("获取客户信息失败: {}", response);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取企业状态信息
|
||||
*/
|
||||
public JSONObject getCorpStatistic() {
|
||||
String accessToken = tokenManager.getAccessToken();
|
||||
String url = BASE_URL + "/kf/get_corp_statistic?access_token=" + accessToken;
|
||||
|
||||
JSONObject body = new JSONObject();
|
||||
// 可添加时间范围等参数
|
||||
|
||||
String response = postJson(url, body.toJSONString());
|
||||
JSONObject result = JSON.parseObject(response);
|
||||
|
||||
if (result.getIntValue("errcode") == 0) {
|
||||
return result.getJSONObject("statistic");
|
||||
}
|
||||
|
||||
logger.error("获取企业状态信息失败: {}", response);
|
||||
return null;
|
||||
}
|
||||
|
||||
private String postJson(String url, String json) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<String> entity = new HttpEntity<>(json, headers);
|
||||
return restTemplate.postForObject(url, entity, String.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.xyzh.common.wechat.kefu.message;
|
||||
|
||||
import org.xyzh.common.wechat.pojo.kefu.KefuSyncMsgResponse.KefuSyncMsg;
|
||||
|
||||
/**
|
||||
* @description 客服消息处理 Handler 接口
|
||||
* 业务模块实现此接口处理客服消息和事件
|
||||
* @filename KefuMessageHandler.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
public interface KefuMessageHandler {
|
||||
|
||||
/**
|
||||
* 用户进入会话事件
|
||||
* @param openKfid 客服账号ID
|
||||
* @param externalUserid 用户ID
|
||||
* @param scene 场景值
|
||||
* @param sceneParam 场景参数(可传递工单ID等)
|
||||
* @param welcomeCode 欢迎语code
|
||||
*/
|
||||
void onEnterSession(String openKfid, String externalUserid, String scene, String sceneParam, String welcomeCode);
|
||||
|
||||
/**
|
||||
* 收到文本消息
|
||||
*/
|
||||
void onTextMessage(String openKfid, String externalUserid, String msgid, String content, Long sendTime);
|
||||
|
||||
/**
|
||||
* 收到图片消息
|
||||
*/
|
||||
void onImageMessage(String openKfid, String externalUserid, String msgid, String mediaId, Long sendTime);
|
||||
|
||||
/**
|
||||
* 会话状态变更
|
||||
*/
|
||||
void onSessionStatusChange(String openKfid, String externalUserid, String changeType,
|
||||
String oldServicerUserid, String newServicerUserid);
|
||||
|
||||
/**
|
||||
* 消息发送失败
|
||||
*/
|
||||
void onMsgSendFail(String openKfid, String externalUserid, String failMsgid, String failType);
|
||||
|
||||
/**
|
||||
* 其他消息(语音、视频、文件等)
|
||||
*/
|
||||
default void onOtherMessage(KefuSyncMsg msg) {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package org.xyzh.common.wechat.kefu.message;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.xyzh.common.wechat.kefu.core.KefuAccessTokenManager;
|
||||
import org.xyzh.common.wechat.pojo.kefu.KefuSyncMsgResponse;
|
||||
import org.xyzh.common.wechat.pojo.kefu.KefuSyncMsgResponse.KefuSyncMsg;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
/**
|
||||
* @description 客服消息收发服务
|
||||
* - 接收消息和事件(同步消息)
|
||||
* - 发送消息
|
||||
* - 发送客服欢迎语
|
||||
* - 撤回消息
|
||||
* @filename KefuMessageService.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Service
|
||||
public class KefuMessageService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(KefuMessageService.class);
|
||||
private static final String BASE_URL = "https://qyapi.weixin.qq.com/cgi-bin";
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
private final KefuAccessTokenManager tokenManager;
|
||||
private KefuMessageHandler handler;
|
||||
|
||||
public KefuMessageService(KefuAccessTokenManager tokenManager) {
|
||||
this.tokenManager = tokenManager;
|
||||
}
|
||||
|
||||
public void setHandler(KefuMessageHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
// ========================= 接收消息和事件 =========================
|
||||
|
||||
/**
|
||||
* 同步消息(拉取新消息)
|
||||
*/
|
||||
public KefuSyncMsgResponse syncMessages(String cursor, String token, Integer limit) {
|
||||
String accessToken = tokenManager.getAccessToken();
|
||||
String url = BASE_URL + "/kf/sync_msg?access_token=" + accessToken;
|
||||
|
||||
JSONObject body = new JSONObject();
|
||||
if (cursor != null && !cursor.isEmpty()) {
|
||||
body.put("cursor", cursor);
|
||||
}
|
||||
if (token != null && !token.isEmpty()) {
|
||||
body.put("token", token);
|
||||
}
|
||||
if (limit != null) {
|
||||
body.put("limit", limit);
|
||||
}
|
||||
|
||||
String response = postJson(url, body.toJSONString());
|
||||
return JSON.parseObject(response, KefuSyncMsgResponse.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理同步消息(模板方法)
|
||||
*/
|
||||
public void processMessages(KefuSyncMsgResponse response) {
|
||||
if (response == null || !response.isSuccess()) {
|
||||
logger.error("消息同步响应异常: {}", response != null ? response.getErrmsg() : "null");
|
||||
return;
|
||||
}
|
||||
|
||||
List<KefuSyncMsg> msgList = response.getMsgList();
|
||||
if (msgList == null || msgList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("开始处理 {} 条消息", msgList.size());
|
||||
|
||||
for (KefuSyncMsg msg : msgList) {
|
||||
try {
|
||||
processMessage(msg);
|
||||
} catch (Exception e) {
|
||||
logger.error("处理消息异常: msgid={}", msg.getMsgid(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void processMessage(KefuSyncMsg msg) {
|
||||
if (handler == null) {
|
||||
logger.warn("未设置消息处理器");
|
||||
return;
|
||||
}
|
||||
|
||||
String msgtype = msg.getMsgtype();
|
||||
|
||||
if ("event".equals(msgtype)) {
|
||||
processEvent(msg);
|
||||
} else if ("text".equals(msgtype)) {
|
||||
String content = msg.getText() != null ? msg.getText().getContent() : "";
|
||||
handler.onTextMessage(msg.getOpenKfid(), msg.getExternalUserid(), msg.getMsgid(), content, msg.getSendTime());
|
||||
} else if ("image".equals(msgtype)) {
|
||||
String mediaId = msg.getImage() != null ? msg.getImage().getMediaId() : "";
|
||||
handler.onImageMessage(msg.getOpenKfid(), msg.getExternalUserid(), msg.getMsgid(), mediaId, msg.getSendTime());
|
||||
} else {
|
||||
handler.onOtherMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
protected void processEvent(KefuSyncMsg msg) {
|
||||
if (msg.getEvent() == null || handler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String eventType = msg.getEvent().getEventType();
|
||||
String openKfid = msg.getEvent().getOpenKfid();
|
||||
String externalUserid = msg.getEvent().getExternalUserid();
|
||||
|
||||
switch (eventType) {
|
||||
case "enter_session":
|
||||
handler.onEnterSession(openKfid, externalUserid, msg.getEvent().getScene(),
|
||||
msg.getEvent().getSceneParam(), msg.getEvent().getWelcomeCode());
|
||||
break;
|
||||
case "msg_send_fail":
|
||||
handler.onMsgSendFail(openKfid, externalUserid, msg.getEvent().getFailMsgid(), msg.getEvent().getFailType());
|
||||
break;
|
||||
case "session_status_change":
|
||||
handler.onSessionStatusChange(openKfid, externalUserid, msg.getEvent().getChangeType(),
|
||||
msg.getEvent().getOldServicerUserid(), msg.getEvent().getNewServicerUserid());
|
||||
break;
|
||||
default:
|
||||
logger.debug("未处理的事件类型: {}", eventType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ========================= 发送消息 =========================
|
||||
|
||||
/**
|
||||
* 发送文本消息
|
||||
*/
|
||||
public String sendTextMessage(String touser, String openKfid, String content) {
|
||||
String accessToken = tokenManager.getAccessToken();
|
||||
String url = BASE_URL + "/kf/send_msg?access_token=" + accessToken;
|
||||
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("touser", touser);
|
||||
body.put("open_kfid", openKfid);
|
||||
body.put("msgtype", "text");
|
||||
|
||||
JSONObject text = new JSONObject();
|
||||
text.put("content", content);
|
||||
body.put("text", text);
|
||||
|
||||
String response = postJson(url, body.toJSONString());
|
||||
JSONObject result = JSON.parseObject(response);
|
||||
|
||||
if (result.getIntValue("errcode") == 0) {
|
||||
return result.getString("msgid");
|
||||
}
|
||||
|
||||
logger.error("发送消息失败: {}", response);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送客服欢迎语
|
||||
*/
|
||||
public String sendWelcomeMessage(String code, String content) {
|
||||
String accessToken = tokenManager.getAccessToken();
|
||||
String url = BASE_URL + "/kf/send_msg_on_event?access_token=" + accessToken;
|
||||
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("code", code);
|
||||
body.put("msgtype", "text");
|
||||
|
||||
JSONObject text = new JSONObject();
|
||||
text.put("content", content);
|
||||
body.put("text", text);
|
||||
|
||||
String response = postJson(url, body.toJSONString());
|
||||
JSONObject result = JSON.parseObject(response);
|
||||
|
||||
if (result.getIntValue("errcode") == 0) {
|
||||
return result.getString("msgid");
|
||||
}
|
||||
|
||||
logger.error("发送欢迎语失败: {}", response);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回消息
|
||||
*/
|
||||
public boolean recallMessage(String msgid) {
|
||||
String accessToken = tokenManager.getAccessToken();
|
||||
String url = BASE_URL + "/kf/recall_msg?access_token=" + accessToken;
|
||||
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("msgid", msgid);
|
||||
|
||||
String response = postJson(url, body.toJSONString());
|
||||
JSONObject result = JSON.parseObject(response);
|
||||
|
||||
return result.getIntValue("errcode") == 0;
|
||||
}
|
||||
|
||||
private String postJson(String url, String json) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<String> entity = new HttpEntity<>(json, headers);
|
||||
return restTemplate.postForObject(url, entity, String.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.xyzh.common.wechat.pojo.kefu;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @description access_token响应
|
||||
* @filename AccessTokenResponse.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class AccessTokenResponse extends WeChatResponse {
|
||||
|
||||
private String accessToken;
|
||||
|
||||
private Integer expiresIn;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.xyzh.common.wechat.pojo.kefu;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 客服账号
|
||||
* @filename KefuAccount.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Data
|
||||
public class KefuAccount {
|
||||
|
||||
private String openKfid;
|
||||
|
||||
private String name;
|
||||
|
||||
private String avatar;
|
||||
|
||||
private String managePrivilege;
|
||||
|
||||
private List<String> receptionistIdList;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.xyzh.common.wechat.pojo.kefu;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @description 添加客服账号响应
|
||||
* @filename KefuAccountAddResponse.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class KefuAccountAddResponse extends WeChatResponse {
|
||||
|
||||
private String openKfid;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.xyzh.common.wechat.pojo.kefu;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 客服账号列表响应
|
||||
* @filename KefuAccountListResponse.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class KefuAccountListResponse extends WeChatResponse {
|
||||
|
||||
private List<KefuAccount> accountList;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.xyzh.common.wechat.pojo.kefu;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 客服回调消息
|
||||
* @filename KefuCallback.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Data
|
||||
public class KefuCallback {
|
||||
|
||||
private String toUserName;
|
||||
|
||||
private String createTime;
|
||||
|
||||
private String msgType;
|
||||
|
||||
private String event;
|
||||
|
||||
private String token;
|
||||
|
||||
private String openKfid;
|
||||
|
||||
private String externalUserid;
|
||||
|
||||
private String scene;
|
||||
|
||||
private String sceneParam;
|
||||
|
||||
private String welcomeCode;
|
||||
|
||||
private String failMsgid;
|
||||
|
||||
private String failType;
|
||||
|
||||
private String msgid;
|
||||
|
||||
private String content;
|
||||
|
||||
private String picUrl;
|
||||
|
||||
private String mediaId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.xyzh.common.wechat.pojo.kefu;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 客服客户信息
|
||||
* @filename KefuCustomer.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Data
|
||||
public class KefuCustomer {
|
||||
|
||||
private String externalUserid;
|
||||
|
||||
private String nickname;
|
||||
|
||||
private String avatar;
|
||||
|
||||
private Integer gender;
|
||||
|
||||
private String unionid;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.xyzh.common.wechat.pojo.kefu;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 获取客户信息响应
|
||||
* @filename KefuCustomerResponse.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class KefuCustomerResponse extends WeChatResponse {
|
||||
|
||||
private List<KefuCustomer> customerList;
|
||||
|
||||
private String invalidExternalUserid;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.xyzh.common.wechat.pojo.kefu;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 客服事件
|
||||
* @filename KefuEvent.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Data
|
||||
public class KefuEvent {
|
||||
|
||||
private String token;
|
||||
|
||||
private String openKfid;
|
||||
|
||||
private String externalUserid;
|
||||
|
||||
private String scene;
|
||||
|
||||
private String sceneParam;
|
||||
|
||||
private String welcomeCode;
|
||||
|
||||
private Long wechatChannels;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.xyzh.common.wechat.pojo.kefu;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @description 获取客服链接响应
|
||||
* @filename KefuLinkResponse.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class KefuLinkResponse extends WeChatResponse {
|
||||
|
||||
private String url;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.xyzh.common.wechat.pojo.kefu;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 客服消息
|
||||
* @filename KefuMessage.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Data
|
||||
public class KefuMessage {
|
||||
|
||||
private String touser;
|
||||
|
||||
private String openKfid;
|
||||
|
||||
private String msgid;
|
||||
|
||||
private String msgtype;
|
||||
|
||||
private TextContent text;
|
||||
|
||||
private ImageContent image;
|
||||
|
||||
private LinkContent link;
|
||||
|
||||
private MiniprogramContent miniprogram;
|
||||
|
||||
@Data
|
||||
public static class TextContent {
|
||||
private String content;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ImageContent {
|
||||
private String mediaId;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class LinkContent {
|
||||
private String title;
|
||||
private String desc;
|
||||
private String url;
|
||||
private String thumbMediaId;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class MiniprogramContent {
|
||||
private String appid;
|
||||
private String pagepath;
|
||||
private String title;
|
||||
private String thumbMediaId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.xyzh.common.wechat.pojo.kefu;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @description 发送消息响应
|
||||
* @filename KefuSendMsgResponse.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class KefuSendMsgResponse extends WeChatResponse {
|
||||
|
||||
private String msgid;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package org.xyzh.common.wechat.pojo.kefu;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @description 同步消息响应
|
||||
* @filename KefuSyncMsgResponse.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class KefuSyncMsgResponse extends WeChatResponse {
|
||||
|
||||
private String nextCursor;
|
||||
|
||||
private Integer hasMore;
|
||||
|
||||
private List<KefuSyncMsg> msgList;
|
||||
|
||||
@Data
|
||||
public static class KefuSyncMsg {
|
||||
|
||||
private String msgid;
|
||||
|
||||
private String openKfid;
|
||||
|
||||
private String externalUserid;
|
||||
|
||||
private Long sendTime;
|
||||
|
||||
private Integer origin;
|
||||
|
||||
private String servicerUserid;
|
||||
|
||||
private String msgtype;
|
||||
|
||||
private TextContent text;
|
||||
|
||||
private ImageContent image;
|
||||
|
||||
private VoiceContent voice;
|
||||
|
||||
private VideoContent video;
|
||||
|
||||
private FileContent file;
|
||||
|
||||
private LocationContent location;
|
||||
|
||||
private LinkContent link;
|
||||
|
||||
private BusinessCardContent businessCard;
|
||||
|
||||
private MiniprogramContent miniprogram;
|
||||
|
||||
private EventContent event;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class TextContent {
|
||||
private String content;
|
||||
private String menuId;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ImageContent {
|
||||
private String mediaId;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class VoiceContent {
|
||||
private String mediaId;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class VideoContent {
|
||||
private String mediaId;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class FileContent {
|
||||
private String mediaId;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class LocationContent {
|
||||
private String latitude;
|
||||
private String longitude;
|
||||
private String name;
|
||||
private String address;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class LinkContent {
|
||||
private String title;
|
||||
private String desc;
|
||||
private String url;
|
||||
private String picUrl;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class BusinessCardContent {
|
||||
private String userid;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class MiniprogramContent {
|
||||
private String title;
|
||||
private String appid;
|
||||
private String pagepath;
|
||||
private String thumbMediaId;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class EventContent {
|
||||
private String eventType;
|
||||
private String openKfid;
|
||||
private String externalUserid;
|
||||
private String scene;
|
||||
private String sceneParam;
|
||||
private String welcomeCode;
|
||||
private String wechatChannels;
|
||||
private String failMsgid;
|
||||
private String failType;
|
||||
private String servicerUserid;
|
||||
private Integer status;
|
||||
private String changeType;
|
||||
private String oldServicerUserid;
|
||||
private String newServicerUserid;
|
||||
private String msgCode;
|
||||
private String recallMsgid;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.xyzh.common.wechat.pojo.kefu;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @description 素材上传响应
|
||||
* @filename MediaUploadResponse.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class MediaUploadResponse extends WeChatResponse {
|
||||
|
||||
private String type;
|
||||
|
||||
private String mediaId;
|
||||
|
||||
private Long createdAt;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.xyzh.common.wechat.pojo.kefu;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @description 微信API通用响应
|
||||
* @filename WeChatResponse.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-19
|
||||
*/
|
||||
@Data
|
||||
public class WeChatResponse {
|
||||
|
||||
private Integer errcode;
|
||||
|
||||
private String errmsg;
|
||||
|
||||
public boolean isSuccess() {
|
||||
return errcode == null || errcode == 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,7 @@
|
||||
<module>common-jdbc</module>
|
||||
<module>common-all</module>
|
||||
<module>common-exception</module>
|
||||
<module>common-wechat</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
@@ -71,6 +72,11 @@
|
||||
<artifactId>common-jdbc</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xyzh.common</groupId>
|
||||
<artifactId>common-wechat</artifactId>
|
||||
<version>${urban-lifeline.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
||||
Reference in New Issue
Block a user