diff --git a/docs/images/mock.png b/docs/images/mock.png
new file mode 100644
index 0000000000000000000000000000000000000000..a73bb3307ec3dee9ca8c7a95a97765de3e77ba0e
Binary files /dev/null and b/docs/images/mock.png differ
diff --git a/pom.xml b/pom.xml
index 36975c5d9fb292bdc201ad41e3e0f8377cfc0cd9..920214a7c5e8417a548b54b4ff8d916cdaebb8eb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,6 +10,9 @@
pom
xfg-dev-tech-app
+ xfg-dev-tech-infrastructure
+ xfg-dev-tech-domain
+ xfg-dev-tech-trigger
@@ -45,6 +48,27 @@
guava
32.1.2-jre
+
+ com.squareup.retrofit2
+ converter-gson
+ 2.9.0
+
+
+ cn.bugstack
+ chatglm-sdk-java
+ 1.0-SNAPSHOT
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.14
+
+
+ org.apache.httpcomponents
+ httpmime
+ 4.5.10
+
diff --git a/xfg-dev-tech-app/pom.xml b/xfg-dev-tech-app/pom.xml
index c04b35542da7b61cbaed06a0765a72bdf87e3c25..588a2962d356ada169aa70f86af8b3446b152f59 100644
--- a/xfg-dev-tech-app/pom.xml
+++ b/xfg-dev-tech-app/pom.xml
@@ -54,6 +54,21 @@
com.google.guava
guava
+
+ cn.bugstack
+ chatglm-sdk-java
+
+
+
+ cn.bugstack
+ xfg-dev-tech-infrastructure
+ 1.0-SNAPSHOT
+
+
+ cn.bugstack
+ xfg-dev-tech-trigger
+ 1.0-SNAPSHOT
+
diff --git a/xfg-dev-tech-app/src/main/java/cn/bugstack/xfg/dev/tech/config/ChatGLMSDKConfig.java b/xfg-dev-tech-app/src/main/java/cn/bugstack/xfg/dev/tech/config/ChatGLMSDKConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c93d43f986ff4c2f60c07cca2a890736ae35054
--- /dev/null
+++ b/xfg-dev-tech-app/src/main/java/cn/bugstack/xfg/dev/tech/config/ChatGLMSDKConfig.java
@@ -0,0 +1,35 @@
+package cn.bugstack.xfg.dev.tech.config;
+
+import cn.bugstack.chatglm.session.OpenAiSession;
+import cn.bugstack.chatglm.session.OpenAiSessionFactory;
+import cn.bugstack.chatglm.session.defaults.DefaultOpenAiSessionFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Fuzhengwei bugstack.cn @小傅哥
+ * @description ChatGLMSDK
+ * @create 2023-10-22 10:00
+ */
+@Configuration
+@EnableConfigurationProperties(ChatGLMSDKConfigProperties.class)
+public class ChatGLMSDKConfig {
+
+ @Bean
+ @ConditionalOnProperty(value = "chatglm.sdk.config.enabled", havingValue = "true", matchIfMissing = false)
+ public OpenAiSession openAiSession(ChatGLMSDKConfigProperties properties) {
+ // 1. 配置文件
+ cn.bugstack.chatglm.session.Configuration configuration = new cn.bugstack.chatglm.session.Configuration();
+ configuration.setApiHost(properties.getApiHost());
+ configuration.setApiSecretKey(properties.getApiSecretKey());
+
+ // 2. 会话工厂
+ OpenAiSessionFactory factory = new DefaultOpenAiSessionFactory(configuration);
+
+ // 3. 开启会话
+ return factory.openSession();
+ }
+
+}
diff --git a/xfg-dev-tech-app/src/main/java/cn/bugstack/xfg/dev/tech/config/ChatGLMSDKConfigProperties.java b/xfg-dev-tech-app/src/main/java/cn/bugstack/xfg/dev/tech/config/ChatGLMSDKConfigProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae57a3d9d53a197b3b9f39531e2c73fda0eff694
--- /dev/null
+++ b/xfg-dev-tech-app/src/main/java/cn/bugstack/xfg/dev/tech/config/ChatGLMSDKConfigProperties.java
@@ -0,0 +1,22 @@
+package cn.bugstack.xfg.dev.tech.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @author Fuzhengwei bugstack.cn @小傅哥
+ * @description ChatGLMSDK Config
+ * @create 2023-10-22 10:00
+ */
+@Data
+@ConfigurationProperties(prefix = "chatglm.sdk.config", ignoreInvalidFields = true)
+public class ChatGLMSDKConfigProperties {
+
+ /** 状态;open = 开启、close 关闭 */
+ private boolean enable;
+ /** 转发地址 */
+ private String apiHost;
+ /** 可以申请 sk-*** */
+ private String apiSecretKey;
+
+}
diff --git a/xfg-dev-tech-app/src/main/resources/application-dev.yml b/xfg-dev-tech-app/src/main/resources/application-dev.yml
index a86d5e85f36103b28b364d0d2b4c237ce53c8bb8..3728d3ac8eb974f43809a9f824ae5c1abbc36514 100644
--- a/xfg-dev-tech-app/src/main/resources/application-dev.yml
+++ b/xfg-dev-tech-app/src/main/resources/application-dev.yml
@@ -2,6 +2,25 @@ server:
port: 8091
application:
name: xfg-dev-tech-mock
+
+# ChatGLM SDK Config
+chatglm:
+ sdk:
+ config:
+ # 状态;true = 开启、false 关闭
+ enabled: true
+ # 官网地址
+ api-host: https://open.bigmodel.cn/
+ # 官网申请 https://open.bigmodel.cn/usercenter/apikeys
+ api-secret-key: d570f7c5d289cdac2abdfdc562e39f3f.trqz1dH8ZK6ED7Pg
+
+# 知识星球配置信息;id -> 星球ID、user-id -> 用户ID、cookie -> 登录cookie
+zsxq:
+ config:
+ id: 28885518425541
+ user-id: 241858242255511
+ cookie: zsxq_access_token=E0538FF2-B440-69E9-57D1-59EA37B9C0C6_9D76421394C6F474
+
# 日志
logging:
level:
diff --git a/xfg-dev-tech-app/src/test/java/cn/bugstack/xfg/dev/tech/test/ApiTest.java b/xfg-dev-tech-app/src/test/java/cn/bugstack/xfg/dev/tech/test/ApiTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..20a429ea3821019a08733d7c667545f7e2655e61
--- /dev/null
+++ b/xfg-dev-tech-app/src/test/java/cn/bugstack/xfg/dev/tech/test/ApiTest.java
@@ -0,0 +1,32 @@
+package cn.bugstack.xfg.dev.tech.test;
+
+import cn.bugstack.xfg.dev.tech.domain.zsxq.service.IAiReply;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import javax.annotation.Resource;
+
+/**
+ * @author Fuzhengwei bugstack.cn @小傅哥
+ * @description 单元测试
+ * @create 2023-10-22 09:08
+ */
+@Slf4j
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class ApiTest {
+
+ @Resource
+ private IAiReply aiReply;
+
+ @Test
+ public void test_IAiReply() {
+ aiReply.doAiReply();
+
+
+ }
+
+}
diff --git a/xfg-dev-tech-domain/pom.xml b/xfg-dev-tech-domain/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..87d91881fa7c1ddac3bcd9e014df16ce5e6e0db3
--- /dev/null
+++ b/xfg-dev-tech-domain/pom.xml
@@ -0,0 +1,56 @@
+
+
+ 4.0.0
+
+ cn.bugstack
+ xfg-dev-tech-mock
+ 1.0-SNAPSHOT
+
+
+ xfg-dev-tech-domain
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.projectlombok
+ lombok
+
+
+ com.alibaba
+ fastjson
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ com.google.guava
+ guava
+
+
+ cn.bugstack
+ chatglm-sdk-java
+
+
+
+
+ xfg-dev-tech-domain
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ ${java.version}
+ ${java.version}
+ ${java.version}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xfg-dev-tech-domain/src/main/java/cn/bugstack/xfg/dev/tech/domain/zsxq/adapter/IZSXQAdapter.java b/xfg-dev-tech-domain/src/main/java/cn/bugstack/xfg/dev/tech/domain/zsxq/adapter/IZSXQAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..34b4f2fef4b2ec9857e847039a9bb6d8baaef05a
--- /dev/null
+++ b/xfg-dev-tech-domain/src/main/java/cn/bugstack/xfg/dev/tech/domain/zsxq/adapter/IZSXQAdapter.java
@@ -0,0 +1,18 @@
+package cn.bugstack.xfg.dev.tech.domain.zsxq.adapter;
+
+import cn.bugstack.xfg.dev.tech.domain.zsxq.model.vo.TopicsItemVO;
+
+import java.util.List;
+
+/**
+ * @author Fuzhengwei bugstack.cn @小傅哥
+ * @description 知识星球接口适配
+ * @create 2023-10-22 10:11
+ */
+public interface IZSXQAdapter {
+
+ List queryTopics();
+
+ void comment(long topicId, String content);
+
+}
diff --git a/xfg-dev-tech-domain/src/main/java/cn/bugstack/xfg/dev/tech/domain/zsxq/model/vo/TopicsItemVO.java b/xfg-dev-tech-domain/src/main/java/cn/bugstack/xfg/dev/tech/domain/zsxq/model/vo/TopicsItemVO.java
new file mode 100644
index 0000000000000000000000000000000000000000..de3a3b9ad809993ee7c8b3adb5612a55e234bad8
--- /dev/null
+++ b/xfg-dev-tech-domain/src/main/java/cn/bugstack/xfg/dev/tech/domain/zsxq/model/vo/TopicsItemVO.java
@@ -0,0 +1,35 @@
+package cn.bugstack.xfg.dev.tech.domain.zsxq.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author Fuzhengwei bugstack.cn @小傅哥
+ * @description 话题
+ * @create 2023-10-22 10:13
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class TopicsItemVO {
+
+ private long topicId;
+
+ private String talk;
+
+ private List showCommentsItems;
+
+ @Data
+ @Builder
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class ShowCommentsItem {
+ private long userId;
+ }
+
+}
diff --git a/xfg-dev-tech-domain/src/main/java/cn/bugstack/xfg/dev/tech/domain/zsxq/service/AiReply.java b/xfg-dev-tech-domain/src/main/java/cn/bugstack/xfg/dev/tech/domain/zsxq/service/AiReply.java
new file mode 100644
index 0000000000000000000000000000000000000000..74579b390c8890fcebeae5a663e898eed3707314
--- /dev/null
+++ b/xfg-dev-tech-domain/src/main/java/cn/bugstack/xfg/dev/tech/domain/zsxq/service/AiReply.java
@@ -0,0 +1,159 @@
+package cn.bugstack.xfg.dev.tech.domain.zsxq.service;
+
+import cn.bugstack.chatglm.model.*;
+import cn.bugstack.chatglm.session.OpenAiSession;
+import cn.bugstack.xfg.dev.tech.domain.zsxq.adapter.IZSXQAdapter;
+import cn.bugstack.xfg.dev.tech.domain.zsxq.model.vo.TopicsItemVO;
+import com.alibaba.fastjson.JSON;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.sse.EventSource;
+import okhttp3.sse.EventSourceListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Nullable;
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Fuzhengwei bugstack.cn @小傅哥
+ * @description 只能回帖
+ * @create 2023-10-22 10:10
+ */
+@Slf4j
+@Service
+public class AiReply implements IAiReply {
+
+ @Autowired(required = false)
+ private OpenAiSession openAiSession;
+ @Resource
+ private IZSXQAdapter zsxqAdapter;
+ @Value("${zsxq.config.user-id}")
+ private Long userId;
+
+ private final String regex = " (.*)";
+ private volatile Set topicIds = new HashSet<>();
+
+ @Override
+ public void doAiReply() {
+ List topicsItemVOS = zsxqAdapter.queryTopics();
+
+ for (TopicsItemVO topicsItem : topicsItemVOS) {
+ // 是否回答过判断
+ if (!isCommentDone(topicsItem)) continue;
+ // 找到圈我我帖子
+ long topicId = topicsItem.getTopicId();
+ String text = topicsItem.getTalk();
+
+ // " 提问 java 冒泡排序"
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(text);
+
+ if (!matcher.find()) continue;
+ String uid = matcher.group(1);
+ String remainingText = matcher.group(3);
+
+ if (this.userId.equals(Long.valueOf(uid))) {
+
+ if (null == openAiSession) {
+ log.info("你没有开启 ChatGLM 参考yml配置文件来开启");
+ // 你可以使用 ChatGLM SDK 进行回答,回复问题;
+ zsxqAdapter.comment(topicId, "【测试,只回答圈我的帖子】对接 ChatGLM SDK https://bugstack.cn/md/project/chatgpt/sdk/chatglm-sdk-java.html 回答:" + remainingText);
+ } else {
+ log.info("ChatGLM 进入回答 {} {}", topicId, remainingText);
+ if (topicIds.contains(topicId)) {
+ continue;
+ } else {
+ topicIds.add(topicId);
+ }
+ new Thread(() -> {
+ // 入参;模型、请求信息
+ ChatCompletionRequest request = new ChatCompletionRequest();
+ request.setModel(Model.CHATGLM_LITE); // chatGLM_6b_SSE、chatglm_lite、chatglm_lite_32k、chatglm_std、chatglm_pro
+ request.setPrompt(new ArrayList() {
+ private static final long serialVersionUID = -7988151926241837899L;
+
+ {
+ add(ChatCompletionRequest.Prompt.builder()
+ .role(Role.user.getCode())
+ .content(remainingText)
+ .build());
+ }
+ });
+
+ // 请求
+ try {
+ StringBuilder content = new StringBuilder();
+ openAiSession.completions(request, new EventSourceListener() {
+ @Override
+ public void onEvent(EventSource eventSource, @Nullable String id, @Nullable String type, String data) {
+ ChatCompletionResponse chatCompletionResponse = com.alibaba.fastjson.JSON.parseObject(data, ChatCompletionResponse.class);
+ log.info("测试结果 onEvent:{}", chatCompletionResponse.getData());
+ // type 消息类型,add 增量,finish 结束,error 错误,interrupted 中断
+ if (EventType.finish.getCode().equals(type)) {
+ ChatCompletionResponse.Meta meta = com.alibaba.fastjson.JSON.parseObject(chatCompletionResponse.getMeta(), ChatCompletionResponse.Meta.class);
+ log.info("[输出结束] Tokens {}", com.alibaba.fastjson.JSON.toJSONString(meta));
+ }
+ content.append(chatCompletionResponse.getData());
+ }
+
+ @Override
+ public void onClosed(EventSource eventSource) {
+ log.info("对话完成");
+
+ // 你可以使用 ChatGLM SDK 进行回答,回复问题;
+ String contents = "ChatGLM 回答:" + content;
+ int maxLength = 5000;
+ int contentLength = contents.length();
+ int startIndex = 0;
+ int endIndex = maxLength;
+
+ while (startIndex < contentLength) {
+ if (endIndex > contentLength) {
+ endIndex = contentLength;
+ }
+
+ String subContent = contents.substring(startIndex, endIndex);
+ zsxqAdapter.comment(topicId, subContent);
+
+ startIndex = endIndex;
+ endIndex += maxLength;
+ }
+
+ topicIds.remove(topicId);
+ }
+
+ });
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }).start();
+ }
+
+ }
+
+ }
+
+ log.info("AI回复:{}", JSON.toJSONString(topicsItemVOS));
+ }
+
+ private boolean isCommentDone(TopicsItemVO topicsItem) {
+ List showComments = topicsItem.getShowCommentsItems();
+ if (null == showComments || showComments.isEmpty()) return true;
+ for (TopicsItemVO.ShowCommentsItem item : topicsItem.getShowCommentsItems()) {
+ long userId = item.getUserId();
+ if (this.userId == userId) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/xfg-dev-tech-domain/src/main/java/cn/bugstack/xfg/dev/tech/domain/zsxq/service/IAiReply.java b/xfg-dev-tech-domain/src/main/java/cn/bugstack/xfg/dev/tech/domain/zsxq/service/IAiReply.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f16237398d77dc04ce1aaa89ab52688cdcdf13a
--- /dev/null
+++ b/xfg-dev-tech-domain/src/main/java/cn/bugstack/xfg/dev/tech/domain/zsxq/service/IAiReply.java
@@ -0,0 +1,12 @@
+package cn.bugstack.xfg.dev.tech.domain.zsxq.service;
+
+/**
+ * @author Fuzhengwei bugstack.cn @小傅哥
+ * @description 智能AI回帖
+ * @create 2023-10-22 10:08
+ */
+public interface IAiReply {
+
+ void doAiReply();
+
+}
diff --git a/xfg-dev-tech-infrastructure/pom.xml b/xfg-dev-tech-infrastructure/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bf0b4da224546c7869cca0d4effe694bb84501d5
--- /dev/null
+++ b/xfg-dev-tech-infrastructure/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+ cn.bugstack
+ xfg-dev-tech-mock
+ 1.0-SNAPSHOT
+
+
+ xfg-dev-tech-infrastructure
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.projectlombok
+ lombok
+
+
+ com.squareup.retrofit2
+ converter-gson
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+ org.apache.httpcomponents
+ httpmime
+
+
+ cn.bugstack
+ xfg-dev-tech-domain
+ 1.0-SNAPSHOT
+
+
+
+
+ xfg-dev-tech-infrastructure
+
+
+
\ No newline at end of file
diff --git a/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/adapter/ZSXQAdapter.java b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/adapter/ZSXQAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..2a5e0ebc5723640fd6fe908715b673e19bcd3a2b
--- /dev/null
+++ b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/adapter/ZSXQAdapter.java
@@ -0,0 +1,65 @@
+package cn.bugstack.xfg.dev.tech.infrastructure.gateway.adapter;
+
+import cn.bugstack.xfg.dev.tech.domain.zsxq.adapter.IZSXQAdapter;
+import cn.bugstack.xfg.dev.tech.domain.zsxq.model.vo.TopicsItemVO;
+import cn.bugstack.xfg.dev.tech.infrastructure.gateway.api.IZSXQApi;
+import cn.bugstack.xfg.dev.tech.infrastructure.gateway.dto.RespData;
+import cn.bugstack.xfg.dev.tech.infrastructure.gateway.dto.ResponseDTO;
+import cn.bugstack.xfg.dev.tech.infrastructure.gateway.dto.TopicsItem;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author Fuzhengwei bugstack.cn @小傅哥
+ * @description 知识星球适配器接口
+ * @create 2023-10-22 10:18
+ */
+@Slf4j
+@Service
+public class ZSXQAdapter implements IZSXQAdapter {
+
+ @Resource
+ private IZSXQApi zsxqApi;
+
+ @Override
+ public List queryTopics() {
+ try {
+ ResponseDTO responseDTO = zsxqApi.topics();
+ RespData respData = responseDTO.getRespData();
+ List topics = respData.getTopics();
+ List topicsItemVOList = new ArrayList<>();
+
+ for (TopicsItem topicsItem : topics) {
+ TopicsItemVO topicsItemVO = TopicsItemVO.builder()
+ .topicId(topicsItem.getTopicId())
+ .talk(topicsItem.getTalk().getText())
+ .showCommentsItems(topicsItem.getShowComments() != null ? topicsItem.getShowComments().stream()
+ .map(showCommentsItem -> {
+ TopicsItemVO.ShowCommentsItem item = new TopicsItemVO.ShowCommentsItem();
+ item.setUserId(showCommentsItem.getOwner().getUserId());
+ return item;
+ })
+ .collect(Collectors.toList()) : new ArrayList<>())
+ .build();
+
+ topicsItemVOList.add(topicsItemVO);
+ }
+
+ return topicsItemVOList;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void comment(long topicId, String content) {
+ zsxqApi.comment(topicId, content);
+ }
+
+}
diff --git a/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/api/IZSXQApi.java b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/api/IZSXQApi.java
new file mode 100644
index 0000000000000000000000000000000000000000..663f1cabb936768aaf974597949c89c048cb01dc
--- /dev/null
+++ b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/api/IZSXQApi.java
@@ -0,0 +1,30 @@
+package cn.bugstack.xfg.dev.tech.infrastructure.gateway.api;
+
+import cn.bugstack.xfg.dev.tech.infrastructure.gateway.dto.ResponseDTO;
+
+import java.io.IOException;
+
+/**
+ * @author Fuzhengwei bugstack.cn @小傅哥
+ * @description 知识星球API接口
+ * @create 2023-10-22 09:47
+ */
+public interface IZSXQApi {
+
+ /**
+ * 查询知识星球帖子内容
+ *
+ * @return 帖子数据
+ * @throws IOException 异常
+ */
+ ResponseDTO topics() throws IOException;
+
+ /**
+ * 回复帖子
+ *
+ * @param topicId 帖子ID
+ * @param content 回复内容
+ */
+ void comment(long topicId, String content);
+
+}
diff --git a/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/api/impl/ZSXQApi.java b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/api/impl/ZSXQApi.java
new file mode 100644
index 0000000000000000000000000000000000000000..26c1fd1213f96943feedfad93f3c5089349885f1
--- /dev/null
+++ b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/api/impl/ZSXQApi.java
@@ -0,0 +1,112 @@
+package cn.bugstack.xfg.dev.tech.infrastructure.gateway.api.impl;
+
+import cn.bugstack.xfg.dev.tech.infrastructure.gateway.api.IZSXQApi;
+import cn.bugstack.xfg.dev.tech.infrastructure.gateway.dto.ResponseDTO;
+import com.alibaba.fastjson2.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+
+/**
+ * @author Fuzhengwei bugstack.cn @小傅哥
+ * @description 知识星球Api实现
+ * @create 2023-10-22 09:53
+ */
+@Slf4j
+@Component
+public class ZSXQApi implements IZSXQApi {
+
+ @Value("${zsxq.config.id}")
+ private String id;
+ @Value("${zsxq.config.cookie}")
+ private String cookie;
+
+ @Override
+ public ResponseDTO topics() throws IOException {
+ CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+
+ HttpGet get = new HttpGet("https://api.zsxq.com/v2/groups/" + id + "/topics?scope=all&count=20");
+
+ get.addHeader("Accept-Encoding", "deflate, gzip");
+ get.addHeader("accept", "application/json, text/plain, */*");
+ get.addHeader("accept-language", "zh-CN,zh;q=0.9,en;q=0.8");
+ get.addHeader("authority", "api.zsxq.com");
+ get.addHeader("cookie", cookie);
+ get.addHeader("dnt", "1");
+ get.addHeader("origin", "https://wx.zsxq.com");
+ get.addHeader("referer", "https://wx.zsxq.com/");
+ get.addHeader("sec-ch-ua", "\"Chromium\";v=\"118\", \"Google Chrome\";v=\"118\", \"Not=A?Brand\";v=\"99\"");
+ get.addHeader("sec-ch-ua-mobile", "?0");
+ get.addHeader("sec-ch-ua-platform", "\"macOS\"");
+ get.addHeader("sec-fetch-dest", "empty");
+ get.addHeader("sec-fetch-mode", "cors");
+ get.addHeader("sec-fetch-site", "same-site");
+ get.addHeader("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36");
+ get.addHeader("x-request-id", "372177b46-4e7d-9373-d891-98a22adaeb7");
+ get.addHeader("x-signature", "32b39b5d1af5995e3b5022e58a8d8f23cd427434");
+ get.addHeader("x-timestamp", "1697249698");
+ get.addHeader("x-version", "2.45.0");
+
+ CloseableHttpResponse response = httpClient.execute(get);
+ if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+ String jsonStr = EntityUtils.toString(response.getEntity());
+ return JSON.parseObject(jsonStr, ResponseDTO.class);
+ } else {
+ throw new RuntimeException("Err Code is " + response.getStatusLine().getStatusCode());
+ }
+ }
+
+ @Override
+ public void comment(long topicId, String content) {
+ CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+
+ HttpPost httpPost = new HttpPost("https://api.zsxq.com/v2/topics/" + topicId + "/comments");
+
+ httpPost.setHeader("Accept-Encoding", "deflate, gzip");
+ httpPost.setHeader("accept", "application/json, text/plain, */*");
+ httpPost.setHeader("accept-language", "zh-CN,zh;q=0.9,en;q=0.8");
+ httpPost.setHeader("authority", "api.zsxq.com");
+ httpPost.setHeader("content-type", "application/json");
+ httpPost.setHeader("cookie", cookie);
+ httpPost.setHeader("dnt", "1");
+ httpPost.setHeader("origin", "https://wx.zsxq.com");
+ httpPost.setHeader("referer", "https://wx.zsxq.com/");
+ httpPost.setHeader("sec-ch-ua", "\"Chromium\";v=\"118\", \"Google Chrome\";v=\"118\", \"Not=A?Brand\";v=\"99\"");
+ httpPost.setHeader("sec-ch-ua-mobile", "?0");
+ httpPost.setHeader("sec-ch-ua-platform", "\"macOS\"");
+ httpPost.setHeader("sec-fetch-dest", "empty");
+ httpPost.setHeader("sec-fetch-mode", "cors");
+ httpPost.setHeader("sec-fetch-site", "same-site");
+ httpPost.setHeader("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36");
+ httpPost.setHeader("x-request-id", "162ae5f17-2123-4ae3-67df-8b9775414e0");
+ httpPost.setHeader("x-signature", "698895e3ec4e651128b3d16755546bd2bc659687");
+ httpPost.setHeader("x-timestamp", "1697257286");
+ httpPost.setHeader("x-version", "2.45.0");
+
+ String requestBody = "{\"req_data\":{\"text\":\"" + StringEscapeUtils.escapeJava(content) + "\",\"image_ids\":[],\"mentioned_user_ids\":[]}}";
+ try {
+ httpPost.setEntity(new StringEntity(requestBody));
+ HttpResponse response = httpClient.execute(httpPost);
+ HttpEntity entity = response.getEntity();
+ String responseString = EntityUtils.toString(entity);
+ log.info("回贴结果 {}", responseString);
+ httpClient.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/Group.java b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/Group.java
new file mode 100644
index 0000000000000000000000000000000000000000..042bcf725fddb783c0624f34dd9e4add5ab174d0
--- /dev/null
+++ b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/Group.java
@@ -0,0 +1,20 @@
+package cn.bugstack.xfg.dev.tech.infrastructure.gateway.dto;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+@Data
+public class Group{
+
+ @SerializedName("group_id")
+ private long groupId;
+
+ @SerializedName("background_url")
+ private String backgroundUrl;
+
+ @SerializedName("name")
+ private String name;
+
+ @SerializedName("type")
+ private String type;
+}
\ No newline at end of file
diff --git a/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/Owner.java b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/Owner.java
new file mode 100644
index 0000000000000000000000000000000000000000..8994d123b75722a5fcc339588712499cfc45c037
--- /dev/null
+++ b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/Owner.java
@@ -0,0 +1,20 @@
+package cn.bugstack.xfg.dev.tech.infrastructure.gateway.dto;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+@Data
+public class Owner{
+
+ @SerializedName("avatar_url")
+ private String avatarUrl;
+
+ @SerializedName("user_id")
+ private long userId;
+
+ @SerializedName("name")
+ private String name;
+
+ @SerializedName("location")
+ private String location;
+}
\ No newline at end of file
diff --git a/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/RespData.java b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/RespData.java
new file mode 100644
index 0000000000000000000000000000000000000000..5420255c4800d64e362c17054c3f56dda27a23bd
--- /dev/null
+++ b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/RespData.java
@@ -0,0 +1,13 @@
+package cn.bugstack.xfg.dev.tech.infrastructure.gateway.dto;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class RespData{
+
+ @SerializedName("topics")
+ private List topics;
+}
\ No newline at end of file
diff --git a/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/ResponseDTO.java b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/ResponseDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..eea7a31413c0d1fdb950ffd3a151429e610e3932
--- /dev/null
+++ b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/ResponseDTO.java
@@ -0,0 +1,14 @@
+package cn.bugstack.xfg.dev.tech.infrastructure.gateway.dto;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+@Data
+public class ResponseDTO {
+
+ @SerializedName("resp_data")
+ private RespData respData;
+
+ @SerializedName("succeeded")
+ private boolean succeeded;
+}
\ No newline at end of file
diff --git a/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/ShowCommentsItem.java b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/ShowCommentsItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..3fb8ad0ba1fb85d05a8948dd9cbfd5205532a18b
--- /dev/null
+++ b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/ShowCommentsItem.java
@@ -0,0 +1,29 @@
+package cn.bugstack.xfg.dev.tech.infrastructure.gateway.dto;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+@Data
+public class ShowCommentsItem{
+
+ @SerializedName("owner")
+ private Owner owner;
+
+ @SerializedName("likes_count")
+ private int likesCount;
+
+ @SerializedName("create_time")
+ private String createTime;
+
+ @SerializedName("rewards_count")
+ private int rewardsCount;
+
+ @SerializedName("sticky")
+ private boolean sticky;
+
+ @SerializedName("text")
+ private String text;
+
+ @SerializedName("comment_id")
+ private long commentId;
+}
\ No newline at end of file
diff --git a/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/Talk.java b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/Talk.java
new file mode 100644
index 0000000000000000000000000000000000000000..c79c6dd407cbc03a30f874ce3d1332d9ad498e25
--- /dev/null
+++ b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/Talk.java
@@ -0,0 +1,14 @@
+package cn.bugstack.xfg.dev.tech.infrastructure.gateway.dto;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+@Data
+public class Talk{
+
+ @SerializedName("owner")
+ private Owner owner;
+
+ @SerializedName("text")
+ private String text;
+}
\ No newline at end of file
diff --git a/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/TopicsItem.java b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/TopicsItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a2e41e3c4d6c4752009c8b3d21a71a196e4c73c
--- /dev/null
+++ b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/TopicsItem.java
@@ -0,0 +1,52 @@
+package cn.bugstack.xfg.dev.tech.infrastructure.gateway.dto;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class TopicsItem{
+
+ @SerializedName("reading_count")
+ private int readingCount;
+
+ @SerializedName("create_time")
+ private String createTime;
+
+ @SerializedName("user_specific")
+ private UserSpecific userSpecific;
+
+ @SerializedName("rewards_count")
+ private int rewardsCount;
+
+ @SerializedName("show_comments")
+ private List showComments;
+
+ @SerializedName("type")
+ private String type;
+
+ @SerializedName("digested")
+ private boolean digested;
+
+ @SerializedName("likes_count")
+ private int likesCount;
+
+ @SerializedName("comments_count")
+ private int commentsCount;
+
+ @SerializedName("sticky")
+ private boolean sticky;
+
+ @SerializedName("talk")
+ private Talk talk;
+
+ @SerializedName("topic_id")
+ private long topicId;
+
+ @SerializedName("readers_count")
+ private int readersCount;
+
+ @SerializedName("group")
+ private Group group;
+}
\ No newline at end of file
diff --git a/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/UserSpecific.java b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/UserSpecific.java
new file mode 100644
index 0000000000000000000000000000000000000000..d5d92749fd3434d5b8101f816f4084b945a87bb3
--- /dev/null
+++ b/xfg-dev-tech-infrastructure/src/main/java/cn/bugstack/xfg/dev/tech/infrastructure/gateway/dto/UserSpecific.java
@@ -0,0 +1,14 @@
+package cn.bugstack.xfg.dev.tech.infrastructure.gateway.dto;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+@Data
+public class UserSpecific{
+
+ @SerializedName("subscribed")
+ private boolean subscribed;
+
+ @SerializedName("liked")
+ private boolean liked;
+}
\ No newline at end of file
diff --git a/xfg-dev-tech-trigger/pom.xml b/xfg-dev-tech-trigger/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..838aefcc786eca9ae2d1e6f54d7014cccab7d7e6
--- /dev/null
+++ b/xfg-dev-tech-trigger/pom.xml
@@ -0,0 +1,63 @@
+
+
+ 4.0.0
+
+ cn.bugstack
+ xfg-dev-tech-mock
+ 1.0-SNAPSHOT
+
+
+ xfg-dev-tech-trigger
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ com.alibaba
+ fastjson
+
+
+ org.springframework
+ spring-tx
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ org.projectlombok
+ lombok
+ 1.18.26
+
+
+ org.springframework
+ spring-context
+
+
+ cn.bugstack
+ xfg-dev-tech-domain
+ 1.0-SNAPSHOT
+ compile
+
+
+
+
+ xfg-dev-tech-trigger
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ ${java.version}
+ ${java.version}
+ ${java.version}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xfg-dev-tech-trigger/src/main/java/cn/bugstack/xfg/dev/tech/job/ReplyJob.java b/xfg-dev-tech-trigger/src/main/java/cn/bugstack/xfg/dev/tech/job/ReplyJob.java
new file mode 100644
index 0000000000000000000000000000000000000000..1336057526f1660122da57f763566c20af80e1a5
--- /dev/null
+++ b/xfg-dev-tech-trigger/src/main/java/cn/bugstack/xfg/dev/tech/job/ReplyJob.java
@@ -0,0 +1,27 @@
+package cn.bugstack.xfg.dev.tech.job;
+
+import cn.bugstack.xfg.dev.tech.domain.zsxq.service.IAiReply;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * @author Fuzhengwei bugstack.cn @小傅哥
+ * @description 回帖JOB
+ * @create 2023-10-22 11:02
+ */
+@Slf4j
+@Component()
+public class ReplyJob {
+
+ @Resource
+ private IAiReply aiReply;
+
+ @Scheduled(cron = "0/10 * * * * ?")
+ public void exec() throws Exception {
+ aiReply.doAiReply();
+ }
+
+}