codeCache;
33 |
34 | @Resource
35 | private IWeiXinRepository weiXinRepository;
36 |
37 | /**
38 | * 1. 用户的请求行文,分为事件event、消息text,这里我们只处理消息内容
39 | * 2. 用户行为、消息类型,是多样性的,这部分如果用户有更多的扩展需求,可以使用设计模式【模板模式 + 策略模式 + 工厂模式】,分拆逻辑。
40 | */
41 | @Override
42 | public String acceptUserBehavior(UserBehaviorMessageEntity userBehaviorMessageEntity) {
43 | // Event 事件类型,忽略处理
44 | if (MsgTypeVO.EVENT.getCode().equals(userBehaviorMessageEntity.getMsgType())) {
45 | return "";
46 | }
47 |
48 | // Text 文本类型
49 | if (MsgTypeVO.TEXT.getCode().equals(userBehaviorMessageEntity.getMsgType())) {
50 |
51 | String messageContent = userBehaviorMessageEntity.getContent();
52 | String replay = "";
53 | if(ContentCodeVO.GENCODE.getCode().equals(messageContent)){
54 | // 获取验证码
55 | String code = weiXinRepository.genCode(userBehaviorMessageEntity.getOpenId());
56 | replay = String.format("您的验证码为:%s 有效期%d分钟!", code, 3);
57 | } else if (ContentCodeVO.GETOPENID.getCode().equals(messageContent)) {
58 | // 获取到openid
59 | String openid = userBehaviorMessageEntity.getOpenId();
60 | replay = String.format("您的OpenId为:%s 请谨慎保管,以免账户泄露!", openid);
61 | }
62 |
63 | // 反馈信息[文本]
64 | MessageTextEntity res = new MessageTextEntity();
65 | res.setToUserName(userBehaviorMessageEntity.getOpenId());
66 | res.setFromUserName(originalId);
67 | res.setCreateTime(String.valueOf(System.currentTimeMillis() / 1000L));
68 | res.setMsgType("text");
69 | res.setContent(replay);
70 | return XmlUtil.beanToXml(res);
71 | }
72 |
73 | throw new ChatGPTException(userBehaviorMessageEntity.getMsgType() + " 未被处理的行为类型 Err!");
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/chatgpt-data-types/src/main/java/com/luckysj/chatgpt/data/types/util/IPUtils.java:
--------------------------------------------------------------------------------
1 | package com.luckysj.chatgpt.data.types.util;
2 |
3 |
4 | import org.apache.commons.lang3.StringUtils;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import javax.servlet.http.HttpServletRequest;
9 | import java.net.InetAddress;
10 | import java.net.UnknownHostException;
11 |
12 | /**
13 | * @author www.luckysj.top 刘仕杰
14 | * @description Ip地址工具类
15 | * @create 2024/01/01 15:53:42
16 | */
17 | public class IPUtils {
18 | private static Logger logger = LoggerFactory.getLogger(IPUtils.class);
19 | private static final String IP_UTILS_FLAG = ",";
20 | private static final String UNKNOWN = "unknown";
21 | private static final String LOCALHOST_IP = "0:0:0:0:0:0:0:1";
22 | private static final String LOCALHOST_IP1 = "127.0.0.1";
23 |
24 | /**
25 | * 获取IP地址
26 | *
27 | * 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
28 | * 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
29 | */
30 | public static String getIpAddr(HttpServletRequest request) {
31 | String ip = null;
32 | try {
33 | //以下两个获取在k8s中,将真实的客户端IP,放到了x-Original-Forwarded-For。而将WAF的回源地址放到了 x-Forwarded-For了。
34 | ip = request.getHeader("X-Original-Forwarded-For");
35 | if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
36 | ip = request.getHeader("X-Forwarded-For");
37 | }
38 | //获取nginx等代理的ip
39 | if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
40 | ip = request.getHeader("x-forwarded-for");
41 | }
42 | if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
43 | ip = request.getHeader("Proxy-Client-IP");
44 | }
45 | if (StringUtils.isEmpty(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
46 | ip = request.getHeader("WL-Proxy-Client-IP");
47 | }
48 | if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
49 | ip = request.getHeader("HTTP_CLIENT_IP");
50 | }
51 | if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
52 | ip = request.getHeader("HTTP_X_FORWARDED_FOR");
53 | }
54 | //兼容k8s集群获取ip
55 | if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
56 | ip = request.getRemoteAddr();
57 | if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {
58 | //根据网卡取本机配置的IP
59 | InetAddress iNet = null;
60 | try {
61 | iNet = InetAddress.getLocalHost();
62 | } catch (UnknownHostException e) {
63 | logger.error("getClientIp error: {}", e);
64 | }
65 | ip = iNet.getHostAddress();
66 | }
67 | }
68 | } catch (Exception e) {
69 | logger.error("IPUtils ERROR ", e);
70 | }
71 | //使用代理,则获取第一个IP地址
72 | if (!StringUtils.isEmpty(ip) && ip.indexOf(IP_UTILS_FLAG) > 0) {
73 | ip = ip.substring(0, ip.indexOf(IP_UTILS_FLAG));
74 | }
75 |
76 | return ip;
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/chatgpt-data-domain/src/main/java/com/luckysj/chatgpt/data/domain/openai/service/channel/impl/ChatGLMService.java:
--------------------------------------------------------------------------------
1 | package com.luckysj.chatgpt.data.domain.openai.service.channel.impl;
2 |
3 | import cn.bugstack.chatglm.model.*;
4 | import cn.bugstack.chatglm.session.OpenAiSession;
5 | import com.alibaba.fastjson.JSON;
6 | import com.fasterxml.jackson.core.JsonProcessingException;
7 | import com.luckysj.chatgpt.data.domain.openai.model.aggregates.ChatProcessAggregate;
8 | import com.luckysj.chatgpt.data.domain.openai.service.channel.OpenAiGroupService;
9 | import com.luckysj.chatgpt.data.types.enums.ChatGLMModel;
10 | import com.luckysj.chatgpt.data.types.exception.ChatGPTException;
11 | import lombok.extern.slf4j.Slf4j;
12 | import okhttp3.sse.EventSource;
13 | import okhttp3.sse.EventSourceListener;
14 | import org.jetbrains.annotations.Nullable;
15 | import org.springframework.stereotype.Service;
16 | import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
17 |
18 | import javax.annotation.Resource;
19 | import java.util.List;
20 | import java.util.stream.Collectors;
21 |
22 | /**
23 | * @author www.luckysj.top 刘仕杰
24 | * @description Chatglm服务
25 | * @create 2023/12/15 18:26:07
26 | */
27 | @Slf4j
28 | @Service
29 | public class ChatGLMService implements OpenAiGroupService {
30 |
31 | @Resource
32 | protected OpenAiSession chatGlMOpenAiSession;
33 |
34 | @Override
35 | public void doMessageResponse(ChatProcessAggregate chatProcess, ResponseBodyEmitter emitter) throws JsonProcessingException {
36 | // 1. 请求消息
37 | List prompts = chatProcess.getMessages().stream()
38 | .map(entity -> ChatCompletionRequest.Prompt.builder()
39 | .role(Role.user.getCode())
40 | .content(entity.getContent())
41 | .build())
42 | .collect(Collectors.toList());
43 |
44 | // 2. 封装参数
45 | ChatCompletionRequest request = new ChatCompletionRequest();
46 | request.setModel(Model.valueOf(ChatGLMModel.get(chatProcess.getModel()).name())); // chatGLM_6b_SSE、chatglm_lite、chatglm_lite_32k、chatglm_std、chatglm_pro
47 | request.setPrompt(prompts);
48 |
49 | // 3.请求应答
50 | chatGlMOpenAiSession.completions(request, new EventSourceListener() {
51 | @Override
52 | public void onEvent(EventSource eventSource, @Nullable String id, @Nullable String type, String data) {
53 | ChatCompletionResponse response = JSON.parseObject(data, ChatCompletionResponse.class);
54 |
55 | // 发送信息
56 | if (EventType.add.getCode().equals(type)){
57 | try {
58 | emitter.send(response.getData());
59 | } catch (Exception e) {
60 | throw new ChatGPTException(e.getMessage());
61 | }
62 | }
63 |
64 | // type 消息类型,add 增量,finish 结束,error 错误,interrupted 中断
65 | if (EventType.finish.getCode().equals(type)) {
66 | ChatCompletionResponse.Meta meta = JSON.parseObject(response.getMeta(), ChatCompletionResponse.Meta.class);
67 | log.info("[输出结束] Tokens {}", JSON.toJSONString(meta));
68 | }
69 | }
70 |
71 | @Override
72 | public void onClosed(EventSource eventSource) {
73 | emitter.complete();
74 | }
75 |
76 | });
77 |
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/chatgpt-data-domain/src/main/java/com/luckysj/chatgpt/data/domain/openai/service/channel/service/impl/ImageGenerativeModelServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.luckysj.chatgpt.data.domain.openai.service.channel.service.impl;
2 |
3 | import cn.bugstack.chatgpt.common.Constants;
4 | import cn.bugstack.chatgpt.domain.images.ImageEnum;
5 | import cn.bugstack.chatgpt.domain.images.ImageRequest;
6 | import cn.bugstack.chatgpt.domain.images.ImageResponse;
7 | import cn.bugstack.chatgpt.domain.images.Item;
8 | import cn.bugstack.chatgpt.session.OpenAiSession;
9 | import com.luckysj.chatgpt.data.domain.openai.model.aggregates.ChatProcessAggregate;
10 | import com.luckysj.chatgpt.data.domain.openai.model.entity.MessageEntity;
11 | import com.luckysj.chatgpt.data.domain.openai.service.channel.service.IGenerativeModelService;
12 | import lombok.extern.slf4j.Slf4j;
13 | import org.springframework.beans.factory.annotation.Autowired;
14 | import org.springframework.stereotype.Service;
15 | import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
16 |
17 | import java.io.IOException;
18 | import java.util.List;
19 | import java.util.concurrent.CompletableFuture;
20 |
21 | /**
22 | * @author www.luckysj.top 刘仕杰
23 | * @description 图片生成服务
24 | * @create 2023/12/18 17:50:07
25 | */
26 | @Slf4j
27 | @Service
28 | public class ImageGenerativeModelServiceImpl implements IGenerativeModelService {
29 |
30 | @Autowired(required = false)
31 | protected OpenAiSession openAiSession;
32 |
33 | @Override
34 | public void doMessageResponse(ChatProcessAggregate chatProcess, ResponseBodyEmitter emitter) throws IOException {
35 | if (null == openAiSession) {
36 | emitter.send("模型调用未开启,可以选择其他模型对话!");
37 | emitter.complete();
38 | return;
39 | }
40 |
41 | emitter.send("图画开始生成,平均时间为10s-20s,请耐心等待~\n");
42 | emitter.send("-------------------------------------\n");
43 |
44 | // 异步执行绘画请求,不异步执行的话会导致上面的提示信息无法提前响应给前端
45 | CompletableFuture.runAsync(() -> {
46 | try {
47 | // 封装请求信息,这里我们提取最近的的用户消息作为上下文
48 | StringBuilder prompt = new StringBuilder();
49 | List messages = chatProcess.getMessages();
50 | for (MessageEntity message : messages) {
51 | String role = message.getRole();
52 | if (Constants.Role.USER.getCode().equals(role)) {
53 | prompt.append(message.getContent());
54 | prompt.append("\r\n");
55 | }
56 | }
57 |
58 | // 绘图请求信息
59 | ImageRequest request = ImageRequest.builder()
60 | .prompt(prompt.toString())
61 | .model(chatProcess.getModel())
62 | .size(ImageEnum.Size.size_1024.getCode())
63 | .build();
64 |
65 | // 异步请求绘图
66 | ImageResponse imageResponse = openAiSession.genImages(request);
67 | List- items = imageResponse.getData();
68 |
69 | // chatgpt可以一次生成多张图片,默认是一张
70 | for (Item item : items) {
71 | String url = item.getUrl();
72 | log.info("url:{}", url);
73 | // md5格式输出给前端
74 | emitter.send("");
75 | }
76 |
77 | emitter.complete();
78 | } catch (IOException e) {
79 | log.error("Error generating images: {}", e.getMessage(), e);
80 | emitter.completeWithError(e);
81 | }
82 | });
83 |
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/chatgpt-data-trigger/src/main/java/com/luckysj/chatgpt/data/trigger/http/AuthController.java:
--------------------------------------------------------------------------------
1 | package com.luckysj.chatgpt.data.trigger.http;
2 |
3 | import com.luckysj.chatgpt.data.domain.auth.model.entity.AuthStateEntity;
4 | import com.luckysj.chatgpt.data.domain.auth.model.valobj.AuthTypeVo;
5 | import com.luckysj.chatgpt.data.domain.auth.service.IAuthService;
6 | import com.luckysj.chatgpt.data.types.annotation.AccessInterceptor;
7 | import com.luckysj.chatgpt.data.types.common.Constants;
8 | import com.luckysj.chatgpt.data.types.model.Response;
9 | import io.micrometer.core.annotation.Timed;
10 | import lombok.extern.slf4j.Slf4j;
11 | import org.springframework.web.bind.annotation.*;
12 |
13 | import javax.annotation.Resource;
14 | import javax.servlet.http.HttpServletRequest;
15 |
16 | /**
17 | * @author www.luckysj.top 刘仕杰
18 | * @description 登录授权接口
19 | * @create 2023/12/05 16:18:06
20 | */
21 | @Slf4j
22 | @RestController
23 | @CrossOrigin("${app.config.cross-origin}")
24 | @RequestMapping("/api/${app.config.api-version}/auth/")
25 | public class AuthController {
26 | @Resource
27 | private IAuthService authService;
28 |
29 | /**
30 | * @description 验证码登录授权接口
31 | * @param code 验证码
32 | * @return Response data为token的Response
33 | * @date 2023/12/08 16:29:01
34 | */
35 | @Timed(value="login_http",description="用户验证码登录接口")
36 | @PostMapping(value = "/login")
37 | @AccessInterceptor(key = "request_ip", fallbackMethod = "loginErr", permitsPerSecond = 10L, blacklistCount = 10)
38 | public Response doLogin(@RequestParam String code, HttpServletRequest request){
39 | try {
40 | AuthStateEntity authStateEntity = authService.doLogin(code);
41 | // 校验不通过
42 | if(!authStateEntity.getCode().equals(AuthTypeVo.CODE_SUCCESS.getCode())){
43 | return Response.builder()
44 | .code(Constants.ResponseCode.TOKEN_ERROR.getCode())
45 | .info(Constants.ResponseCode.TOKEN_ERROR.getInfo())
46 | .build();
47 | }
48 |
49 | //校验通过,放行,携带token信息
50 | return Response.builder()
51 | .code(Constants.ResponseCode.SUCCESS.getCode())
52 | .info(Constants.ResponseCode.SUCCESS.getInfo())
53 | .data(authStateEntity.getToken())
54 | .build();
55 | } catch (Exception e) {
56 | log.error("鉴权登录校验失败,验证码: {},错误消息:{}", code, e.getMessage());
57 | return Response.builder()
58 | .code(Constants.ResponseCode.UN_ERROR.getCode())
59 | .info(Constants.ResponseCode.UN_ERROR.getInfo())
60 | .build();
61 | }
62 | }
63 |
64 | // 获取身份信息测试接口(本地测试用,不需要通过微信公众号登录,防止影响原项目的运行)
65 | @RequestMapping(value = "getCode", method = RequestMethod.GET)
66 | public Response getACode(@RequestParam String openid) {
67 | String code = authService.getAuthCode(openid);
68 | return Response.builder()
69 | .code(Constants.ResponseCode.SUCCESS.getCode())
70 | .info(Constants.ResponseCode.SUCCESS.getInfo())
71 | .data(code)
72 | .build();
73 | }
74 |
75 | public Response loginErr(String code, HttpServletRequest request) {
76 | System.out.println(code);
77 | return Response.builder()
78 | .code(Constants.ResponseCode.FREQUENCY_LIMITED.getCode())
79 | .info(Constants.ResponseCode.FREQUENCY_LIMITED.getInfo())
80 | .data(code)
81 | .build();
82 | }
83 |
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/chatgpt-data-types/src/main/java/com/luckysj/chatgpt/data/types/util/IdWorkerUtils.java:
--------------------------------------------------------------------------------
1 | package com.luckysj.chatgpt.data.types.util;
2 |
3 | import java.util.Random;
4 |
5 | /**
6 | * snow flow .
7 | *
8 | */
9 | public final class IdWorkerUtils {
10 |
11 | private static final Random RANDOM = new Random();
12 |
13 | private static final long WORKER_ID_BITS = 5L;
14 |
15 | private static final long DATACENTERIDBITS = 5L;
16 |
17 | private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
18 |
19 | private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTERIDBITS);
20 |
21 | private static final long SEQUENCE_BITS = 12L;
22 |
23 | private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
24 |
25 | private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
26 |
27 | private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTERIDBITS;
28 |
29 | private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
30 |
31 | private static final IdWorkerUtils ID_WORKER_UTILS = new IdWorkerUtils();
32 |
33 | private long workerId;
34 |
35 | private long datacenterId;
36 |
37 | private long idepoch;
38 |
39 | private long sequence = '0';
40 |
41 | private long lastTimestamp = -1L;
42 |
43 | private IdWorkerUtils() {
44 | this(RANDOM.nextInt((int) MAX_WORKER_ID), RANDOM.nextInt((int) MAX_DATACENTER_ID), 1288834974657L);
45 | }
46 |
47 | private IdWorkerUtils(final long workerId, final long datacenterId, final long idepoch) {
48 | if (workerId > MAX_WORKER_ID || workerId < 0) {
49 | throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", MAX_WORKER_ID));
50 | }
51 | if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
52 | throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", MAX_DATACENTER_ID));
53 | }
54 | this.workerId = workerId;
55 | this.datacenterId = datacenterId;
56 | this.idepoch = idepoch;
57 | }
58 |
59 | /**
60 | * Gets instance.
61 | *
62 | * @return the instance
63 | */
64 | public static IdWorkerUtils getInstance() {
65 | return ID_WORKER_UTILS;
66 | }
67 |
68 | public synchronized long nextId() {
69 | long timestamp = timeGen();
70 | if (timestamp < lastTimestamp) {
71 | throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
72 | }
73 | if (lastTimestamp == timestamp) {
74 | sequence = (sequence + 1) & SEQUENCE_MASK;
75 | if (sequence == 0) {
76 | timestamp = tilNextMillis(lastTimestamp);
77 | }
78 | } else {
79 | sequence = 0L;
80 | }
81 |
82 | lastTimestamp = timestamp;
83 |
84 | return ((timestamp - idepoch) << TIMESTAMP_LEFT_SHIFT)
85 | | (datacenterId << DATACENTER_ID_SHIFT)
86 | | (workerId << WORKER_ID_SHIFT) | sequence;
87 | }
88 |
89 | private long tilNextMillis(final long lastTimestamp) {
90 | long timestamp = timeGen();
91 | while (timestamp <= lastTimestamp) {
92 | timestamp = timeGen();
93 | }
94 | return timestamp;
95 | }
96 |
97 | private long timeGen() {
98 | return System.currentTimeMillis();
99 | }
100 |
101 | /**
102 | * Build part number string.
103 | *
104 | * @return the string
105 | */
106 | public String buildPartNumber() {
107 | return String.valueOf(ID_WORKER_UTILS.nextId());
108 | }
109 |
110 | /**
111 | * Create uuid string.
112 | *
113 | * @return the string
114 | */
115 | public String createUUID() {
116 | return String.valueOf(ID_WORKER_UTILS.nextId());
117 | }
118 |
119 | public static void main(String[] args) {
120 | System.out.println(IdWorkerUtils.getInstance().nextId());
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/chatgpt-data-app/src/test/java/com/luckysj/chatgpt/data/AliPayTest.java:
--------------------------------------------------------------------------------
1 | package com.luckysj.chatgpt.data;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.JSONObject;
5 | import com.alipay.api.AlipayApiException;
6 | import com.alipay.api.AlipayClient;
7 | import com.alipay.api.DefaultAlipayClient;
8 | import com.alipay.api.request.AlipayTradeQueryRequest;
9 | import com.alipay.api.request.AlipayTradeWapPayRequest;
10 | import com.alipay.api.response.AlipayTradeQueryResponse;
11 | import org.junit.jupiter.api.Test;
12 | import org.junit.runner.RunWith;
13 | import org.springframework.beans.factory.annotation.Value;
14 | import org.springframework.boot.test.context.SpringBootTest;
15 | import org.springframework.test.context.junit4.SpringRunner;
16 |
17 |
18 | import java.util.Map;
19 |
20 | /**
21 | * @author Mr.M
22 | * @version 1.0
23 | * @description 支付宝查询接口
24 | * @date 2022/10/4 17:18
25 | */
26 | @RunWith(SpringRunner.class)
27 | @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
28 | public class AliPayTest {
29 |
30 | @Value("${pay.alipay.app_id}")
31 | String APP_ID;
32 |
33 | @Value("${pay.alipay.app_private_key}")
34 | String APP_PRIVATE_KEY;
35 |
36 | @Value("${pay.alipay.alipay_public_key}")
37 | String ALIPAY_PUBLIC_KEY;
38 |
39 | // 请求网关地址
40 | @Value("${pay.alipay.url}")
41 | public String URL;
42 |
43 | // 编码
44 | public static String CHARSET = "UTF-8";
45 | // 返回格式
46 | public static String FORMAT = "json";
47 | // 日志记录目录
48 | public static String log_path = "/log";
49 | // RSA2
50 | public static String SIGNTYPE = "RSA2";
51 |
52 | @Test
53 | public void queryPayResult() throws AlipayApiException {
54 | AlipayClient alipayClient = new DefaultAlipayClient(URL, APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, SIGNTYPE); //获得初始化的AlipayClient
55 | AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
56 | JSONObject bizContent = new JSONObject();
57 | bizContent.put("out_trade_no", "202210100010101002");
58 | //bizContent.put("trade_no", "2014112611001004680073956707");
59 | request.setBizContent(bizContent.toString());
60 | AlipayTradeQueryResponse response = alipayClient.execute(request);
61 | if (response.isSuccess()) {
62 | System.out.println("调用成功");
63 | String resultJson = response.getBody();
64 | //转map
65 | Map resultMap = JSON.parseObject(resultJson, Map.class);
66 | Map alipay_trade_query_response = (Map) resultMap.get("alipay_trade_query_response");
67 | //支付结果
68 | String trade_status = (String) alipay_trade_query_response.get("trade_status");
69 | System.out.println(trade_status);
70 | } else {
71 | System.out.println("调用失败");
72 | }
73 | }
74 |
75 | @Test
76 | public void testRequestPay(){
77 | AlipayClient alipayClient = new DefaultAlipayClient(URL, APP_ID, APP_PRIVATE_KEY, FORMAT, CHARSET, ALIPAY_PUBLIC_KEY,SIGNTYPE);
78 | //获得初始化的AlipayClient
79 | AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();//创建API对应的request
80 | // alipayRequest.setReturnUrl("http://domain.com/CallBack/return_url.jsp");
81 | // alipayRequest.setNotifyUrl("http://domain.com/CallBack/notify_url.jsp");//在公共参数中设置回跳和通知地址
82 | alipayRequest.setBizContent("{" +
83 | " \"out_trade_no\":\"202210100010101002\"," +
84 | " \"total_amount\":0.1," +
85 | " \"subject\":\"Iphone6 16G\"," +
86 | " \"product_code\":\"QUICK_WAP_WAY\"" +
87 | " }");//填充业务参数
88 | try {
89 | String form = alipayClient.pageExecute(alipayRequest).getBody(); //调用SDK生成表单
90 | System.out.println("发起交易测试");
91 | } catch (AlipayApiException e) {
92 | e.printStackTrace();
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/chatgpt-data-domain/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | chatgpt-data
7 | com.luckysj
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | chatgpt-data-domain
13 |
14 |
15 |
16 | org.springframework
17 | spring-webmvc
18 |
19 |
20 | org.apache.tomcat.embed
21 | tomcat-embed-core
22 |
23 |
24 | org.projectlombok
25 | lombok
26 |
27 |
28 | com.alibaba
29 | fastjson
30 |
31 |
32 | org.apache.commons
33 | commons-lang3
34 |
35 |
36 | com.google.guava
37 | guava
38 |
39 |
40 | io.jsonwebtoken
41 | jjwt
42 |
43 |
44 | com.auth0
45 | java-jwt
46 |
47 |
48 | commons-codec
49 | commons-codec
50 |
51 |
52 |
53 | cn.bugstack.chatgpt
54 | chatgpt-sdk-java
55 |
56 |
57 | cn.bugstack
58 | chatglm-sdk-java
59 |
60 |
61 | com.luckysj
62 | chatgpt-data-types
63 | 1.0-SNAPSHOT
64 |
65 |
66 | com.github.houbb
67 | sensitive-word
68 |
69 |
70 |
71 | com.alipay.sdk
72 | alipay-sdk-java
73 | 3.7.73.ALL
74 |
75 |
76 |
77 | commons-logging
78 | commons-logging
79 | 1.2
80 |
81 |
82 |
83 | org.springframework.boot
84 | spring-boot-starter-test
85 | test
86 |
87 |
88 |
89 |
90 |
91 | chatgpt-data-domain
92 |
93 |
94 | org.apache.maven.plugins
95 | maven-compiler-plugin
96 |
97 | ${java.version}
98 | ${java.version}
99 | ${java.version}
100 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/chatgpt-data-domain/src/main/java/com/luckysj/chatgpt/data/domain/order/service/AbstractOrderService.java:
--------------------------------------------------------------------------------
1 | package com.luckysj.chatgpt.data.domain.order.service;
2 |
3 |
4 | import com.luckysj.chatgpt.data.domain.order.config.AliPayConfig;
5 | import com.luckysj.chatgpt.data.domain.order.model.entity.*;
6 | import com.luckysj.chatgpt.data.domain.order.model.valobj.PayStatusVO;
7 | import com.luckysj.chatgpt.data.domain.order.repository.IOrderRepository;
8 | import com.luckysj.chatgpt.data.types.common.Constants;
9 | import com.luckysj.chatgpt.data.types.exception.ChatGPTException;
10 | import lombok.extern.slf4j.Slf4j;
11 |
12 | import javax.annotation.Resource;
13 | import java.math.BigDecimal;
14 |
15 | /**
16 | * @author www.luckysj.top 刘仕杰
17 | * @description 抽象订单服务
18 | * @create 2023/12/11 21:28:17
19 | */
20 | @Slf4j
21 | public abstract class AbstractOrderService implements IOrderService {
22 |
23 | @Resource
24 | protected IOrderRepository orderRepository;
25 |
26 | @Resource
27 | protected AliPayConfig aliPayConfig;
28 |
29 | @Override
30 | public PayOrderEntity createOrder(ShopCartEntity shopCartEntity) {
31 | try {
32 | // 0. 基础信息
33 | String openid = shopCartEntity.getOpenid();
34 | Integer productId = shopCartEntity.getProductId();
35 |
36 | // 1. 查询有效的未支付订单,如果存在直接返回支付宝支付 CodeUrl
37 | UnpaidOrderEntity unpaidOrderEntity = orderRepository.queryUnpaidOrder(shopCartEntity);
38 | if (null != unpaidOrderEntity && PayStatusVO.WAIT.equals(unpaidOrderEntity.getPayStatus()) && null != unpaidOrderEntity.getPayUrl()) {
39 | log.info("创建订单-存在,已生成支付宝支付,返回 openid: {} orderId: {} payUrl: {}", openid, unpaidOrderEntity.getOrderId(), unpaidOrderEntity.getPayUrl());
40 | return PayOrderEntity.builder()
41 | .openid(openid)
42 | .orderId(unpaidOrderEntity.getOrderId())
43 | .payUrl(unpaidOrderEntity.getPayUrl())
44 | .payStatus(unpaidOrderEntity.getPayStatus())
45 | .build();
46 | } else if (null != unpaidOrderEntity && null == unpaidOrderEntity.getPayUrl()) {
47 | log.info("创建订单-存在,未生成支付宝支付,返回 openid: {} orderId: {}", openid, unpaidOrderEntity.getOrderId());
48 | PayOrderEntity payOrderEntity = this.doPrepayOrder(openid, unpaidOrderEntity.getOrderId(), unpaidOrderEntity.getProductName(), unpaidOrderEntity.getTotalAmount());
49 | log.info("创建订单-完成,生成支付单。openid: {} orderId: {} payUrl: {}", openid, payOrderEntity.getOrderId(), payOrderEntity.getPayUrl());
50 | return payOrderEntity;
51 | }
52 |
53 | // 2. 商品查询
54 | ProductEntity productEntity = orderRepository.queryProduct(productId);
55 | // 商品有效性判断
56 | if (!productEntity.isAvailable()) {
57 | throw new ChatGPTException(Constants.ResponseCode.ORDER_PRODUCT_ERR.getCode(), Constants.ResponseCode.ORDER_PRODUCT_ERR.getInfo());
58 | }
59 |
60 | // 3. 保存订单
61 | OrderEntity orderEntity = this.doSaveOrder(openid, productEntity);
62 |
63 | // 4. 创建支付
64 | PayOrderEntity payOrderEntity = this.doPrepayOrder(openid, orderEntity.getOrderId(), productEntity.getProductName(), orderEntity.getTotalAmount());
65 | log.info("创建订单-完成,生成支付单。openid: {} orderId: {} payUrl: {}", openid, orderEntity.getOrderId(), payOrderEntity.getPayUrl());
66 |
67 | return payOrderEntity;
68 | } catch (Exception e) {
69 | log.error("创建订单,已生成微信支付,返回 openid: {} productId: {}", shopCartEntity.getOpenid(), shopCartEntity.getProductId());
70 | throw new ChatGPTException(Constants.ResponseCode.UN_ERROR.getCode(), Constants.ResponseCode.UN_ERROR.getInfo());
71 | }
72 | }
73 |
74 | protected abstract OrderEntity doSaveOrder(String openid, ProductEntity productEntity);
75 |
76 | protected abstract PayOrderEntity doPrepayOrder(String openid, String orderId, String productName, BigDecimal amountTotal);
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/chatgpt-data-domain/src/main/java/com/luckysj/chatgpt/data/domain/openai/service/channel/service/impl/TextGenerativeModelServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.luckysj.chatgpt.data.domain.openai.service.channel.service.impl;
2 |
3 | import cn.bugstack.chatgpt.common.Constants;
4 | import cn.bugstack.chatgpt.domain.chat.ChatChoice;
5 | import cn.bugstack.chatgpt.domain.chat.ChatCompletionRequest;
6 | import cn.bugstack.chatgpt.domain.chat.ChatCompletionResponse;
7 | import cn.bugstack.chatgpt.domain.chat.Message;
8 | import cn.bugstack.chatgpt.session.OpenAiSession;
9 | import com.alibaba.fastjson.JSON;
10 | import com.luckysj.chatgpt.data.domain.openai.model.aggregates.ChatProcessAggregate;
11 | import com.luckysj.chatgpt.data.domain.openai.service.channel.service.IGenerativeModelService;
12 | import lombok.extern.slf4j.Slf4j;
13 | import okhttp3.sse.EventSource;
14 | import okhttp3.sse.EventSourceListener;
15 | import org.apache.commons.lang3.StringUtils;
16 | import org.jetbrains.annotations.NotNull;
17 | import org.jetbrains.annotations.Nullable;
18 | import org.springframework.beans.factory.annotation.Autowired;
19 | import org.springframework.stereotype.Service;
20 | import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
21 |
22 | import java.util.List;
23 | import java.util.stream.Collectors;
24 |
25 | /**
26 | * @author www.luckysj.top 刘仕杰
27 | * @description 对话生成服务
28 | * @create 2023/12/18 17:55:43
29 | */
30 | @Slf4j
31 | @Service
32 | public class TextGenerativeModelServiceImpl implements IGenerativeModelService {
33 | @Autowired(required = false)
34 | protected OpenAiSession openAiSession;
35 |
36 | @Override
37 | public void doMessageResponse(ChatProcessAggregate chatProcess, ResponseBodyEmitter emitter) throws Exception {
38 | if (null == openAiSession) {
39 | emitter.send("模型调用未开启,可以选择其他模型对话!");
40 | return;
41 | }
42 |
43 | // 1. 处理请求消息,使用stream流将chatProcessAggregate中的消息数组转换为Message数组
44 | List messages = chatProcess.getMessages().stream()
45 | .map(entity -> Message.builder()
46 | .role(Constants.Role.valueOf(entity.getRole().toUpperCase()))
47 | .content(entity.getContent())
48 | .name(entity.getName())
49 | .build())
50 | .collect(Collectors.toList());
51 |
52 | // 2. 封装本次询问的相关参数,如消息内容,使用模型等
53 | ChatCompletionRequest chatCompletion = ChatCompletionRequest
54 | .builder()
55 | .stream(true)
56 | .messages(messages)
57 | .model(ChatCompletionRequest.Model.GPT_3_5_TURBO.getCode())
58 | .build();
59 |
60 | // 3.2 请求应答
61 | openAiSession.chatCompletions(chatCompletion, new EventSourceListener() {
62 | @Override
63 | public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type, @NotNull String data) {
64 | // 解析接收到的JSON数据为ChatCompletionResponse对象。
65 | ChatCompletionResponse chatCompletionResponse = JSON.parseObject(data, ChatCompletionResponse.class);
66 | // choices中有本次回复内容等信息
67 | List choices = chatCompletionResponse.getChoices();
68 | for (ChatChoice chatChoice : choices) {
69 | Message delta = chatChoice.getDelta();
70 | if (Constants.Role.ASSISTANT.getCode().equals(delta.getRole())) continue;
71 |
72 | // 应答完成
73 | String finishReason = chatChoice.getFinishReason();
74 | if (StringUtils.isNoneBlank(finishReason) && "stop".equals(finishReason)) {
75 | emitter.complete();
76 | break;
77 | }
78 |
79 | // 发送信息
80 | try {
81 | emitter.send(delta.getContent());
82 | } catch (Exception e) {
83 | throw new RuntimeException(e);
84 | }
85 | }
86 |
87 | }
88 | });
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/chatgpt-data-domain/src/main/java/com/luckysj/chatgpt/data/domain/openai/service/AbstractChatService.java:
--------------------------------------------------------------------------------
1 | package com.luckysj.chatgpt.data.domain.openai.service;
2 |
3 | import cn.bugstack.chatgpt.session.OpenAiSession;
4 | import com.fasterxml.jackson.core.JsonProcessingException;
5 | import com.luckysj.chatgpt.data.domain.openai.model.aggregates.ChatProcessAggregate;
6 | import com.luckysj.chatgpt.data.domain.openai.model.entity.RuleLogicEntity;
7 | import com.luckysj.chatgpt.data.domain.openai.model.entity.UserAccountQuotaEntity;
8 | import com.luckysj.chatgpt.data.domain.openai.model.valobj.LogicCheckTypeVO;
9 | import com.luckysj.chatgpt.data.domain.openai.repository.IOpenAiRepository;
10 | import com.luckysj.chatgpt.data.domain.openai.service.channel.OpenAiGroupService;
11 | import com.luckysj.chatgpt.data.domain.openai.service.channel.impl.ChatGLMService;
12 | import com.luckysj.chatgpt.data.domain.openai.service.channel.impl.ChatGPTService;
13 | import com.luckysj.chatgpt.data.domain.openai.service.rule.factory.DefaultLogicFactory;
14 | import com.luckysj.chatgpt.data.types.common.Constants;
15 | import com.luckysj.chatgpt.data.types.enums.OpenAiChannel;
16 | import com.luckysj.chatgpt.data.types.exception.ChatGPTException;
17 | import lombok.extern.slf4j.Slf4j;
18 | import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
19 |
20 | import javax.annotation.Resource;
21 | import java.util.HashMap;
22 | import java.util.Map;
23 |
24 | /**
25 | * @author www.luckysj.top 刘仕杰
26 | * @description 对话服务模板模式抽象类
27 | * @create 2023/12/04 19:50:27
28 | */
29 | @Slf4j
30 | public abstract class AbstractChatService implements IChatService {
31 |
32 | @Resource
33 | private IOpenAiRepository openAiRepository;
34 |
35 | // 对话服务组
36 | private final Map openAiGroup = new HashMap<>();
37 |
38 | public AbstractChatService(ChatGPTService chatGPTService, ChatGLMService chatGLMService) {
39 | openAiGroup.put(OpenAiChannel.ChatGPT, chatGPTService);
40 | openAiGroup.put(OpenAiChannel.ChatGLM, chatGLMService);
41 | }
42 |
43 |
44 | @Override
45 | public ResponseBodyEmitter completions(ResponseBodyEmitter emitter, ChatProcessAggregate chatProcess) {
46 | try {
47 | // 1.绑定应答回调事件
48 | emitter.onCompletion(() -> {
49 | log.info("流式问答请求完成,使用模型:{}", chatProcess.getModel());
50 | });
51 | emitter.onError(throwable -> log.error("流式问答请求错误,使用模型:{}", chatProcess.getModel(), throwable));
52 |
53 | // 2.查询账户
54 | UserAccountQuotaEntity userAccountQuotaEntity = openAiRepository.queryUserAccount(chatProcess.getOpenid());
55 |
56 | // 3.规则过滤,需要把ACCOUNT_STATUS放在最前面,防止账户不存在导致的Null异常
57 | RuleLogicEntity ruleLogicEntity = this.doCheckLogic(chatProcess, userAccountQuotaEntity,
58 | DefaultLogicFactory.LogicModel.ACCOUNT_STATUS.getCode(),
59 | DefaultLogicFactory.LogicModel.ACCESS_LIMIT.getCode(),
60 | DefaultLogicFactory.LogicModel.SENSITIVE_WORD.getCode(),
61 | DefaultLogicFactory.LogicModel.MODEL_TYPE.getCode(),
62 | DefaultLogicFactory.LogicModel.USER_QUOTA.getCode()
63 | );
64 | // 判断校验是否通过
65 | if (!LogicCheckTypeVO.SUCCESS.equals(ruleLogicEntity.getType())) {
66 | emitter.send(ruleLogicEntity.getInfo());
67 | emitter.complete();
68 | return emitter;
69 | }
70 |
71 | // 4.请求应答处理【使用策略模式判断使用GPT的会话还是其他模型的会话】
72 | openAiGroup.get(chatProcess.getChannel()).doMessageResponse(ruleLogicEntity.getData(), emitter);
73 |
74 | } catch (Exception e) {
75 | throw new ChatGPTException(Constants.ResponseCode.UN_ERROR.getCode(), e.getMessage());
76 | }
77 |
78 | // 3. 返回结果
79 | return emitter;
80 |
81 | }
82 |
83 | // 规则校验
84 | protected abstract RuleLogicEntity doCheckLogic(
85 | ChatProcessAggregate chatProcessAggregate, UserAccountQuotaEntity userAccountQuotaEntity,
86 | String... logics) throws Exception;
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/chatgpt-data-infrastructure/src/main/java/com/luckysj/chatgpt/data/infrastructure/repository/AuthRepository.java:
--------------------------------------------------------------------------------
1 | package com.luckysj.chatgpt.data.infrastructure.repository;
2 |
3 | import com.luckysj.chatgpt.data.domain.auth.repository.IAuthRepository;
4 | import com.luckysj.chatgpt.data.domain.openai.model.valobj.UserAccountStatusVO;
5 | import com.luckysj.chatgpt.data.infrastructure.dao.IUserAccountDao;
6 | import com.luckysj.chatgpt.data.infrastructure.po.UserAccountPO;
7 | import com.luckysj.chatgpt.data.infrastructure.redis.IRedisService;
8 | import lombok.extern.slf4j.Slf4j;
9 | import org.apache.commons.lang3.RandomStringUtils;
10 | import org.apache.commons.lang3.StringUtils;
11 | import org.redisson.api.RLock;
12 | import org.springframework.beans.factory.annotation.Value;
13 | import org.springframework.stereotype.Repository;
14 | import org.springframework.stereotype.Service;
15 |
16 | import javax.annotation.Resource;
17 | import java.util.concurrent.TimeUnit;
18 |
19 | @Slf4j
20 | @Repository
21 | public class AuthRepository implements IAuthRepository {
22 | @Resource
23 | private IUserAccountDao userAccountDao;
24 | @Resource
25 | private IRedisService redisService;
26 | @Value("${app.config.new-user-quota}")
27 | private Integer initialQuota; //新用户默认额度
28 | @Value("${app.config.new-user-model}")
29 | private String initialModel; //新用户默认模型
30 |
31 | // 验证码前缀
32 | private static final String Key = "weixin_code:";
33 |
34 | @Override
35 | public boolean insertUserIfNotExist(String openid) {
36 | // 首先判断是否已存在
37 | UserAccountPO userAccount = userAccountDao.queryUserAccount(openid);
38 | if(userAccount != null) return true;
39 |
40 | // 插入账号信息
41 | UserAccountPO userAccountPO = new UserAccountPO();
42 | userAccountPO.setOpenid(openid);
43 | userAccountPO.setTotalQuota(initialQuota);
44 | userAccountPO.setSurplusQuota(initialQuota);
45 | userAccountPO.setModelTypes(initialModel);
46 | userAccountPO.setStatus(UserAccountStatusVO.AVAILABLE.getCode());
47 | return userAccountDao.insert(userAccountPO) > 0 ? true : false;
48 | }
49 |
50 | @Override
51 | public String getCodeUserOpenId(String code) {
52 | return redisService.getValue(Key + code);
53 | }
54 |
55 | @Override
56 | public void removeCodeByOpenId(String code, String openId) {
57 | redisService.remove(Key + openId);
58 | redisService.remove(Key + code);
59 | }
60 |
61 | @Override
62 | public String genCodeTest(String openid) {
63 | // 首先判断是否已经存在验证码
64 | String codeOld = redisService.getValue(Key + openid);
65 | if(StringUtils.isNoneBlank(codeOld)){
66 | return codeOld;
67 | }
68 |
69 | // 获取锁,防止多次验证码生成
70 | RLock lock = redisService.getLock(Key);
71 | try {
72 | // 上锁并设置超时时间为15秒
73 | lock.lock(15, TimeUnit.SECONDS);
74 |
75 | // 生成验证码,这里可能会存在验证码重复的问题,因为我们登录只需要验证码,如果验证码重复,重新生成
76 | String code = RandomStringUtils.randomNumeric(6);
77 |
78 | // 防重校验&重新生成
79 | for (int i = 0; i < 10 && StringUtils.isNotBlank(redisService.getValue(Key + "_" + code)); i++) {
80 | if (i < 3) {
81 | code = RandomStringUtils.randomNumeric(6);
82 | log.warn("验证码重复,生成6位字符串验证码 {} {}", openid, code);
83 | } else if (i < 5) {
84 | code = RandomStringUtils.randomNumeric(7);
85 | log.warn("验证码重复,生成7位字符串验证码 {} {}", openid, code);
86 | } else if (i < 9) {
87 | code = RandomStringUtils.randomNumeric(8);
88 | log.warn("验证码重复,生成8位字符串验证码 {} {}", openid, code);
89 | } else {
90 | return "";
91 | }
92 | }
93 |
94 | // 保存验证码到缓存
95 | redisService.setValue(Key + openid, code, 5 * 60 * 1000);
96 | redisService.setValue(Key + code, openid, 5 * 60 * 1000 );
97 | return code;
98 | } finally {
99 | lock.unlock();
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/chatgpt-data-trigger/src/main/java/com/luckysj/chatgpt/data/trigger/http/ChatGPTAIServiceController.java:
--------------------------------------------------------------------------------
1 | package com.luckysj.chatgpt.data.trigger.http;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.luckysj.chatgpt.data.domain.auth.service.IAuthService;
5 | import com.luckysj.chatgpt.data.domain.openai.model.aggregates.ChatProcessAggregate;
6 | import com.luckysj.chatgpt.data.domain.openai.model.entity.MessageEntity;
7 | import com.luckysj.chatgpt.data.domain.openai.service.IChatService;
8 | import com.luckysj.chatgpt.data.trigger.http.dto.ChatGPTRequestDTO;
9 | import com.luckysj.chatgpt.data.types.common.Constants;
10 | import com.luckysj.chatgpt.data.types.exception.ChatGPTException;
11 | import io.micrometer.core.annotation.Timed;
12 | import lombok.extern.slf4j.Slf4j;
13 | import org.springframework.web.bind.annotation.*;
14 | import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
15 |
16 | import javax.annotation.Resource;
17 | import javax.servlet.http.HttpServletResponse;
18 | import java.io.IOException;
19 | import java.util.stream.Collectors;
20 |
21 | /**
22 | * @author www.luckysj.top 刘仕杰
23 | * @description chatgpt对话服务接口
24 | * @create 2023/12/04 19:28:04
25 | */
26 | @Slf4j
27 | @RestController()
28 | @CrossOrigin("${app.config.cross-origin}")
29 | @RequestMapping("/api/${app.config.api-version}/")
30 | public class ChatGPTAIServiceController {
31 |
32 | @Resource
33 | private IChatService chatService;
34 |
35 | @Resource
36 | private IAuthService authService;
37 |
38 | // 接口测试 curl -X POST http://localhost:7070/api/v1/chatgpt/chat/completions -H "Content-Type: application/json" -H "Authorization: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMTQ1MTQiLCJvcGVuSWQiOiIxMTQ1MTQiLCJleHAiOjE3MDkyOTcxOTAsImlhdCI6MTcwODY5MjM5MCwianRpIjoiOWYyNjFiMjgtNjNiOC00YzAwLWJmZDMtNmI1NDRiYzc2YTE4In0.x9eXcOJr6QQYn81397ch7T2Ejk_75pybXSS-5RixOfc" -d "{\"messages\":[{\"content\":\"写一个java冒泡排序\",\"role\":\"user\"}],\"model\":\"gpt-3.5-turbo\"}"
39 | @Timed(value="chat_completions_http",description="请求对话接口")
40 | @PostMapping(value = "chatgpt/chat/completions")
41 | public ResponseBodyEmitter completionsStream(@RequestBody ChatGPTRequestDTO request, @RequestHeader("Authorization") String token, HttpServletResponse response) {
42 | log.info("流式问答请求开始,使用模型:{} 请求信息:{}", request.getModel(), JSON.toJSONString(request.getMessages()));
43 | try {
44 | // 1. 基础配置;流式输出、编码、禁用缓存
45 | response.setContentType("text/event-stream");
46 | response.setCharacterEncoding("UTF-8");
47 | response.setHeader("Cache-Control", "no-cache");
48 |
49 | // 2.构造异步响应对象,token验证
50 | ResponseBodyEmitter emitter = new ResponseBodyEmitter(3 * 60 * 1000L);
51 | boolean authResult = authService.checkToken(token);
52 | // token验证不通过,返回错误码,关闭异步响应
53 | if (!authResult) {
54 | try {
55 | emitter.send(Constants.ResponseCode.TOKEN_ERROR.getCode());
56 | } catch (IOException e) {
57 | throw new RuntimeException(e);
58 | }
59 | emitter.complete();
60 | return emitter;
61 | }
62 |
63 | // 3.获取openid
64 | String openid = authService.parseOpenid(token);
65 |
66 | // 2. 构建 贯穿整个业务流程的聚合对象
67 | ChatProcessAggregate chatProcessAggregate = ChatProcessAggregate.builder()
68 | .openid(openid)
69 | .model(request.getModel())
70 | .messages(request.getMessages().stream()
71 | .map(entity -> MessageEntity.builder()
72 | .role(entity.getRole())
73 | .content(entity.getContent())
74 | .name(entity.getName())
75 | .build())
76 | .collect(Collectors.toList()))
77 | .build();
78 |
79 | // 3. 请求结果&返回
80 | return chatService.completions(emitter, chatProcessAggregate);
81 | } catch (Exception e) {
82 | log.error("流式应答,请求模型:{} 发生异常 {}", request.getModel(), e.getMessage());
83 | throw new ChatGPTException(e.getMessage());
84 | }
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/chatgpt-data-infrastructure/src/main/java/com/luckysj/chatgpt/data/infrastructure/redis/RedisService.java:
--------------------------------------------------------------------------------
1 | package com.luckysj.chatgpt.data.infrastructure.redis;
2 |
3 | import org.redisson.api.*;
4 | import org.springframework.stereotype.Service;
5 |
6 | import javax.annotation.Resource;
7 | import java.time.Duration;
8 |
9 | @Service("redissonService")
10 | public class RedisService implements IRedisService{
11 | @Resource
12 | private RedissonClient redissonClient;
13 |
14 | public void setValue(String key, T value) {
15 | redissonClient.getBucket(key).set(value);
16 | }
17 |
18 | @Override
19 | public void setValue(String key, T value, long expired) {
20 | RBucket bucket = redissonClient.getBucket(key);
21 | bucket.set(value, Duration.ofMillis(expired));
22 | }
23 |
24 | public T getValue(String key) {
25 | return redissonClient.getBucket(key).get();
26 | }
27 |
28 | @Override
29 | public RQueue getQueue(String key) {
30 | return redissonClient.getQueue(key);
31 | }
32 |
33 | @Override
34 | public RBlockingQueue getBlockingQueue(String key) {
35 | return redissonClient.getBlockingQueue(key);
36 | }
37 |
38 | @Override
39 | public RDelayedQueue getDelayedQueue(RBlockingQueue rBlockingQueue) {
40 | return redissonClient.getDelayedQueue(rBlockingQueue);
41 | }
42 |
43 | @Override
44 | public long incr(String key) {
45 | return redissonClient.getAtomicLong(key).incrementAndGet();
46 | }
47 |
48 | @Override
49 | public long incrBy(String key, long delta) {
50 | return redissonClient.getAtomicLong(key).addAndGet(delta);
51 | }
52 |
53 | @Override
54 | public long decr(String key) {
55 | return redissonClient.getAtomicLong(key).decrementAndGet();
56 | }
57 |
58 | @Override
59 | public long decrBy(String key, long delta) {
60 | return redissonClient.getAtomicLong(key).addAndGet(-delta);
61 | }
62 |
63 | @Override
64 | public void remove(String key) {
65 | redissonClient.getBucket(key).delete();
66 | }
67 |
68 | @Override
69 | public boolean isExists(String key) {
70 | return redissonClient.getBucket(key).isExists();
71 | }
72 |
73 | public void addToSet(String key, String value) {
74 | RSet set = redissonClient.getSet(key);
75 | set.add(value);
76 | }
77 |
78 | public boolean isSetMember(String key, String value) {
79 | RSet set = redissonClient.getSet(key);
80 | return set.contains(value);
81 | }
82 |
83 | public void addToList(String key, String value) {
84 | RList list = redissonClient.getList(key);
85 | list.add(value);
86 | }
87 |
88 | public String getFromList(String key, int index) {
89 | RList list = redissonClient.getList(key);
90 | return list.get(index);
91 | }
92 |
93 | public void addToMap(String key, String field, String value) {
94 | RMap map = redissonClient.getMap(key);
95 | map.put(field, value);
96 | }
97 |
98 | public String getFromMap(String key, String field) {
99 | RMap map = redissonClient.getMap(key);
100 | return map.get(field);
101 | }
102 |
103 | public void addToSortedSet(String key, String value) {
104 | RSortedSet sortedSet = redissonClient.getSortedSet(key);
105 | sortedSet.add(value);
106 | }
107 |
108 | @Override
109 | public RLock getLock(String key) {
110 | return redissonClient.getLock(key);
111 | }
112 |
113 | @Override
114 | public RLock getFairLock(String key) {
115 | return redissonClient.getFairLock(key);
116 | }
117 |
118 | @Override
119 | public RReadWriteLock getReadWriteLock(String key) {
120 | return redissonClient.getReadWriteLock(key);
121 | }
122 |
123 | @Override
124 | public RSemaphore getSemaphore(String key) {
125 | return redissonClient.getSemaphore(key);
126 | }
127 |
128 | @Override
129 | public RPermitExpirableSemaphore getPermitExpirableSemaphore(String key) {
130 | return redissonClient.getPermitExpirableSemaphore(key);
131 | }
132 |
133 | @Override
134 | public RCountDownLatch getCountDownLatch(String key) {
135 | return redissonClient.getCountDownLatch(key);
136 | }
137 |
138 | @Override
139 | public RBloomFilter getBloomFilter(String key) {
140 | return redissonClient.getBloomFilter(key);
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/chatgpt-data-trigger/src/main/java/com/luckysj/chatgpt/data/trigger/job/NoPayNotifyOrderJob.java:
--------------------------------------------------------------------------------
1 | package com.luckysj.chatgpt.data.trigger.job;
2 |
3 | import cn.hutool.json.JSONObject;
4 | import com.alibaba.fastjson.JSON;
5 | import com.alipay.api.AlipayClient;
6 | import com.alipay.api.request.AlipayTradeQueryRequest;
7 | import com.alipay.api.response.AlipayTradeQueryResponse;
8 | import com.google.common.eventbus.EventBus;
9 |
10 | import com.luckysj.chatgpt.data.domain.order.model.valobj.AliPayTradeTypeVo;
11 | import com.luckysj.chatgpt.data.domain.order.service.IOrderService;
12 | import com.luckysj.chatgpt.data.types.common.Constants;
13 | import io.micrometer.core.annotation.Timed;
14 | import lombok.extern.slf4j.Slf4j;
15 | import org.redisson.api.RTopic;
16 | import org.springframework.amqp.rabbit.core.RabbitTemplate;
17 | import org.springframework.beans.factory.annotation.Autowired;
18 | import org.springframework.beans.factory.annotation.Value;
19 | import org.springframework.scheduling.annotation.Scheduled;
20 | import org.springframework.stereotype.Component;
21 |
22 | import javax.annotation.Resource;
23 | import java.math.BigDecimal;
24 | import java.math.RoundingMode;
25 | import java.text.SimpleDateFormat;
26 | import java.util.Date;
27 | import java.util.List;
28 | import java.util.Map;
29 |
30 | /**
31 | * @author www.luckysj.top 刘仕杰
32 | * @description 检测未接收到或未正确处理的支付回调通知
33 | * @create 2023/12/15 15:21:54
34 | */
35 | @Slf4j
36 | @Component()
37 | public class NoPayNotifyOrderJob {
38 |
39 | @Resource
40 | private IOrderService orderService;
41 |
42 | // @Resource
43 | // private EventBus eventBus;
44 |
45 | // @Resource(name = "delivery")
46 | // private RTopic redisTopic;
47 |
48 | @Resource
49 | private RabbitTemplate rabbitTemplate;
50 |
51 | @Resource
52 | private AlipayClient alipayClient;
53 |
54 | private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
55 |
56 | @Timed(value="no_pay_notify_order_job",description="定时任务,订单支付状态更新")
57 | @Scheduled(cron = "0 0/3 * * * ?") //三分钟执行一次
58 | public void exec() {
59 | try {
60 | List orderIds = orderService.queryNoPayNotifyOrder();
61 | if (orderIds.isEmpty()) {
62 | log.info("定时任务,订单支付状态更新,暂无未更新订单 orderId is null");
63 | return;
64 | }
65 | for (String orderId : orderIds) {
66 | // 查询结果
67 | AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
68 | JSONObject bizContent = new JSONObject();
69 | bizContent.put("out_trade_no", orderId);
70 | request.setBizContent(bizContent.toString());
71 | AlipayTradeQueryResponse response = alipayClient.execute(request);
72 | if (response.isSuccess()) {
73 | String resultJson = response.getBody();
74 | //转map
75 | Map resultMap = JSON.parseObject(resultJson, Map.class);
76 | Map alipay_trade_query_response = (Map) resultMap.get("alipay_trade_query_response");
77 | //支付结果
78 | String trade_status = (String) alipay_trade_query_response.get("trade_status");
79 | log.info("定时任务,查询支付结果查询成功,支付状态为{},orderId is {}", trade_status, orderId);
80 |
81 | if(AliPayTradeTypeVo.TRADE_SUCCESS.getCode().equals(trade_status)){
82 | String transactionId =response.getTradeNo();
83 | String totalAmountStr = response.getTotalAmount();
84 | BigDecimal totalAmount = new BigDecimal(totalAmountStr);
85 | Date successTime = response.getSendPayDate();
86 | boolean isSuccess = orderService.changeOrderPaySuccess(orderId, transactionId, totalAmount, successTime);
87 | if (isSuccess) {
88 | // 发布消息
89 | // eventBus.post(orderId);
90 | // redisTopic.publish(orderId);
91 | // rabbitTemplate.convertAndSend(Constants.MessageQueueKey.DeliveryExchange, Constants.MessageQueueKey.DeliveryKey, orderId);
92 | orderService.publishDeliveryMessage(orderId); //发布发货消息
93 | }
94 | }
95 | } else {
96 | log.info("定时任务,查询支付结果失败,失败原因:{},orderId is {}",response.getSubMsg(), orderId);
97 | continue;
98 | }
99 |
100 | }
101 | } catch (Exception e) {
102 | log.error("定时任务,订单支付状态更新失败", e);
103 | }
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/chatgpt-data-app/src/main/java/com/luckysj/chatgpt/data/config/RedisClientConfig.java:
--------------------------------------------------------------------------------
1 | package com.luckysj.chatgpt.data.config;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.alibaba.fastjson.serializer.SerializerFeature;
5 | import com.luckysj.chatgpt.data.types.annotation.RedisTopic;
6 | import io.netty.buffer.ByteBuf;
7 | import io.netty.buffer.ByteBufAllocator;
8 | import io.netty.buffer.ByteBufInputStream;
9 | import io.netty.buffer.ByteBufOutputStream;
10 | import org.redisson.Redisson;
11 | import org.redisson.api.RTopic;
12 | import org.redisson.api.RedissonClient;
13 | import org.redisson.api.listener.MessageListener;
14 | import org.redisson.client.codec.BaseCodec;
15 | import org.redisson.client.protocol.Decoder;
16 | import org.redisson.client.protocol.Encoder;
17 | import org.redisson.config.Config;
18 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
19 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
20 | import org.springframework.context.ConfigurableApplicationContext;
21 | import org.springframework.context.annotation.Bean;
22 | import org.springframework.context.annotation.Configuration;
23 |
24 | import java.io.IOException;
25 |
26 | /**
27 | * @author www.luckysj.top 刘仕杰
28 | * @description redis客户端
29 | * @create 2023/12/17 20:49:41
30 | */
31 | @Configuration
32 | @EnableConfigurationProperties(RedisCientConfigProperties.class)
33 | public class RedisClientConfig {
34 |
35 | @Bean("redissonClient")
36 | public RedissonClient redissonClient(ConfigurableApplicationContext applicationContext, RedisCientConfigProperties properties) {
37 | Config config = new Config();
38 | // 根据需要可以设定编解码器;https://github.com/redisson/redisson/wiki/4.-%E6%95%B0%E6%8D%AE%E5%BA%8F%E5%88%97%E5%8C%96
39 | // config.setCodec(new RedisCodec());
40 |
41 | config.useSingleServer()
42 | .setAddress("redis://" + properties.getHost() + ":" + properties.getPort())
43 | .setPassword(properties.getPassword())
44 | .setConnectionPoolSize(properties.getPoolSize())
45 | .setConnectionMinimumIdleSize(properties.getMinIdleSize())
46 | .setIdleConnectionTimeout(properties.getIdleTimeout())
47 | .setConnectTimeout(properties.getConnectTimeout())
48 | .setRetryAttempts(properties.getRetryAttempts())
49 | .setRetryInterval(properties.getRetryInterval())
50 | .setPingConnectionInterval(properties.getPingInterval())
51 | .setKeepAlive(properties.isKeepAlive())
52 | ;
53 |
54 | RedissonClient redissonClient = Redisson.create(config);
55 |
56 | // 注册消息发布订阅主题Topic
57 | // 找到所有实现了Redisson中MessageListener接口的bean名字
58 | String[] beanNamesForType = applicationContext.getBeanNamesForType(MessageListener.class);
59 | for (String beanName : beanNamesForType) {
60 | // 通过bean名字获取到监听bean
61 | MessageListener bean = applicationContext.getBean(beanName, MessageListener.class);
62 |
63 | Class extends MessageListener> beanClass = bean.getClass();
64 |
65 | // 如果bean的注解里包含我们的自定义注解RedisTopic.class,则以RedisTopic注解的值作为name将该bean注册到bean工厂,方便在别处注入
66 | if (beanClass.isAnnotationPresent(RedisTopic.class)) {
67 | RedisTopic redisTopic = beanClass.getAnnotation(RedisTopic.class);
68 |
69 | RTopic topic = redissonClient.getTopic(redisTopic.topic());
70 | topic.addListener(String.class, bean);
71 |
72 | ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
73 | beanFactory.registerSingleton(redisTopic.topic(), topic);
74 | }
75 | }
76 |
77 | return redissonClient;
78 | }
79 |
80 | static class RedisCodec extends BaseCodec {
81 |
82 | private final Encoder encoder = in -> {
83 | ByteBuf out = ByteBufAllocator.DEFAULT.buffer();
84 | try {
85 | ByteBufOutputStream os = new ByteBufOutputStream(out);
86 | JSON.writeJSONString(os, in, SerializerFeature.WriteClassName);
87 | return os.buffer();
88 | } catch (IOException e) {
89 | out.release();
90 | throw e;
91 | } catch (Exception e) {
92 | out.release();
93 | throw new IOException(e);
94 | }
95 | };
96 |
97 | private final Decoder