feat:聊天第三方接口对接

This commit is contained in:
dzc committed 2025-11-25 16:22:57 +08:00
1 parent f23dc0412c
commit 6a8aed3c96
11 files changed
+445 -5

No files matched your search

@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.member.client.service;
import cn.iocoder.yudao.module.member.client.vo.*;
import javax.validation.Valid;
public interface ChatApi {
/**
* 聊天用户登录
*/
ChatLoginRespVo login(@Valid ChatLoginReqVO dto);
/**
* 创建或者更新聊天用户
*/
boolean createOrUpdate(@Valid ChatCreateOrUpdateUserReqVo dto);
/**
* 根据userId查询用户详情
*/
ChatGetUserByUserIdRespVo getUserByUserId(@Valid ChatGetUserByUserIdReqVo dto);
}
@@ -0,0 +1,153 @@
package cn.iocoder.yudao.module.member.client.service;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.module.member.client.tool.ChatApiTool;
import cn.iocoder.yudao.module.member.client.vo.*;
import cn.iocoder.yudao.module.member.client.vo.base.ChatBaseRespVo;
import cn.iocoder.yudao.module.member.framework.web.config.ChatProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.Instant;
import java.util.*;
@Slf4j
@Service
public class ChatApiImpl implements ChatApi {
@Resource
private ChatProperties chatProperties;
/**
* 聊天用户登录
*/
@Override
public ChatLoginRespVo login(ChatLoginReqVO dto) {
ChatApiTool.API api = setApiConfig(ChatApiTool.USER_LOGIN);
try {
String body = JSONUtil.toJsonStr(dto);
String res = HttpRequest.post(api.getFullPath()).header(apiCountersign(body)).body(body).execute().body();
log.debug("聊天用户登录,res:{}", res);
if (ObjectUtil.isEmpty(res)) {
log.error("聊天用户登录,res为空");
return null;
}
ChatBaseRespVo baseRes = JSONUtil.toBean(res, ChatBaseRespVo.class);
if (ObjectUtil.isEmpty(baseRes) || !baseRes.isSuccess()) {
log.error("聊天用户登录,返回错误");
return null;
}
ChatLoginRespVo vo = JSONUtil.toBean(baseRes.getData(), ChatLoginRespVo.class);
if (ObjectUtil.isEmpty(vo)) {
log.error("聊天用户登录,返回json解析错误");
return null;
}
return vo;
} catch (Exception e) {
e.printStackTrace();
log.error("聊天用户登录,意外的错误", e);
return null;
}
}
/**
* 创建或者更新聊天用户
*/
@Override
public boolean createOrUpdate(ChatCreateOrUpdateUserReqVo dto) {
ChatApiTool.API api = setApiConfig(ChatApiTool.USER_SAVE_OR_UPDATE);
try {
String body = JSONUtil.toJsonStr(dto);
String res = HttpRequest.post(api.getFullPath()).header(apiCountersign(body)).body(body).execute().body();
log.debug("创建或者更新聊天用户,res:{}", res);
if (ObjectUtil.isEmpty(res)) {
log.error("创建或者更新聊天用户,res为空");
return false;
}
ChatBaseRespVo baseRes = JSONUtil.toBean(res, ChatBaseRespVo.class);
if (ObjectUtil.isEmpty(baseRes) || !baseRes.isSuccess()) {
log.error("创建或者更新聊天用户,返回错误");
return false;
}
return true;
} catch (Exception e) {
e.printStackTrace();
log.error("创建或者更新聊天用户,意外的错误", e);
return false;
}
}
/**
* 根据userId查询用户详情
*/
@Override
public ChatGetUserByUserIdRespVo getUserByUserId(ChatGetUserByUserIdReqVo dto) {
ChatApiTool.API api = setApiConfig(ChatApiTool.USER_GET);
try {
String body = JSONUtil.toJsonStr(dto);
String res = HttpRequest.post(api.getFullPath()).header(apiCountersign(body)).body(body).execute().body();
log.debug("根据userId查询用户详情,res:{}", res);
if (ObjectUtil.isEmpty(res)) {
log.error("根据userId查询用户详情,res为空");
return null;
}
ChatBaseRespVo baseRes = JSONUtil.toBean(res, ChatBaseRespVo.class);
if (ObjectUtil.isEmpty(baseRes) || !baseRes.isSuccess()) {
log.error("根据userId查询用户详情,返回错误");
return null;
}
ChatGetUserByUserIdRespVo vo = JSONUtil.toBean(baseRes.getData(), ChatGetUserByUserIdRespVo.class);
if (ObjectUtil.isEmpty(vo)) {
log.error("根据userId查询用户详情,返回json解析错误");
return null;
}
return vo;
} catch (Exception e) {
e.printStackTrace();
log.error("根据userId查询用户详情,意外的错误", e);
return null;
}
}
private ChatApiTool.API setApiConfig(ChatApiTool.API api) {
if (ObjectUtil.isNotEmpty(api)) {
api.setHost(chatProperties.getBaseUrl());
}
api.init();
return api;
}
/**
* 接口加签
*/
private Map<String, List<String>> apiCountersign(String body) {
String appKey = chatProperties.getAppKey();
String appSecret = chatProperties.getAppSecret();
String bizType = "10";
String timestamp = String.valueOf(Instant.now().getEpochSecond());
String signStr = String.format("%s%s%s%s%s", appKey, appSecret, bizType, timestamp, body);
String successSignMd5 = DigestUtils.md5Hex(signStr);
Map<String, List<String>> header = new LinkedHashMap<>();
header.put("APP-KEY", new ArrayList<String>() {{
add(appKey);
}});
header.put("BIZ-TYPE", new ArrayList<String>() {{
add(bizType);
}});
header.put("TIMESTAMP", new ArrayList<String>() {{
add(timestamp);
}});
header.put("SIGN", new ArrayList<String>() {{
add(successSignMd5);
}});
return header;
}
}
@@ -0,0 +1,67 @@
package cn.iocoder.yudao.module.member.client.tool;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpMethod;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* 聊天接口工具类
*/
public class ChatApiTool {
public static final API USER_LOGIN = API.builder().summary("用户登录").url("/third/user/login").method(HttpMethod.POST).build();
public static final API USER_GET = API.builder().summary("根据userId查询用户详情").url("/third/user/getUserByUserId").method(HttpMethod.POST).build();
public static final API USER_SAVE_OR_UPDATE = API.builder().summary("创建或者更新用户").url("/third/user/createOrUpdate").method(HttpMethod.POST).build();
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
static public class API {
private String summary;
private String host;
private String url;
private String fullPath;
private HttpMethod method;
private Map<String, Object> paramsMap;
public String getParamsString() throws UnsupportedEncodingException {
if (ObjectUtil.isEmpty(this.paramsMap)) return "";
StringBuilder query = new StringBuilder();
for (Map.Entry<String, Object> entry : paramsMap.entrySet()) {
if (ObjectUtil.isNotEmpty(entry.getValue())) {
if (ObjectUtil.isNotEmpty(query)) query.append("&");
query.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name())).append("=").append(URLEncoder.encode(String.valueOf(entry.getValue()), StandardCharsets.UTF_8.name()));
}
}
return "?" + query;
}
public API setParamsMap(Map<String, Object> paramsMap) {
this.paramsMap = paramsMap;
return this;
}
public String init() {
String apiPath = host + url;
fullPath = apiPath;
return apiPath;
}
}
}
@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.member.client.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
@Data
@Schema(description = "chat用户注册req")
public class ChatCreateOrUpdateUserReqVo {
@NotEmpty(message = "用户id不可为空")
@Schema(description = "用户id")
private Long userId;
@Length(max = 20, message = "用户名不能大于20字符")
@NotEmpty(message = "用户名不可为空")
@Schema(description = "用户名")
private String userName;
@NotEmpty(message = "用户手机号不可为空")
@Schema(description = "用户手机号")
private String phone;
@Length(min = 5, max = 20, message = "密码长度必须在5-20个字符之间")
@NotEmpty(message = "用户密码不可为空")
@Schema(description = "用户密码")
private String password;
@Length(max = 20, message = "昵称不能大于20字符")
@Schema(description = "用户昵称")
private String nickName;
@Schema(description = "用户头像")
private String avatar;
}
@@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.member.client.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
@Schema(description = "chat根据userId查询用户详情")
public class ChatGetUserByUserIdReqVo {
@NotNull(message = "用户id不能为空")
@Schema(description = "用户id")
private Long userId;
}
@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.member.client.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Data
@Schema(description = "chat根据userId查询用户详情resp")
public class ChatGetUserByUserIdRespVo {
@NotNull(message = "用户id不能为空")
@Schema(description = "id")
private Long id;
@NotEmpty(message = "用户名不能为空")
@Length(max = 20, message = "用户名不能大于20字符")
@Schema(description = "用户名")
private String userName;
@NotEmpty(message = "用户昵称不能为空")
@Length(max = 20, message = "昵称不能大于20字符")
@Schema(description = "用户昵称")
private String nickName;
@Schema(description = "性别")
private Integer sex;
@Schema(description = "用户类型 1:普通用户 2:审核账户")
private Integer type;
@Length(max = 128, message = "个性签名不能大于128个字符")
@Schema(description = "个性签名")
private String signature;
@Schema(description = "头像")
private String headImage;
@Schema(description = "头像缩略图")
private String headImageThumb;
@Schema(description = "是否在线")
private Boolean online;
@Schema(description = "账号是否被封禁")
private Boolean isBanned;
@Schema(description = "被封禁原因")
private String reason;
}
@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.member.client.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Data
@Schema(description = "chat用户登录DTO")
public class ChatLoginReqVO {
@Max(value = 2, message = "登录终端类型取值范围:0,2")
@Min(value = 0, message = "登录终端类型取值范围:0,2")
@NotNull(message = "登录终端类型不可为空")
@Schema(description = "登录终端 0:web 1:app 2:pc")
private Integer terminal;
@NotEmpty(message = "用户id不存在")
@Schema(description = "用户id")
private Long userId;
}
@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.member.client.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "chat用户登录resp")
public class ChatLoginRespVo {
@Schema(description = "每次请求都必须在header中携带accessToken")
private String accessToken;
@Schema(description = "accessToken过期时间(秒)")
private Integer accessTokenExpiresIn;
@Schema(description = "accessToken过期后,通过refreshToken换取新的token")
private String refreshToken;
@Schema(description = "refreshToken过期时间(秒)")
private Integer refreshTokenExpiresIn;
}
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.member.client.vo.base;
import cn.hutool.core.util.ObjUtil;
import lombok.Data;
@Data
public class ChatBaseRespVo {
private Integer code;
private String message;
private String data;
public boolean isSuccess() {
return ObjUtil.equal(code, 200);
}
}
@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.member.framework.web.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties("chat")
@Data
public class ChatProperties {
private String baseUrl;
private String appKey;
private String appSecret;
}
@@ -4,10 +4,10 @@ server:
--- #################### 数据库相关配置 ####################
spring:
# autoconfigure:
# exclude:
# - org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant,手动创建
# - org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus,手动创建
# autoconfigure:
# exclude:
# - org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant,手动创建
# - org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus,手动创建
# 数据源配置项
autoconfigure:
exclude:
@@ -225,4 +225,9 @@ iot:
# 插件配置
pf4j:
pluginsDir: ${user.home}/plugins # 插件目录
pluginsDir: ${user.home}/plugins # 插件目录
chat:
base-url: http://127.0.0.1:8888
api-key: 4H2HkBLygMsxaQKO
app-secret: JIizm84av3TK5xaQdsng7X5mqLPjbeQ0