From eb1bbe78a1f9c4f28d99b1d5892ea7e5ced6e06e Mon Sep 17 00:00:00 2001 From: Wonder Date: Sat, 25 Oct 2025 19:23:47 +0800 Subject: [PATCH] =?UTF-8?q?=20=E2=9C=A8Feat:=20=E5=AE=8C=E6=88=90=20Lark?= =?UTF-8?q?=20=E7=AE=80=E5=8D=95=E4=BF=A1=E6=81=AF=E5=8F=91=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 12 ++++ .../stardewvalley/constant/GuideConstant.java | 13 ++++ .../stardewvalley/constant/LarkConstant.java | 8 +++ .../controller/ChatController.java | 6 +- .../controller/HealthController.java | 6 +- .../controller/LarkController.java | 20 +++++++ .../stardewvalley/entity/BaseResponse.java | 33 ++++++++++ .../exception/BusinessException.java | 22 +++++++ .../stardewvalley/exception/ErrorCode.java | 33 ++++++++++ .../stardewvalley/exception/ThrowUtils.java | 19 ++++++ .../stardewvalley/lark/LarkUtil.java | 60 +++++++++++++++++++ .../stardewvalley/util/ResultUtil.java | 24 ++++++++ .../stardewvalley/lark/LarkUtilTest.java | 18 ++++++ 13 files changed, 270 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/wonderland/stardewvalley/constant/GuideConstant.java create mode 100644 src/main/java/com/wonderland/stardewvalley/constant/LarkConstant.java create mode 100644 src/main/java/com/wonderland/stardewvalley/controller/LarkController.java create mode 100644 src/main/java/com/wonderland/stardewvalley/entity/BaseResponse.java create mode 100644 src/main/java/com/wonderland/stardewvalley/exception/BusinessException.java create mode 100644 src/main/java/com/wonderland/stardewvalley/exception/ErrorCode.java create mode 100644 src/main/java/com/wonderland/stardewvalley/exception/ThrowUtils.java create mode 100644 src/main/java/com/wonderland/stardewvalley/lark/LarkUtil.java create mode 100644 src/main/java/com/wonderland/stardewvalley/util/ResultUtil.java create mode 100644 src/test/java/com/wonderland/stardewvalley/lark/LarkUtilTest.java diff --git a/pom.xml b/pom.xml index c178eb9..8bb0832 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,12 @@ spring-ai-starter-model-openai + + com.larksuite.oapi + oapi-sdk + 2.4.24 + + com.github.xiaoymin knife4j-openapi3-jakarta-spring-boot-starter @@ -52,6 +58,12 @@ lombok + + cn.hutool + hutool-all + 5.8.9 + + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/com/wonderland/stardewvalley/constant/GuideConstant.java b/src/main/java/com/wonderland/stardewvalley/constant/GuideConstant.java new file mode 100644 index 0000000..b8ad9ff --- /dev/null +++ b/src/main/java/com/wonderland/stardewvalley/constant/GuideConstant.java @@ -0,0 +1,13 @@ +package com.wonderland.stardewvalley.constant; + +public class GuideConstant { + + final public static String GUIDE = "\uD83C\uDF31 在这个名为“如果人生是星露谷”的世界中,玩家扮演的是一位现实世界中的普通人 ,在某种“精神投射”或“虚拟现实”中,进入了一个与《星露谷物语》相似的模拟世界。这个世界并非简单的幻想乡,而是对现实生活的重构与映射 ,玩家通过在现实世界中“种田、打工、探索、运动”等行为,影响游戏中的生活节奏与心理状态。\n" + + "\n" + + "\uD83C\uDFAE 游戏说明:\n" + + "\uD83C\uDF06 车水马龙的街道,拥挤的人潮,在这充满快节奏的水泥森林里,人们被巨大的压力裹挟着,疲惫不堪,宛若行尸走肉。改不完的稿子,数不完的ddl,望不到头的前路,许久没体会过的放松....生活什么时变得这么累了\n" + + "\n" + + "\uD83C\uDFD9 你被困在水泥森林里多久了?你又有多久没给自己好好地放个假了?你童年的星星依然在天上等着你,但你还能找到回家的路吗?\u200B \n" + + "\n" + + "\uD83C\uDF3E 放下城市的喧嚣,放下心里的压力,拿起一杆锄头,和我们一起,去往一个叫“星露谷”的地方吧。"; +} diff --git a/src/main/java/com/wonderland/stardewvalley/constant/LarkConstant.java b/src/main/java/com/wonderland/stardewvalley/constant/LarkConstant.java new file mode 100644 index 0000000..17692e4 --- /dev/null +++ b/src/main/java/com/wonderland/stardewvalley/constant/LarkConstant.java @@ -0,0 +1,8 @@ +package com.wonderland.stardewvalley.constant; + +public class LarkConstant { + + final public static String APP_ID = "cli_a876d25014e9100e"; + final public static String APP_SECRET = "8rSGfa6nLgB8vQmQRFSzodK4AozlFP3U"; + final public static String USER_OPEN_ID = "ou_857c58ee28b89f44c4ecc939a83b2476"; +} diff --git a/src/main/java/com/wonderland/stardewvalley/controller/ChatController.java b/src/main/java/com/wonderland/stardewvalley/controller/ChatController.java index e35c333..cb2ea63 100644 --- a/src/main/java/com/wonderland/stardewvalley/controller/ChatController.java +++ b/src/main/java/com/wonderland/stardewvalley/controller/ChatController.java @@ -2,6 +2,8 @@ package com.wonderland.stardewvalley.controller; import com.wonderland.stardewvalley.ai.ChatUtil; import com.wonderland.stardewvalley.entity.AIChatRequest; +import com.wonderland.stardewvalley.entity.BaseResponse; +import com.wonderland.stardewvalley.util.ResultUtil; import jakarta.annotation.Resource; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -16,7 +18,7 @@ public class ChatController { private ChatUtil chatUtil; @PostMapping - public String chat(@RequestBody AIChatRequest aiChatRequest) { - return chatUtil.doChat(aiChatRequest.getBackGround(), aiChatRequest.getContent()); + public BaseResponse chat(@RequestBody AIChatRequest aiChatRequest) { + return ResultUtil.success(chatUtil.doChat(aiChatRequest.getBackGround(), aiChatRequest.getContent())); } } diff --git a/src/main/java/com/wonderland/stardewvalley/controller/HealthController.java b/src/main/java/com/wonderland/stardewvalley/controller/HealthController.java index 570ae83..d162a5c 100644 --- a/src/main/java/com/wonderland/stardewvalley/controller/HealthController.java +++ b/src/main/java/com/wonderland/stardewvalley/controller/HealthController.java @@ -1,5 +1,7 @@ package com.wonderland.stardewvalley.controller; +import com.wonderland.stardewvalley.entity.BaseResponse; +import com.wonderland.stardewvalley.util.ResultUtil; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -9,7 +11,7 @@ import org.springframework.web.bind.annotation.RestController; public class HealthController { @GetMapping("/ping") - public String healthTest() { - return "Welcome to Stardew Valley!"; + public BaseResponse healthTest() { + return ResultUtil.success("Welcome to Stardew Valley!"); } } diff --git a/src/main/java/com/wonderland/stardewvalley/controller/LarkController.java b/src/main/java/com/wonderland/stardewvalley/controller/LarkController.java new file mode 100644 index 0000000..cacf2c7 --- /dev/null +++ b/src/main/java/com/wonderland/stardewvalley/controller/LarkController.java @@ -0,0 +1,20 @@ +package com.wonderland.stardewvalley.controller; + +import com.wonderland.stardewvalley.constant.GuideConstant; +import com.wonderland.stardewvalley.entity.BaseResponse; +import com.wonderland.stardewvalley.lark.LarkUtil; +import com.wonderland.stardewvalley.util.ResultUtil; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("lark") +public class LarkController { + + @GetMapping("welcome") + public BaseResponse welcome() throws Exception { + LarkUtil.sendText(GuideConstant.GUIDE); + return ResultUtil.success("发送成功"); + } +} diff --git a/src/main/java/com/wonderland/stardewvalley/entity/BaseResponse.java b/src/main/java/com/wonderland/stardewvalley/entity/BaseResponse.java new file mode 100644 index 0000000..60ababe --- /dev/null +++ b/src/main/java/com/wonderland/stardewvalley/entity/BaseResponse.java @@ -0,0 +1,33 @@ +package com.wonderland.stardewvalley.entity; + + +import com.wonderland.stardewvalley.exception.ErrorCode; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class BaseResponse implements Serializable { + + private int code; + + private T data; + + private String message; + + + public BaseResponse(int code, T data, String message) { + this.code = code; + this.data = data; + this.message = message; + } + + + public BaseResponse(int code, T data) { + this(code, data, ""); + } + + public BaseResponse(ErrorCode errorCode) { + this(errorCode.getCode(), null, errorCode.getMessage()); + } +} diff --git a/src/main/java/com/wonderland/stardewvalley/exception/BusinessException.java b/src/main/java/com/wonderland/stardewvalley/exception/BusinessException.java new file mode 100644 index 0000000..2e7e308 --- /dev/null +++ b/src/main/java/com/wonderland/stardewvalley/exception/BusinessException.java @@ -0,0 +1,22 @@ +package com.wonderland.stardewvalley.exception; + + +public class BusinessException extends RuntimeException{ + + private final int code; + + public BusinessException(int code, String message) { + super(message); + this.code = code; + } + + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.code = errorCode.getCode(); + } + + public BusinessException(ErrorCode errorCode, String message) { + super(message); + this.code = errorCode.getCode(); + } +} diff --git a/src/main/java/com/wonderland/stardewvalley/exception/ErrorCode.java b/src/main/java/com/wonderland/stardewvalley/exception/ErrorCode.java new file mode 100644 index 0000000..49f401f --- /dev/null +++ b/src/main/java/com/wonderland/stardewvalley/exception/ErrorCode.java @@ -0,0 +1,33 @@ +package com.wonderland.stardewvalley.exception; + + +import lombok.Getter; + +@Getter +public enum ErrorCode { + + SUCCESS(0, "ok"), + PARAMS_ERROR(40000, "请求参数错误"), + NOT_LOGIN_ERROR(40100, "未登录"), + NO_AUTH_ERROR(40101, "无权限"), + NOT_FOUND_ERROR(40400, "请求数据不存在"), + FORBIDDEN_ERROR(40300, "禁止访问"), + SYSTEM_ERROR(50000, "系统内部异常"), + OPERATION_ERROR(50001, "操作失败"); + + /** + * 状态码 + */ + private final int code; + + /** + * 信息 + */ + private final String message; + + ErrorCode(int code, String message) { + this.code = code; + this.message = message; + } + +} diff --git a/src/main/java/com/wonderland/stardewvalley/exception/ThrowUtils.java b/src/main/java/com/wonderland/stardewvalley/exception/ThrowUtils.java new file mode 100644 index 0000000..9958897 --- /dev/null +++ b/src/main/java/com/wonderland/stardewvalley/exception/ThrowUtils.java @@ -0,0 +1,19 @@ +package com.wonderland.stardewvalley.exception; + + +public class ThrowUtils { + + public static void throwIf(boolean condition, RuntimeException runtimeException) { + if (condition) { + throw runtimeException; + } + } + + public static void throwIf(boolean condition, ErrorCode errorCode) { + throwIf(condition, new BusinessException(errorCode)); + } + + public static void throwIf(boolean condition, ErrorCode errorCode, String message) { + throwIf(condition, new BusinessException(errorCode, message)); + } +} diff --git a/src/main/java/com/wonderland/stardewvalley/lark/LarkUtil.java b/src/main/java/com/wonderland/stardewvalley/lark/LarkUtil.java new file mode 100644 index 0000000..eb70681 --- /dev/null +++ b/src/main/java/com/wonderland/stardewvalley/lark/LarkUtil.java @@ -0,0 +1,60 @@ +package com.wonderland.stardewvalley.lark; + + +import cn.hutool.core.util.IdUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.gson.JsonParser; +import com.lark.oapi.Client; +import com.lark.oapi.core.utils.Jsons; +import com.lark.oapi.service.im.v1.model.CreateMessageReq; +import com.lark.oapi.service.im.v1.model.CreateMessageReqBody; +import com.lark.oapi.service.im.v1.model.CreateMessageResp; +import com.wonderland.stardewvalley.constant.LarkConstant; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; + +@Slf4j +@Service +public class LarkUtil { +// SDK 使用文档:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/java-sdk-guide/preparations +// 复制该 Demo 后, 需要将 "YOUR_APP_ID", "YOUR_APP_SECRET" 替换为自己应用的 APP_ID, APP_SECRET. +// 以下示例代码默认根据文档示例值填充,如果存在代码问题,请在 API 调试台填上相关必要参数后再复制代码使用 + + public static void sendText(String content) throws Exception { + // 构建client + Client client = Client.newBuilder(LarkConstant.APP_ID, LarkConstant.APP_SECRET).build(); + + // 组装文本 JSON + ObjectMapper mapper = new ObjectMapper(); + ObjectNode node = mapper.createObjectNode(); + node.put("text", content); + log.info("[Lark] 发送消息:{}", node.toString()); + + // 创建请求对象 + CreateMessageReq req = CreateMessageReq.newBuilder() + .receiveIdType("open_id") + .createMessageReqBody(CreateMessageReqBody.newBuilder() + .receiveId(LarkConstant.USER_OPEN_ID) + .msgType("text") + .content(node.toString()) + .uuid(IdUtil.randomUUID()) + .build()) + .build(); + + // 发起请求 + CreateMessageResp resp = client.im().v1().message().create(req); + + // 处理服务端错误 + if(!resp.success()) { + System.out.println(String.format("code:%s,msg:%s,reqId:%s, resp:%s", + resp.getCode(), resp.getMsg(), resp.getRequestId(), Jsons.createGSON(true, false).toJson(JsonParser.parseString(new String(resp.getRawResponse().getBody(), StandardCharsets.UTF_8))))); + return; + } + + // 业务数据处理 + System.out.println(Jsons.DEFAULT.toJson(resp.getData())); + } +} diff --git a/src/main/java/com/wonderland/stardewvalley/util/ResultUtil.java b/src/main/java/com/wonderland/stardewvalley/util/ResultUtil.java new file mode 100644 index 0000000..7c73fcc --- /dev/null +++ b/src/main/java/com/wonderland/stardewvalley/util/ResultUtil.java @@ -0,0 +1,24 @@ +package com.wonderland.stardewvalley.util; + + +import com.wonderland.stardewvalley.entity.BaseResponse; +import com.wonderland.stardewvalley.exception.ErrorCode; + +public class ResultUtil { + + public static BaseResponse success(T data) { + return new BaseResponse<>(ErrorCode.SUCCESS.getCode(), data, ErrorCode.SUCCESS.getMessage()); + } + + public static BaseResponse error(ErrorCode errorCode) { + return new BaseResponse<>(errorCode); + } + + public static BaseResponse error(int code, String message) { + return new BaseResponse<>(code, null, message); + } + + public static BaseResponse error(ErrorCode errorCode, String message) { + return new BaseResponse<>(errorCode.getCode(), null, message); + } +} diff --git a/src/test/java/com/wonderland/stardewvalley/lark/LarkUtilTest.java b/src/test/java/com/wonderland/stardewvalley/lark/LarkUtilTest.java new file mode 100644 index 0000000..f6028c8 --- /dev/null +++ b/src/test/java/com/wonderland/stardewvalley/lark/LarkUtilTest.java @@ -0,0 +1,18 @@ +package com.wonderland.stardewvalley.lark; + +import org.junit.jupiter.api.Test; + +class LarkUtilTest { + + @Test + void sendText() throws Exception { + LarkUtil.sendText("\uD83C\uDF31 在这个名为“如果人生是星露谷”的世界中,玩家扮演的是一位现实世界中的普通人 ,在某种“精神投射”或“虚拟现实”中,进入了一个与《星露谷物语》相似的模拟世界。这个世界并非简单的幻想乡,而是对现实生活的重构与映射 ,玩家通过在现实世界中“种田、打工、探索、运动”等行为,影响游戏中的生活节奏与心理状态。\n" + + "\n" + + "\uD83C\uDFAE 游戏说明:\n" + + "\uD83C\uDF06 车水马龙的街道,拥挤的人潮,在这充满快节奏的水泥森林里,人们被巨大的压力裹挟着,疲惫不堪,宛若行尸走肉。改不完的稿子,数不完的ddl,望不到头的前路,许久没体会过的放松....生活什么时变得这么累了\n" + + "\n" + + "\uD83C\uDFD9 你被困在水泥森林里多久了?你又有多久没给自己好好地放个假了?你童年的星星依然在天上等着你,但你还能找到回家的路吗?\u200B \n" + + "\n" + + "\uD83C\uDF3E 放下城市的喧嚣,放下心里的压力,拿起一杆锄头,和我们一起,去往一个叫“星露谷”的地方吧。"); + } +} \ No newline at end of file