chenbiao há 2 anos atrás
pai
commit
3416215bce

+ 11 - 0
netflix-dao/src/main/java/com/cyksj/mapper/ChatgptUserConversationRecordMapper.java

@@ -0,0 +1,11 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.ChatgptUserConversationRecord;
+
+/**
+ * @author chan
+ * @date 2024/4/2 18:12
+ */
+public interface ChatgptUserConversationRecordMapper extends BaseMapper<ChatgptUserConversationRecord> {
+}

+ 11 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/ChatgptSession.java

@@ -41,6 +41,17 @@ public class ChatgptSession extends BaseEntity {
     @TableField("accountId")
     private Long accountId;
 
+    /**
+     * 车id
+     */
+    @TableField("carID")
+    private String carId;
+
+    /**
+     * 车名称
+     */
+    private String cardName;
+
     /**
      * 官方session
      */

+ 14 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/ChatgptUser.java

@@ -61,4 +61,18 @@ public class ChatgptUser extends BaseEntity {
      */
     private String remark;
 
+    /**
+     * 限制次数
+     */
+    private Integer limit;
+
+    /**
+     * 限制时间
+     */
+    private Long limitTime;
+
+    /**
+     * 是否为车队
+     */
+    private Boolean isCar;
 }

+ 49 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/ChatgptUserConversationRecord.java

@@ -0,0 +1,49 @@
+package com.cyksj.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Data;
+
+/**
+ * @author chan
+ * @date 2024/3/18 17:10
+ */
+@Data
+@TableName("chatgpt_user_conversation_record")
+@SearchBean(tables = "chatgpt_user_conversation_record")
+public class ChatgptUserConversationRecord extends BaseEntity {
+
+
+    /**
+     * 凭证
+     */
+    private String userToken;
+
+    /**
+     * 会话id
+     */
+    private String conversationId;
+
+    /**
+     * 消息id
+     */
+    private String messageId;
+
+    /**
+     * 模型
+     */
+    private String model;
+
+    /**
+     * 车次id
+     */
+    private String carId;
+
+    /**
+     * 车次名称
+     */
+    private String carName;
+
+
+}

+ 107 - 0
netflix-dao/src/main/java/com/cyksj/model/request/gpt/ConversationRequest.java

@@ -0,0 +1,107 @@
+package com.cyksj.model.request.gpt;
+
+import lombok.Data;
+import org.checkerframework.checker.units.qual.A;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * @author chan
+ * @date 2024/4/1 18:36
+ */
+@Data
+public class ConversationRequest {
+     /* Conversation 对象
+    {
+    "action": "next",
+    "messages": [
+        {
+            "id": "aaa2a3ac-f825-460f-ac20-bfb039b99257",
+            "author": {
+                "role": "user"
+            },
+            "content": {
+                "content_type": "text",
+                "parts": [
+                    "你好"
+                ]
+            },
+            "metadata": {
+
+            }
+        }
+    ],
+    "parent_message_id": "aaa13a0c-b7cc-409f-a90f-d99f9e9488d6",
+    "model": "gpt-4",
+    "timezone_offset_min": -480,
+    "suggestions": [
+        "What can I do in Paris for 5 days, if I'm especially interested in fashion?",
+        "Write a short-and-sweet text message inviting my neighbor to a barbecue.",
+        "Can you brainstorm some edge cases for a function that takes birthdate as input and returns the horoscope?",
+        "Make up a 5-sentence story about \"Sharky\", a tooth-brushing shark superhero. Make each sentence a bullet point."
+    ],
+    "history_and_training_disabled": false,
+    "conversation_mode": {
+        "kind": "primary_assistant"
+    },
+    "force_paragen": false,
+    "force_paragen_model_slug": "",
+    "force_nulligen": false,
+    "force_rate_limit": false,
+    "websocket_request_id": "171e7569-71a1-4d11-9b91-6a7f02093567"
+    */
+
+    private String action;
+    private String conversation_id;
+    private List<Message> messages;
+    private String parent_message_id;
+    private String model;
+    private int timezone_offset_min;
+    private List<String> suggestions;
+    private boolean history_and_training_disabled;
+    private ConversationMode conversation_mode;
+    private boolean force_paragen;
+    private String force_paragen_model_slug;
+    private boolean force_nulligen;
+    private boolean force_rate_limit;
+    private String websocket_request_id;
+
+    private String cardId;
+
+    // Inner class for Message
+    @Data
+    public static class Message {
+        private String id;
+        private Author author;
+        private Content content;
+        private Map<String, Object> metadata;
+
+        // Inner class for Author
+        public static class Author {
+            private String role;
+            // Getters and setters
+        }
+
+        // Inner class for Content
+        public static class Content {
+            private String content_type;
+            private List<String> parts;
+            // Getters and setters
+        }
+
+        // Getters and setters
+    }
+
+    // Inner class for ConversationMode
+    public static class ConversationMode {
+        private String kind;
+        // Getters and setters
+    }
+
+    // Getters and setters
+
+
+
+}

+ 41 - 0
netflix-service/src/main/java/com/cyksj/redis/RedisService.java

@@ -594,6 +594,44 @@ public class RedisService {
         return false;
     }
 
+
+    /**
+     * 添加元素到ZSet
+     */
+    public Boolean zAdd(String key, double score, Object value) {
+        try {
+            return redisTemplate.opsForZSet().add(key, value, score);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * 获取ZSet的大小
+     */
+    public Long zCard(String key) {
+        try {
+            return redisTemplate.opsForZSet().zCard(key);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 删除ZSet中分数范围内的元素
+     */
+    public Long zRemoveRangeByScore(String key, double min, double max) {
+        try {
+            return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0L;
+        }
+    }
+
+
     public Set<String> keys(String name) {
         return redisTemplate.keys(name);
     }
@@ -671,6 +709,9 @@ public class RedisService {
         WX_INDENT_BIND_KEY("wx_indent_bind:key:", "微信身份授权绑定key", 60 * 60 * 24),
         EQUIPMENT_USER_SERVICE_RECORD("equipment_user_service_record:", "用户设备客服获取记录key", 60 * 60 * 24),
         INFORMATION_KEY("information_key:", "文章", 30 * 60L),
+        CHATGPT_CONVERSATION_LIMIT("chatgpt:conversation:limit:", "chatGpt 对话限制", 3 * 60 * 60L),
+
+        CHATGPT_CAR_CONVERSATION_LIMIT("chatgpt:conversation:car:", "chatGpt 车队对话数量", 3 * 60 * 60L),
         ;
 
         private String name;

+ 16 - 0
netflix-service/src/main/java/com/cyksj/service/chatgpt/ChatGptAccountService.java

@@ -1,6 +1,10 @@
 package com.cyksj.service.chatgpt;
 
 import com.cyksj.model.entity.Account;
+import com.cyksj.model.entity.ChatgptUser;
+import com.cyksj.model.request.gpt.ConversationRequest;
+
+import java.time.LocalDateTime;
 
 /**
  * @author chan
@@ -12,4 +16,16 @@ public interface ChatGptAccountService {
     void upAccount(Account account);
 
     String getLoginUrl(Long userId, Long relationId);
+
+    ChatgptUser findByUserTokenAndExpireTimeAfter(String userToken, LocalDateTime now);
+
+    /**
+     * 对话限制
+     * @param userToken 用户token
+     * @param model 对话模型
+     * @return 是否限制
+     */
+    boolean conversationLimit(String userToken, String model);
+
+    void saveConversationRecord(String userToken, ConversationRequest conversationRequest);
 }

+ 126 - 0
netflix-service/src/main/java/com/cyksj/service/chatgpt/impl/ChatGptAccountServiceImpl.java

@@ -12,6 +12,8 @@ import com.cyksj.common.exception.BusinessRuntimeException;
 import com.cyksj.common.util.StringUtil;
 import com.cyksj.mapper.*;
 import com.cyksj.model.entity.*;
+import com.cyksj.model.request.gpt.ConversationRequest;
+import com.cyksj.redis.RedisService;
 import com.cyksj.service.chatgpt.ChatGptAccountService;
 import com.cyksj.service.user.UserBindRelationService;
 import lombok.RequiredArgsConstructor;
@@ -23,6 +25,7 @@ import org.springframework.stereotype.Service;
 import javax.imageio.stream.FileImageOutputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.time.LocalDateTime;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -41,6 +44,12 @@ public class ChatGptAccountServiceImpl implements ChatGptAccountService {
 
     private static final String GPT_PROXY = "https://chat-chan-87jztgkf257d.xyhelper.net";
 
+    private static final int MAX_REQUESTS = 40; // 3小时内最大请求次数
+
+    private static final long WINDOW_SIZE = 3 * 60 * 60; // 3小时窗口的秒数
+
+    private final RedisService redisService;
+
     private final ChatgptUserMapper chatgptUserMapper;
 
     private final ChatgptSessionMapper chatgptSessionMapper;
@@ -55,6 +64,7 @@ public class ChatGptAccountServiceImpl implements ChatGptAccountService {
 
     private final UserBindRelationService userBindRelationService;
 
+    private final ChatgptUserConversationRecordMapper chatgptUserConversationRecordMapper;
 
     @Override
     public String addAccount(Account account) {
@@ -255,5 +265,121 @@ public class ChatGptAccountServiceImpl implements ChatGptAccountService {
         }
     }
 
+    @Override
+    public ChatgptUser findByUserTokenAndExpireTimeAfter(String userToken, LocalDateTime now) {
+        return chatgptUserMapper.selectOne(Wrappers.lambdaQuery(ChatgptUser.class).eq(ChatgptUser::getUserToken, userToken).gt(ChatgptUser::getExpireTime, now));
+    }
+
+    @Override
+    public boolean conversationLimit(String userToken, String model) {
+        boolean limit = true;
+        if("gpt-4".equals(model)){
+            ChatgptUser chatgptUser = chatgptUserMapper.selectOne(Wrappers.lambdaQuery(ChatgptUser.class).eq(ChatgptUser::getUserToken, userToken));
+            if (chatgptUser == null) {
+                return false;
+            }
+            //如果不为车队 直接返回
+            if(!chatgptUser.getIsCar()){
+                return true;
+            }
+            limit = isConversationAllowed(userToken, chatgptUser.getLimit(), chatgptUser.getLimitTime());
+            //车队次数记录
+
+        }
+        return limit;
+    }
+
+    @Override
+    public void saveConversationRecord(String userToken, ConversationRequest conversationRequest) {
+        ChatgptSession chatgptSession = chatgptSessionMapper.selectOne(Wrappers.lambdaQuery(ChatgptSession.class).eq(ChatgptSession::getCarId, conversationRequest.getCardId()));
+        ChatgptUserConversationRecord chatgptUserConversationRecord = new ChatgptUserConversationRecord();
+        chatgptUserConversationRecord.setUserToken(userToken);
+        chatgptUserConversationRecord.setCarId(chatgptSession.getCarId());
+        chatgptUserConversationRecord.setCarName(chatgptSession.getCardName());
+        if(StringUtils.isBlank(conversationRequest.getConversation_id())){
+            chatgptUserConversationRecord.setMessageId(conversationRequest.getMessages().get(0).getId());
+        }else {
+            chatgptUserConversationRecord.setConversationId(conversationRequest.getConversation_id());
+        }
+
+        chatgptUserConversationRecord.setModel(conversationRequest.getModel());
+        chatgptUserConversationRecordMapper.insert(chatgptUserConversationRecord);
+    }
+
+    /**
+     * 检查是否允许进行进行提问
+     *
+     * @param userToken 用户token
+     * @return true 如果允许请求,false 如果请求被限制
+     */
+    public boolean isConversationAllowed(String userToken, int limit, Long limitTime) {
+        String key = RedisService.key.CHATGPT_CONVERSATION_LIMIT.getName() + ":" + userToken;
+        long currentTimeMillis = System.currentTimeMillis();
+        long windowStartMillis = currentTimeMillis - (limitTime * 60 * 60) * 1000;
+
+        // 清除时间窗口之前的请求记录
+        redisService.zRemoveRangeByScore(key, 0, windowStartMillis);
+
+        Long currentSize = redisService.zCard(key);
+        if (currentSize != null && currentSize >= limit) {
+            // 如果当前请求次数超过限制,则拒绝请求
+            return false;
+        } else {
+            // 如果未超过限制,记录当前请求的时间戳
+            redisService.zAdd(key, currentTimeMillis, currentTimeMillis);
+            // 设置ZSet的过期时间,窗口大小加上一段冗余时间
+            redisService.expire(key, (limitTime * 60 * 60) + 20);
+            return true;
+        }
+    }
+
+
+    /**
+     * 记录车队的提问次数
+     * @param userToken
+     * @param limit
+     * @param limitTime
+     * @return
+     */
+    public boolean cardConversationRecord(String cardid) {
+        String key = RedisService.key.CHATGPT_CAR_CONVERSATION_LIMIT.getName() + ":" + cardid;
+        long currentTimeMillis = System.currentTimeMillis();
+        long windowStartMillis = currentTimeMillis - (WINDOW_SIZE) * 1000;
+
+        // 清除时间窗口之前的请求记录
+        redisService.zRemoveRangeByScore(key, 0, windowStartMillis);
+
+        Long currentSize = redisService.zCard(key);
+        if (currentSize != null && currentSize >= MAX_REQUESTS) {
+            // 如果当前请求次数超过限制,则拒绝请求
+            return false;
+        } else {
+            // 如果未超过限制,记录当前请求的时间戳
+            redisService.zAdd(key, currentTimeMillis, currentTimeMillis);
+            // 设置ZSet的过期时间,窗口大小加上一段冗余时间
+            redisService.expire(key, (WINDOW_SIZE * 60 * 60) + 20);
+            return true;
+        }
+    }
+
+    /**
+     * 获取指定用户ID在滑动窗口内的提问次数
+     *
+     * @param userToken 用户token
+     * @return 滑动窗口内的请求次数
+     */
+    public Long getConversationCount(String userToken, Long limitTime) {
+        String key = RedisService.key.CHATGPT_CONVERSATION_LIMIT.getName() + ":" + userToken;
+        long currentTimeMillis = System.currentTimeMillis();
+        long windowStartMillis = currentTimeMillis -  (limitTime * 60 * 60) * 1000;
+
+        // 清除时间窗口之前的请求记录(可选,根据需要决定是否在此处清理过期记录)
+        redisService.zRemoveRangeByScore(key, 0, windowStartMillis);
+
+        // 获取当前窗口内的请求次数
+        Long currentSize = redisService.zCard(key);
+        return currentSize != null ? currentSize : 0L;
+    }
+
 }
 

+ 0 - 2
netflix-service/src/main/java/com/cyksj/service/order/post/impl/OEPostDataService.java

@@ -27,8 +27,6 @@ import java.util.Map;
 public class OEPostDataService extends AbstractPostDataService {
 
 
-
-
     @Override
     public void postData(Long orderId, String callback, String evenType) throws Exception {
 

+ 87 - 4
netflix-web/src/main/java/com/cyksj/web/controller/mirror/MirrorController.java

@@ -1,19 +1,28 @@
 package com.cyksj.web.controller.mirror;
 
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.util.IoKit;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.entity.ChatgptSession;
+import com.cyksj.model.entity.ChatgptUser;
+import com.cyksj.model.request.gpt.ConversationRequest;
 import com.cyksj.service.chatgpt.ChatGptAccountService;
 import com.cyksj.web.util.StpUserUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
 
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.io.OutputStream;
+import java.time.LocalDateTime;
 
 /**
  * /server/镜像服务
+ *
  * @author chan
  * @date 2024/3/19 14:41
  */
@@ -27,9 +36,12 @@ public class MirrorController {
 
     private final HttpServletResponse response;
 
+    private final HttpServletRequest request;
+
 
     /**
      * GPT车票跳转登录
+     *
      * @param relationId 车票id
      */
     @GetMapping("/chatGptMirror/{relationId}")
@@ -38,4 +50,75 @@ public class MirrorController {
 
         response.sendRedirect(chatGptAccountService.getLoginUrl(userId, relationId));
     }
+
+    /**
+     * GPT车票跳转登录
+     * @param userToken 用户token
+     * @param carId 车票id
+     * @return 登陆结果
+     */
+    @GetMapping("/gpt/oauth")
+    public Result<String> gptOauth(@RequestParam("usertoken") String userToken, @RequestParam("carid") String carId) {
+        try {
+            ChatgptUser user = chatGptAccountService.findByUserTokenAndExpireTimeAfter(userToken, LocalDateTime.now());
+            if (user == null) {
+                return GatewayResponse.SUCCESS.newBuilder().setMsg("用户不存在或已过期").setCode(0).toResult();
+            }
+
+            ChatgptSession session = chatGptAccountService.checkSession(carId);
+            if (user.isPlus() || (user.isPlus() == carInfo.isPlus())) {
+                return GatewayResponse.SUCCESS.newBuilder().setMsg("登陆成功").setCode(1).toResult();
+            } else {
+                return GatewayResponse.FAIL.newBuilder().setMsg("您不是PLUS用户,无法登陆该车").setCode(0).toResult();
+            }
+        } catch (Exception e) {
+            logger.error("服务器错误", e);
+            return GatewayResponse.FAIL.newBuilder().setMsg("服务器错误").setCode(0).toResult();
+        }
+    }
+
+
+    /**
+     * 会话限制
+     */
+    @RequestMapping("/gpt/conversation/limit")
+    public void conversationLimit(@RequestBody ConversationRequest conversationRequest) {
+        //1.从request获取请求头Authorization 并取 值内 'Bearer ' 后的值为usertoken
+        String authorization = request.getHeader("Authorization");
+        String carid = request.getHeader("Carid");
+        conversationRequest.setCardId(carid);
+        String userToken = authorization.substring(7);
+        //2.根据usertoken 查询 chatgpt_user 表中的记录
+        ChatgptUser user = chatGptAccountService.findByUserTokenAndExpireTimeAfter(userToken, LocalDateTime.now());
+        //3.如果记录不存在,返回状态码 401
+        if (user == null) {
+            response.setStatus(401);
+        }
+        //4.获取gpt-4 的规则限制
+        boolean flag = chatGptAccountService.conversationLimit(userToken, conversationRequest.getModel());
+        //记录提问时间,车次,标题和模型
+        chatGptAccountService.saveConversationRecord(userToken, conversationRequest);
+        OutputStream out = null;
+        String messgae = "";
+        if(flag){
+            response.setStatus(429);
+            messgae = "对话限制";
+        }else {
+            response.setStatus(200);
+        }
+        //5.获取 conversationRequest 中的model 字段,判断是否为 gpt-4 如果为gpt-4 执行 查看是否达到限制方法
+        //6.如果达到限制,返回状态码 429 并返回文本 xxx
+        try {
+            out = response.getOutputStream();
+            IoKit.write(messgae, out);
+        } catch (Throwable e) {
+            log.error("conversationLimit write response error", e);
+        } finally {
+            IoKit.close(out);
+        }
+        response.setStatus(200);
+    }
+
+
+    public Result<>
 }