diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApi.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApi.java new file mode 100644 index 0000000..3b58773 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApi.java @@ -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); + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApiImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApiImpl.java new file mode 100644 index 0000000..8254807 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApiImpl.java @@ -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> 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> header = new LinkedHashMap<>(); + header.put("APP-KEY", new ArrayList() {{ + add(appKey); + }}); + header.put("BIZ-TYPE", new ArrayList() {{ + add(bizType); + }}); + header.put("TIMESTAMP", new ArrayList() {{ + add(timestamp); + }}); + header.put("SIGN", new ArrayList() {{ + add(successSignMd5); + }}); + return header; + } + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/tool/ChatApiTool.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/tool/ChatApiTool.java new file mode 100644 index 0000000..163672d --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/tool/ChatApiTool.java @@ -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 paramsMap; + + public String getParamsString() throws UnsupportedEncodingException { + if (ObjectUtil.isEmpty(this.paramsMap)) return ""; + StringBuilder query = new StringBuilder(); + for (Map.Entry 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 paramsMap) { + this.paramsMap = paramsMap; + return this; + } + + public String init() { + String apiPath = host + url; + fullPath = apiPath; + return apiPath; + } + + } + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/ChatCreateOrUpdateUserReqVo.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/ChatCreateOrUpdateUserReqVo.java new file mode 100644 index 0000000..e104db0 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/ChatCreateOrUpdateUserReqVo.java @@ -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; + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/ChatGetUserByUserIdReqVo.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/ChatGetUserByUserIdReqVo.java new file mode 100644 index 0000000..a2a22b6 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/ChatGetUserByUserIdReqVo.java @@ -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; + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/ChatGetUserByUserIdRespVo.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/ChatGetUserByUserIdRespVo.java new file mode 100644 index 0000000..4d19640 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/ChatGetUserByUserIdRespVo.java @@ -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; + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/ChatLoginReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/ChatLoginReqVO.java new file mode 100644 index 0000000..9231a41 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/ChatLoginReqVO.java @@ -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; + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/ChatLoginRespVo.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/ChatLoginRespVo.java new file mode 100644 index 0000000..f9bb145 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/ChatLoginRespVo.java @@ -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; + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/base/ChatBaseRespVo.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/base/ChatBaseRespVo.java new file mode 100644 index 0000000..6eed57e --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/base/ChatBaseRespVo.java @@ -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); + } + +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/web/config/ChatProperties.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/web/config/ChatProperties.java new file mode 100644 index 0000000..0956903 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/web/config/ChatProperties.java @@ -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; + +} diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml index d341920..dd4cb34 100644 --- a/yudao-server/src/main/resources/application-dev.yaml +++ b/yudao-server/src/main/resources/application-dev.yaml @@ -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 # 插件目录 \ No newline at end of file + pluginsDir: ${user.home}/plugins # 插件目录 + +chat: + base-url: http://127.0.0.1:8888 + api-key: 4H2HkBLygMsxaQKO + app-secret: JIizm84av3TK5xaQdsng7X5mqLPjbeQ0 \ No newline at end of file