Jelajahi Sumber

Merge branch 'chatgpt2.0' into dev

# Conflicts:
#	netflix-web/src/main/resources/application-dev.yml
#	netflix-web/src/main/resources/application-prd.yml
chenbiao 2 tahun lalu
induk
melakukan
6a5e9f6e4b
18 mengubah file dengan 1190 tambahan dan 12 penghapusan
  1. 11 0
      netflix-dao/src/main/java/com/cyksj/mapper/ChatgptUserConversationRecordMapper.java
  2. 15 0
      netflix-dao/src/main/java/com/cyksj/model/entity/ChatgptSession.java
  3. 14 0
      netflix-dao/src/main/java/com/cyksj/model/entity/ChatgptUser.java
  4. 5 0
      netflix-dao/src/main/java/com/cyksj/model/entity/ChatgptUserConversation.java
  5. 49 0
      netflix-dao/src/main/java/com/cyksj/model/entity/ChatgptUserConversationRecord.java
  6. 15 0
      netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDonSku.java
  7. 105 0
      netflix-dao/src/main/java/com/cyksj/model/request/gpt/ConversationRequest.java
  8. 23 0
      netflix-dao/src/main/java/com/cyksj/model/response/ConversationLimitResponse.java
  9. 53 0
      netflix-dao/src/main/java/com/cyksj/model/views/ChatGptUserConversationRecordHistoryView.java
  10. 40 0
      netflix-dao/src/main/java/com/cyksj/model/views/ChatGptUserView.java
  11. 62 0
      netflix-dao/src/main/java/com/cyksj/model/views/ChatgptCarInfoView.java
  12. 151 0
      netflix-service/src/main/java/com/cyksj/redis/RedisService.java
  13. 43 0
      netflix-service/src/main/java/com/cyksj/service/chatgpt/ChatGptAccountService.java
  14. 462 6
      netflix-service/src/main/java/com/cyksj/service/chatgpt/impl/ChatGptAccountServiceImpl.java
  15. 0 2
      netflix-service/src/main/java/com/cyksj/service/order/post/impl/OEPostDataService.java
  16. 142 4
      netflix-web/src/main/java/com/cyksj/web/controller/mirror/MirrorController.java
  17. 0 0
      netflix-web/src/main/resources/application-dev.yml
  18. 0 0
      netflix-web/src/main/resources/application-prd.yml

+ 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> {
+}

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

@@ -41,6 +41,20 @@ public class ChatgptSession extends BaseEntity {
     @TableField("accountId")
     private Long accountId;
 
+    @TableField("isCar")
+    private Boolean isCar;
+
+    /**
+     * 车id
+     */
+    @TableField("carID")
+    private String carId;
+
+    /**
+     * 车名称
+     */
+    private String carName;
+
     /**
      * 官方session
      */
@@ -52,4 +66,5 @@ public class ChatgptSession extends BaseEntity {
      */
     private String remark;
 
+
 }

+ 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 limitNum;
+
+    /**
+     * 限制时间
+     */
+    private Long limitTime;
+
+    /**
+     * 是否为车队
+     */
+    private Boolean isCar;
 }

+ 5 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/ChatgptUserConversation.java

@@ -38,5 +38,10 @@ public class ChatgptUserConversation extends BaseEntity {
      */
     private String title;
 
+    /**
+     * 消息id
+     */
+    private String messageId;
+
 
 }

+ 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;
+
+
+}

+ 15 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDonSku.java

@@ -163,6 +163,21 @@ public class GoodsDonSku extends BaseEntity {
      */
     private Boolean isMirror;
 
+    /**
+     * 是否是车队形式
+     */
+    private Boolean isCar;
+
+    /**
+     * 限制次数
+     */
+    private Integer gptLimitNum;
+
+    /**
+     * 限制时间
+     */
+    private Long gptLimitTime;
+
     /**
      * 特定价格
      */

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

@@ -0,0 +1,105 @@
+package com.cyksj.model.request.gpt;
+
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @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 carId;
+
+    // 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
+
+
+
+}

+ 23 - 0
netflix-dao/src/main/java/com/cyksj/model/response/ConversationLimitResponse.java

@@ -0,0 +1,23 @@
+package com.cyksj.model.response;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author chan
+ * @date 2024/4/9 17:41
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class ConversationLimitResponse {
+
+    /**
+     * 是否限制
+     */
+    private boolean limited;
+
+    private Long nextAvailableTime; // 以毫秒为单位
+
+}

+ 53 - 0
netflix-dao/src/main/java/com/cyksj/model/views/ChatGptUserConversationRecordHistoryView.java

@@ -0,0 +1,53 @@
+package com.cyksj.model.views;
+
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @author chan
+ * @date 2024/4/10 11:37
+ */
+@Data
+@SearchBean(tables = "(SELECT MAX(id) id from chatgpt_user_conversation_record as r\n" +
+        "GROUP BY r.conversation_id) t left join chatgpt_user_conversation_record as r on t.id = r.id , chatgpt_user_conversation as c"
+        ,where = "r.conversation_id = c.conversationId"
+        ,orderBy = "r.created_time desc"
+)
+public class ChatGptUserConversationRecordHistoryView {
+
+    @DbField("r.user_token")
+    private String userToken;
+    /**
+     * 对话标题
+     */
+    @DbField("c.title")
+    private String title;
+
+    /**
+     * 对话id
+     */
+    @DbField("r.conversation_id")
+    private String conversationId;
+
+    /**
+     * 对话时间
+     */
+    @DbField("r.created_time")
+    private Date conversationTime;
+
+    /**
+     * 车队id
+     */
+    @DbField("c.car_id")
+    private String carId;
+
+    /**
+     * 车队名称
+     */
+    @DbField("c.car_name")
+    private String carName;
+
+}

+ 40 - 0
netflix-dao/src/main/java/com/cyksj/model/views/ChatGptUserView.java

@@ -0,0 +1,40 @@
+package com.cyksj.model.views;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @author chan
+ * @date 2024/4/10 11:21
+ */
+@Data
+@Builder
+public class ChatGptUserView {
+
+    /**
+     * 规格名称
+     */
+    private String skuName;
+
+    /**
+     * 过期时间
+     */
+    private Date expireTime;
+
+    /**
+     * 限制时间
+     */
+    private Long limitTime;
+
+    /**
+     * 限制次数
+     */
+    private Integer limitNum;
+
+    /**
+     * 使用次数
+     */
+    private Long use;
+}

+ 62 - 0
netflix-dao/src/main/java/com/cyksj/model/views/ChatgptCarInfoView.java

@@ -0,0 +1,62 @@
+package com.cyksj.model.views;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @author chan
+ * @date 2024/4/8 17:02
+ */
+@Data
+public class ChatgptCarInfoView {
+
+    /**
+     * 车次id
+     */
+    private String carId;
+
+    /**
+     * 车次名称
+     */
+    private String carName;
+
+    /**
+     * 车次状态 繁忙 空闲 停运
+     */
+
+    private String status;
+
+
+    /**
+     * 得分
+     */
+    private Double score;
+
+    /**
+     * 可用时间
+     */
+    private Date expTime;
+
+    /**
+     * 车次类型 team plus 3.5
+     */
+
+    private String type;
+
+    /**
+     * 限制次数
+     */
+    private Integer gptLimit;
+
+    /**
+     * 使用次数
+     */
+
+    private Integer use;
+
+    /**
+     * 是否为历史车次
+     */
+    private Boolean isHistory;
+}

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

@@ -5,6 +5,7 @@ import lombok.AllArgsConstructor;
 import lombok.Getter;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ZSetOperations;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.PostConstruct;
@@ -594,6 +595,150 @@ 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 Double zIncrementScore(String key, String value, double delta) {
+        try {
+            return redisTemplate.opsForZSet().incrementScore(key, value, delta);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+
+    // 封装获取ZSet分数操作
+    public Double zScore(String key, Object value) {
+        try {
+            return redisTemplate.opsForZSet().score(key, value);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    // 封装ZSet添加或更新分数操作
+    public Boolean zAddOrUpdateScore(String key, Object value, double score) {
+        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 zCount(String key, long start, long end) {
+        try {
+            return redisTemplate.opsForZSet().count(key, start, end);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    // 封装获取ZSet范围内成员的方法
+    public Set<Object> zRange(String key, long start, long end) {
+        try {
+            ZSetOperations<String, Object> zSetOperations = redisTemplate.opsForZSet();
+            return zSetOperations.range(key, start, end);
+        } 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;
+        }
+    }
+
+    /**
+     * 返回有序集 key 中,所有分数介于 min 和 max 之间(包括等于 min 或 max)的成员。
+     * 有序集成员按分数值递增(从小到大)次序排列。
+     * 此方法还支持限制返回的元素数量。
+     *
+     * @param key 有序集合的key
+     * @param min 分数范围的最小值
+     * @param max 分数范围的最大值
+     * @param offset 开始返回的索引位置
+     * @param count 返回的最大元素数量
+     * @return 介于最小值和最大值之间的成员的集合
+     */
+    public Set<Object> zRangeByScore(String key, double min, double max, long offset, long count) {
+        return redisTemplate.opsForZSet().rangeByScore(key, min, max, offset, count);
+    }
+
+    /**
+     * 从ZSet获取所有成员及其分数
+     * @param key ZSet的键
+     * @return ZSet中所有成员及其分数的集合
+     */
+    public Set<ZSetOperations.TypedTuple<Object>> zRangeWithScores(String key, long start, long end){
+        return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
+    }
+
+    /**
+     * 检查指定值是否存在于有序集合中
+     * @param key
+     * @param value
+     * @return
+     */
+    public boolean checkValueExistsInZSet(String key, String value) {
+        // 检查指定值是否存在于有序集合中
+        return redisTemplate.opsForZSet().score(key, value) != null;
+    }
+
+    /**
+     * 从ZSet中删除指定的成员
+     *
+     * @param key ZSet的键
+     * @param value 要删除的成员值
+     * @return 被成功移除的成员数量
+     */
+    public Long zRemove(String key, Object value) {
+        try {
+            return redisTemplate.opsForZSet().remove(key, value);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0L;
+        }
+    }
+
+
+
+
     public Set<String> keys(String name) {
         return redisTemplate.keys(name);
     }
@@ -671,6 +816,12 @@ 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),
+        CHATGPT_CAR_SCORES("chatgpt:car:scores","chatGpt 车队分数", 48 * 60 * 60L),
+        CHATGPT_CAR_HIGH_CHAT("chatgpt:car:high:chat:%s","chatGpt gpt4 对话次数", 48 * 60 * 60L),
+        CHATGPT_CAR_LOW_CHAT("chatgpt:car:low:chat:%s","chatGpt 3.5 对话次数", 48 * 60 * 60L),
         ;
 
         private String name;

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

@@ -1,6 +1,15 @@
 package com.cyksj.service.chatgpt;
 
 import com.cyksj.model.entity.Account;
+import com.cyksj.model.entity.ChatgptSession;
+import com.cyksj.model.entity.ChatgptUser;
+import com.cyksj.model.request.gpt.ConversationRequest;
+import com.cyksj.model.response.ConversationLimitResponse;
+import com.cyksj.model.views.ChatGptUserView;
+import com.cyksj.model.views.ChatgptCarInfoView;
+
+import java.time.LocalDateTime;
+import java.util.Set;
 
 /**
  * @author chan
@@ -12,4 +21,38 @@ public interface ChatGptAccountService {
     void upAccount(Account account);
 
     String getLoginUrl(Long userId, Long relationId);
+
+    String getCarLoginUrl(Long userId, Long relationId, String carId);
+
+    ChatgptUser findByUserTokenAndExpireTimeAfter(String userToken, LocalDateTime now);
+
+    /**
+     * 对话限制
+     * @param userToken 用户token
+     * @param model 对话模型
+     * @return 是否限制
+     */
+    ConversationLimitResponse conversationLimit(String userToken, String model, String carId);
+
+    void saveConversationRecord(String userToken, ConversationRequest conversationRequest);
+
+    /**
+     * 获取对话次数
+     * @param userToken 用户token
+     * @param limitTime 限制时间
+     * @return
+     */
+    Long getConversationCount(String userToken, Long limitTime);
+
+    ChatgptSession checkSession(String carId);
+
+    Boolean checkCarAccount(long userId, Long relationId);
+
+    ChatgptUser getCarChatGptUser(long userId, Long relationId);
+
+    Set<ChatgptCarInfoView> getCarInfoList(String userToken);
+
+    ChatGptUserView getUserInfo(long userId, Long relationId);
+
+    void genTitleSync(String messageId, String carid, String userToken);
 }

+ 462 - 6
netflix-service/src/main/java/com/cyksj/service/chatgpt/impl/ChatGptAccountServiceImpl.java

@@ -1,5 +1,6 @@
 package com.cyksj.service.chatgpt.impl;
 
+import cn.hutool.core.date.DateTime;
 import cn.hutool.core.lang.UUID;
 import cn.hutool.http.HttpRequest;
 import cn.hutool.http.HttpResponse;
@@ -7,25 +8,40 @@ import cn.hutool.http.HttpUtil;
 import cn.hutool.http.Method;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
 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.model.response.ConversationLimitResponse;
+import com.cyksj.model.views.ChatGptUserConversationRecordHistoryView;
+import com.cyksj.model.views.ChatGptUserView;
+import com.cyksj.model.views.ChatgptCarInfoView;
+import com.cyksj.redis.RedisService;
 import com.cyksj.service.chatgpt.ChatGptAccountService;
 import com.cyksj.service.user.UserBindRelationService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.util.MapUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.ZSetOperations;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.PostConstruct;
 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;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * @author chan
@@ -39,8 +55,19 @@ public class ChatGptAccountServiceImpl implements ChatGptAccountService {
     @Value("${chatgpt.domain}")
     private String GPT_DOMAIN;
 
+    @Value("${chatgpt.car.domain}")
+    private String CAR_GPT_DOMAIN;
+
     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 static final GlobalThreadPoolTaskExecutor TASK_EXECUTOR = GlobalThreadPoolTaskExecutor.getInstance();
+
+    private final RedisService redisService;
+
     private final ChatgptUserMapper chatgptUserMapper;
 
     private final ChatgptSessionMapper chatgptSessionMapper;
@@ -55,6 +82,13 @@ public class ChatGptAccountServiceImpl implements ChatGptAccountService {
 
     private final UserBindRelationService userBindRelationService;
 
+    private final ChatgptUserConversationRecordMapper chatgptUserConversationRecordMapper;
+
+    private final ChatgptUserConversationMapper chatgptUserConversationMapper;
+
+    private final AccountMapper accountMapper;
+
+    private final BeanSearcher beanSearcher;
 
     @Override
     public String addAccount(Account account) {
@@ -159,13 +193,33 @@ public class ChatGptAccountServiceImpl implements ChatGptAccountService {
         GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId());
 
         if (goodsDonSku != null && goodsDonSku.getIsMirror()) {
-            ChatgptUser chatgptUser = getChatgptUser(userId, relationId, groupsRelation, groupsTrips);
+            ChatgptUser chatgptUser = getChatgptUser(userId, relationId, groupsRelation, groupsTrips, goodsDonSku);
             return GPT_DOMAIN + "/login_token?access_token=" + chatgptUser.getUserToken();
         } else {
             throw BusinessRuntimeException.getInstance("服务器出了点问题");
         }
     }
 
+    /**
+     * 获取车队登录url
+     *
+     * @param userId
+     * @param carId
+     * @return
+     */
+    @Override
+    public String getCarLoginUrl(Long userId, Long relationId, String carId) {
+        GroupsRelation groupsRelation = getGroupsRelation(userId, relationId);
+        GroupsTrips groupsTrips = getGroupsTrips(groupsRelation);
+        GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId());
+        if (goodsDonSku != null && goodsDonSku.getIsMirror() && goodsDonSku.getIsCar()) {
+            ChatgptUser chatgptUser = getChatgptCarUser(userId, relationId, groupsRelation, goodsDonSku);
+            return GPT_DOMAIN + "/auth//auth/logintoken?carid=" + carId + "&usertoken=" + chatgptUser.getUserToken();
+        } else {
+            throw BusinessRuntimeException.getInstance("服务器出了点问题");
+        }
+    }
+
     /**
      * 获取车位信息
      *
@@ -196,11 +250,22 @@ public class ChatGptAccountServiceImpl implements ChatGptAccountService {
         return groupsTrips;
     }
 
-    private ChatgptUser getChatgptUser(Long userId, Long relationId, GroupsRelation groupsRelation, GroupsTrips groupsTrips) {
+    private ChatgptUser getChatgptUser(Long userId, Long relationId, GroupsRelation groupsRelation, GroupsTrips groupsTrips, GoodsDonSku goodsDonSku) {
         ChatgptUser chatgptUser = chatgptUserMapper.selectOne(Wrappers.lambdaQuery(ChatgptUser.class).eq(ChatgptUser::getRelationId, relationId));
         User user = userMapper.selectById(userId);
         if (chatgptUser == null) {
-            chatgptUser = createChatgptUser(groupsRelation, groupsTrips, user);
+            chatgptUser = createChatgptUser(groupsRelation, groupsTrips, user, goodsDonSku);
+        } else {
+            updateChatgptUser(groupsRelation, user, chatgptUser);
+        }
+        return chatgptUser;
+    }
+
+    private ChatgptUser getChatgptCarUser(Long userId, Long relationId, GroupsRelation groupsRelation, GoodsDonSku goodsDonSku) {
+        ChatgptUser chatgptUser = chatgptUserMapper.selectOne(Wrappers.lambdaQuery(ChatgptUser.class).eq(ChatgptUser::getRelationId, relationId));
+        User user = userMapper.selectById(userId);
+        if (chatgptUser == null) {
+            chatgptUser = createChatgptUser(groupsRelation, null, user, goodsDonSku);
         } else {
             updateChatgptUser(groupsRelation, user, chatgptUser);
         }
@@ -210,12 +275,37 @@ public class ChatGptAccountServiceImpl implements ChatGptAccountService {
     /**
      * 创建chatgptUser
      */
-    private ChatgptUser createChatgptUser(GroupsRelation groupsRelation, GroupsTrips groupsTrips, User user) {
-        ChatgptSession chatgptSession = chatgptSessionMapper.selectOne(Wrappers.lambdaQuery(ChatgptSession.class).eq(ChatgptSession::getAccountId, groupsTrips.getAccountId()));
+    private ChatgptUser createChatgptUser(GroupsRelation groupsRelation, GroupsTrips groupsTrips, User user, GoodsDonSku goodsDonSku) {
         ChatgptUser chatgptUser = new ChatgptUser();
+        if (!goodsDonSku.getIsCar()) {
+            ChatgptSession chatgptSession = chatgptSessionMapper.selectOne(Wrappers.lambdaQuery(ChatgptSession.class).eq(ChatgptSession::getAccountId, groupsTrips.getAccountId()));
+            if (chatgptSession == null) {
+
+                Account account = accountMapper.selectById(groupsTrips.getAccountId());
+                String officialSession = "";
+                try {
+                    JSONObject loginResult = getLoginResult(account);
+                    if (StringUtils.isBlank(loginResult.getStr("accessToken"))) {
+                        throw BusinessRuntimeException.getInstance(loginResult.getStr("detail"));
+                    }
+                    officialSession = loginResult.toString();
+                } catch (Exception e) {
+                    throw BusinessRuntimeException.getInstance("配置账号错误 msg: " + e.getMessage());
+                }
+
+                createChatgptSession(account, officialSession);
+                chatgptSession = chatgptSessionMapper.selectOne(Wrappers.lambdaQuery(ChatgptSession.class).eq(ChatgptSession::getAccountId, groupsTrips.getAccountId()));
+
+            }
+            chatgptUser.setSessionId(chatgptSession.getId());
+        }
+        if (goodsDonSku.getIsCar()) {
+            chatgptUser.setIsCar(true);
+            chatgptUser.setLimitNum(goodsDonSku.getGptLimitNum());
+            chatgptUser.setLimitTime(goodsDonSku.getGptLimitTime());
+        }
         chatgptUser.setExpireTime(groupsRelation.getExpiryTime());
         chatgptUser.setIsPlus(1);
-        chatgptUser.setSessionId(chatgptSession.getId());
         chatgptUser.setName(user.getNickname());
         chatgptUser.setImg(getWxImg(user.getHeadimgurl(), user));
         chatgptUser.setRelationId(groupsRelation.getId());
@@ -255,5 +345,371 @@ 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 void saveConversationRecord(String userToken, ConversationRequest conversationRequest) {
+        ChatgptSession chatgptSession = chatgptSessionMapper.selectOne(Wrappers.lambdaQuery(ChatgptSession.class).eq(ChatgptSession::getCarId, conversationRequest.getCarId()));
+        ChatgptUserConversationRecord chatgptUserConversationRecord = new ChatgptUserConversationRecord();
+        chatgptUserConversationRecord.setUserToken(userToken);
+        chatgptUserConversationRecord.setCarId(chatgptSession.getCarId());
+        chatgptUserConversationRecord.setCarName(chatgptSession.getCarName());
+        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);
+
+        updateExperienceAndScore(conversationRequest.getCarId(), !"text-davinci-002-render-sha".equals(conversationRequest.getModel()), System.currentTimeMillis());
+    }
+
+
+    @Override
+    public ConversationLimitResponse conversationLimit(String userToken, String model, String carId) {
+        ConversationLimitResponse conversationLimitResponse = new ConversationLimitResponse();
+        conversationLimitResponse.setLimited(false);
+        if ("gpt-4".equals(model)) {
+            ChatgptUser chatgptUser = chatgptUserMapper.selectOne(Wrappers.lambdaQuery(ChatgptUser.class).eq(ChatgptUser::getUserToken, userToken));
+            if (chatgptUser == null) {
+                conversationLimitResponse.setLimited(true);
+            }
+            //如果不为车队 直接返回
+            if (!chatgptUser.getIsCar()) {
+                return conversationLimitResponse;
+            }
+            conversationLimitResponse = isConversationAllowed(userToken, chatgptUser.getLimitNum(), chatgptUser.getLimitTime());
+            //车队次数记录
+            cardConversationRecord(carId);
+        }
+
+        return conversationLimitResponse;
+    }
+
+
+    /**
+     * 检查是否允许进行进行提问
+     *
+     * @param userToken 用户token
+     * @return true 如果允许请求,false 如果请求被限制
+     */
+    public ConversationLimitResponse 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;
+        ConversationLimitResponse conversationLimitResponse = new ConversationLimitResponse();
+        // 清除时间窗口之前的请求记录
+        redisService.zRemoveRangeByScore(key, 0, windowStartMillis);
+
+        Long currentSize = redisService.zCard(key);
+        if (currentSize != null && currentSize >= limit) {
+            // 如果当前请求次数超过限制,则拒绝请求
+            Set<Object> times = redisService.zRangeByScore(key, 0, currentTimeMillis, 0, 1);
+            Long oldestTime = (Long) times.stream().findFirst().orElse(null);
+            if (oldestTime != null) {
+                // 下一次可用时间是最早请求时间之后的3小时
+                long nextAvailableTime = oldestTime + (limitTime * 60 * 60 * 1000);
+                conversationLimitResponse.setNextAvailableTime(nextAvailableTime);
+            }
+            conversationLimitResponse.setLimited(true);
+            return conversationLimitResponse;
+        } else {
+            // 如果未超过限制,记录当前请求的时间戳
+            redisService.zAdd(key, currentTimeMillis, currentTimeMillis);
+            // 设置ZSet的过期时间,窗口大小加上一段冗余时间
+            redisService.expire(key, (limitTime * 60 * 60) + 20);
+            conversationLimitResponse.setLimited(false);
+            return conversationLimitResponse;
+        }
+    }
+
+
+    /**
+     * 记录车队的提问次数
+     */
+    public boolean cardConversationRecord(String carid) {
+        String key = RedisService.key.CHATGPT_CAR_CONVERSATION_LIMIT.getName() + ":" + carid;
+        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 滑动窗口内的请求次数
+     */
+    @Override
+    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;
+    }
+
+    /**
+     * 获取指定车队ID在滑动窗口内的提问次数
+     *
+     * @param carId 车队ID
+     * @return 滑动窗口内的请求次数
+     */
+    private Long getCarConversationCount(String carId, Long limitTime) {
+        String key = RedisService.key.CHATGPT_CAR_CONVERSATION_LIMIT.getName() + ":" + carId;
+        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;
+    }
+
+    @Override
+    public ChatgptSession checkSession(String carId) {
+        ChatgptSession chatgptSession = chatgptSessionMapper.selectOne(Wrappers.lambdaQuery(ChatgptSession.class).eq(ChatgptSession::getCarId, carId).eq(ChatgptSession::getIsCar, true).last(" limit 1"));
+        if (chatgptSession == null) {
+            throw BusinessRuntimeException.getInstance("车队不存在");
+        }
+        return chatgptSession;
+    }
+
+
+    private Long carOaiLimit(String carId) {
+        if (redisService.hasKey("clears_in:" + carId)) {
+            return redisService.getExpire("clears_in:" + carId);
+        }
+        return 0L;
+    }
+
+
+    @Override
+    public Boolean checkCarAccount(long userId, Long relationId) {
+        GroupsRelation groupsRelation = getGroupsRelation(userId, relationId);
+        GroupsTrips groupsTrips = getGroupsTrips(groupsRelation);
+        GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId());
+
+        if (goodsDonSku != null && goodsDonSku.getIsMirror() && goodsDonSku.getIsCar()) {
+            ChatgptUser chatgptUser = getChatgptCarUser(userId, relationId, groupsRelation, goodsDonSku);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public ChatgptUser getCarChatGptUser(long userId, Long relationId) {
+        GroupsRelation groupsRelation = getGroupsRelation(userId, relationId);
+        GroupsTrips groupsTrips = getGroupsTrips(groupsRelation);
+        GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId());
+
+        if (goodsDonSku != null && goodsDonSku.getIsMirror() && goodsDonSku.getIsCar()) {
+            ChatgptUser chatgptUser = getChatgptCarUser(userId, relationId, groupsRelation, goodsDonSku);
+            return chatgptUser;
+        }
+        return null;
+    }
+
+    /**
+     * 获取车队信息
+     */
+    @Override
+    public Set<ChatgptCarInfoView> getCarInfoList(String userToken) {
+        List<ChatGptUserConversationRecordHistoryView> search = beanSearcher.searchAll(ChatGptUserConversationRecordHistoryView.class, MapUtils.builder().field("userToken", userToken).limit(0, 2).build());
+        Set<ChatgptCarInfoView> res = search.stream()
+                .map((historyView) -> {
+                    ChatgptCarInfoView chatgptCarInfoView = buildChatgptCarInfoView(historyView.getCarId(), redisService.zScore(RedisService.key.CHATGPT_CAR_SCORES.getName(), historyView.getCarId()));
+                    chatgptCarInfoView.setIsHistory(true);
+                    return chatgptCarInfoView;
+                }).collect(Collectors.toSet());
+
+        Set<ChatgptCarInfoView> lowestScoreFleets = getLowestScoreFleets(10);
+
+        res.addAll(lowestScoreFleets);
+
+        return res;
+    }
+
+
+    // 更新体验并计算评分
+    public void updateExperienceAndScore(String carId, boolean isHighLevel, long timestamp) {
+        // 定义键名
+        String key = isHighLevel ? RedisService.key.CHATGPT_CAR_HIGH_CHAT.getName() + carId : RedisService.key.CHATGPT_CAR_LOW_CHAT.getName() + carId;
+        double scoreToAdd = isHighLevel ? 2.0 : 1.0; // 分数更新规则
+
+        // 更新体验数据
+        redisService.zAdd(key, timestamp, String.valueOf(timestamp));
+
+        // 更新车队评分
+        redisService.zIncrementScore(RedisService.key.CHATGPT_CAR_SCORES.getName(), carId, scoreToAdd);
+
+        // 清理旧数据(可选)和重新计算评分(根据需要实现)
+        cleanupOldExperiencesAndRecalculateScore(carId, timestamp);
+    }
+
+    // 清理旧数据和重新计算评分
+    public void cleanupOldExperiencesAndRecalculateScore(String carId, long currentTimestamp) {
+        long threeHoursAgo = currentTimestamp - (3 * 60 * 60 * 1000); // 3小时前的时间戳
+
+        // 清理高级体验旧数据
+        redisService.zRemoveRangeByScore(RedisService.key.CHATGPT_CAR_HIGH_CHAT.getName() + carId, 0, threeHoursAgo);
+
+        // 清理低级体验旧数据
+        redisService.zRemoveRangeByScore(RedisService.key.CHATGPT_CAR_LOW_CHAT.getName() + carId, 0, threeHoursAgo);
+
+        // 重新计算评分
+        recalculateScore(carId, currentTimestamp);
+    }
+
+
+    // 重新计算指定车队的评分
+    private void recalculateScore(String carId, long currentTimestamp) {
+        // 实际应用中,你需要根据高级体验和低级体验的数量重新计算得分
+        Double highExperienceScore = (double) redisService.zCount(RedisService.key.CHATGPT_CAR_HIGH_CHAT.getName() + carId, currentTimestamp - (3 * 60 * 60 * 1000), currentTimestamp);
+        Double lowExperienceScore = (double) redisService.zCount(RedisService.key.CHATGPT_CAR_LOW_CHAT.getName() + carId, currentTimestamp - (3 * 60 * 60 * 1000), currentTimestamp) / 2;
+        double newScore = highExperienceScore + lowExperienceScore;
+        redisService.zAdd(RedisService.key.CHATGPT_CAR_SCORES.getName(), newScore, carId);
+    }
+
+
+    // 获取得分最低的N个车队的方法
+    public Set<ChatgptCarInfoView> getLowestScoreFleets(int count) {
+        Set<ZSetOperations.TypedTuple<Object>> lowestScorecarIds;
+        Long fleetsSize = redisService.zCard(RedisService.key.CHATGPT_CAR_SCORES.getName());
+        // 构建ChatgptCarInfoView集合
+        if (fleetsSize != null && fleetsSize > 10) {
+            TASK_EXECUTOR.execute(() -> {
+                initFleets(true);
+            });
+        } else {
+            initFleets(false);
+        }
+        lowestScorecarIds = redisService.zRangeWithScores(RedisService.key.CHATGPT_CAR_SCORES.getName(), 0L, count - 1);
+
+        return lowestScorecarIds.stream()
+                .map(typedTuple -> {
+                    String value = StringUtil.getString(typedTuple.getValue());
+                    Double score = typedTuple.getScore(); // 获取分数
+                    // 根据需要处理value和score
+                    return buildChatgptCarInfoView(value, score); // 假设这个方法现在也接收分数
+                })
+                .collect(Collectors.toSet());
+    }
+
+    public void initFleets(Boolean flag) {
+        LambdaQueryWrapper<ChatgptSession> wrapper = Wrappers.lambdaQuery(ChatgptSession.class);
+        if (flag) {
+            Set<ZSetOperations.TypedTuple<Object>> fleets = redisService.zRangeWithScores(RedisService.key.CHATGPT_CAR_SCORES.getName(), 0L, -1);
+            List<Object> collect = fleets.stream().map(ZSetOperations.TypedTuple::getValue).collect(Collectors.toList());
+            wrapper.notIn(collect.size() > 0, ChatgptSession::getCarId, collect);
+        }
+        //如果存在未存在redis中的车辆则同步进 chatgpt:car:scores
+        List<ChatgptSession> chatgptSessions = chatgptSessionMapper.selectList(wrapper.eq(ChatgptSession::getIsCar, true));
+
+        chatgptSessions.forEach((chatgptSession) -> {
+            if (!redisService.checkValueExistsInZSet(RedisService.key.CHATGPT_CAR_SCORES.getName(), chatgptSession.getCarId())) {
+                redisService.zAdd(RedisService.key.CHATGPT_CAR_SCORES.getName(), 0, chatgptSession.getCarId());
+            }
+        });
+
+    }
+
+
+    // 构建ChatgptCarInfoView对象
+    private ChatgptCarInfoView buildChatgptCarInfoView(String carId, Double score) {
+        // 在这里根据carId获取相关信息并填充到ChatgptCarInfoView对象中
+        ChatgptCarInfoView view = new ChatgptCarInfoView();
+        view.setCarId(carId);
+        // 假设以下方法从Redis或其他服务获取数据
+        view.setStatus(score >= 50 ? "繁忙" : "空闲"); // 或“繁忙”
+        view.setType("plus");
+        view.setGptLimit(40); // 假设值
+        int use = getCarConversationCount(carId, 3L).intValue();
+        view.setUse(Math.min(use, 40));
+        Long aLong = carOaiLimit(carId);
+        if (aLong != 0L) {
+            view.setStatus("停运");
+            view.setExpTime(new DateTime(System.currentTimeMillis() + aLong * 1000));
+        }
+        // 设置其他必要的属性...
+        return view;
+    }
+
+    @Override
+    public ChatGptUserView getUserInfo(long userId, Long relationId) {
+        if (checkCarAccount(userId, relationId)) {
+            GroupsRelation groupsRelation = getGroupsRelation(userId, relationId);
+            GroupsTrips groupsTrips = getGroupsTrips(groupsRelation);
+            GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId());
+            ChatgptUser chatgptUser = chatgptUserMapper.selectOne(Wrappers.lambdaQuery(ChatgptUser.class).eq(ChatgptUser::getRelationId, relationId));
+            return ChatGptUserView.builder()
+                    .skuName(goodsDonSku.getGName())
+                    .expireTime(chatgptUser.getExpireTime())
+                    .limitNum(chatgptUser.getLimitNum())
+                    .limitTime(chatgptUser.getLimitTime())
+                    .use(getConversationCount(chatgptUser.getUserToken(), chatgptUser.getLimitTime()))
+                    .build();
+        } else {
+            throw BusinessRuntimeException.getInstance("您还未购买该车票");
+        }
+    }
+
+    @Override
+    public void genTitleSync(String messageId, String carid, String userToken) {
+        //同步对话
+        ChatgptUserConversationRecord chatgptUserConversationRecord = chatgptUserConversationRecordMapper.selectOne(Wrappers.lambdaQuery(ChatgptUserConversationRecord.class).eq(ChatgptUserConversationRecord::getMessageId, messageId));
+        if (chatgptUserConversationRecord != null) {
+            if (StringUtils.isBlank(chatgptUserConversationRecord.getConversationId())) {
+                ChatgptUserConversation conversation = chatgptUserConversationMapper.selectOne(Wrappers.lambdaQuery(ChatgptUserConversation.class).eq(ChatgptUserConversation::getMessageId, messageId).last(" limit 1"));
+                if (conversation != null) {
+                    chatgptUserConversationRecord.setConversationId(conversation.getConversationId());
+                    chatgptUserConversationRecordMapper.updateById(chatgptUserConversationRecord);
+                }
+            }
+        }
+    }
+
+    @PostConstruct
+    public void init() {
+        //如果存在未存在redis中的车辆则同步进 chatgpt:car:scores
+        TASK_EXECUTOR.execute(() -> {
+            Set<ZSetOperations.TypedTuple<Object>> lowestScorecarIds = redisService.zRangeWithScores(RedisService.key.CHATGPT_CAR_SCORES.getName(), 0L, -1);
+            List<Object> collect = lowestScorecarIds.stream().map(ZSetOperations.TypedTuple::getValue).collect(Collectors.toList());
+            List<ChatgptSession> chatgptSessions = chatgptSessionMapper.selectList(Wrappers.lambdaQuery(ChatgptSession.class).notIn(collect.size() > 0, ChatgptSession::getCarId, collect).eq(ChatgptSession::getIsCar, true));
+            chatgptSessions.forEach((chatgptSession) -> {
+                if (!redisService.checkValueExistsInZSet(RedisService.key.CHATGPT_CAR_SCORES.getName(), chatgptSession.getCarId())) {
+                    redisService.zAdd(RedisService.key.CHATGPT_CAR_SCORES.getName(), 0, chatgptSession.getCarId());
+                }
+            });
+        });
+    }
+
 }
 

+ 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 {
 

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

@@ -1,19 +1,36 @@
 package com.cyksj.web.controller.mirror;
 
+import cn.hutool.json.JSONObject;
+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.ChatgptUser;
+import com.cyksj.model.request.gpt.ConversationRequest;
+import com.cyksj.model.response.ConversationLimitResponse;
+import com.cyksj.model.views.ChatGptUserConversationRecordHistoryView;
+import com.cyksj.model.views.ChatGptUserView;
+import com.cyksj.model.views.ChatgptCarInfoView;
 import com.cyksj.service.chatgpt.ChatGptAccountService;
 import com.cyksj.web.util.StpUserUtil;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapBuilder;
+import com.ejlchina.searcher.util.MapUtils;
 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.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;
+import java.util.Set;
 
 /**
  * /server/镜像服务
+ *
  * @author chan
  * @date 2024/3/19 14:41
  */
@@ -27,9 +44,15 @@ public class MirrorController {
 
     private final HttpServletResponse response;
 
+    private final HttpServletRequest request;
+
+    private final BeanSearcher beanSearcher;
+
+
 
     /**
      * GPT车票跳转登录
+     *
      * @param relationId 车票id
      */
     @GetMapping("/chatGptMirror/{relationId}")
@@ -38,4 +61,119 @@ public class MirrorController {
 
         response.sendRedirect(chatGptAccountService.getLoginUrl(userId, relationId));
     }
+
+    /**
+     * GPT车队跳转登录
+     *
+     * @param carId 车队id
+     */
+    @GetMapping("/chatGptMirror/{relationId}/{carId}")
+    public Result<String> chatGPtCarLogin(@PathVariable Long relationId, @PathVariable String carId) throws IOException {
+        Long userId = StpUserUtil.getLoginIdAsLong();
+        chatGptAccountService.checkCarAccount(userId, relationId);
+        String url = chatGptAccountService.getCarLoginUrl(userId, relationId, carId);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(url);
+    }
+
+    @GetMapping("/gpt/genTitleSync")
+    public Result<String> genTitleSync(String messageId){
+        //1.从request获取请求头Authorization 并取 值内 'Bearer ' 后的值为usertoken
+        String authorization = request.getHeader("Authorization");
+        String carid = request.getHeader("Carid");
+        String userToken = authorization.substring(7);
+        chatGptAccountService.genTitleSync(messageId, carid, userToken);
+        return GatewayResponse.SUCCESS.newBuilder().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.setCarId(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 的规则限制
+        //text-davinci-002-render-sha 3.5
+        ConversationLimitResponse conversationLimitResponse = chatGptAccountService.conversationLimit(userToken, conversationRequest.getModel(), conversationRequest.getCarId());
+        //记录提问时间,车次,标题和模型
+        chatGptAccountService.saveConversationRecord(userToken, conversationRequest);
+        OutputStream out = null;
+        String messgae = "";
+        if (conversationLimitResponse.isLimited()) {
+            response.setStatus(429);
+            String json = "{\"detail\":{\"message\":\"您的账号已达到GPT-4的使用上限。您现在可以继续使用默认模型,或者重试在\",\"code\":\"model_cap_exceeded\",\"clears_in\":2687}}";
+            JSONObject res = new JSONObject(json);
+            res.putOpt("clears_in",conversationLimitResponse.getNextAvailableTime()/1000);
+            messgae = res.toString();
+        } 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);
+    }
+
+
+    /**
+     * 获取车队列表
+     * @param relationId
+     * @return
+     */
+    @GetMapping("/gpt/cars")
+    public Result<Set<ChatgptCarInfoView>> getCarInfoList(Long relationId) {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        //查看用户是否存在车票
+        ChatgptUser carChatGptUser = chatGptAccountService.getCarChatGptUser(userId, relationId);
+        if (carChatGptUser == null) {
+            throw BusinessRuntimeException.getInstance("您还未购买车票");
+        }
+
+        Set<ChatgptCarInfoView> carInfoList = chatGptAccountService.getCarInfoList(carChatGptUser.getUserToken());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(carInfoList);
+    }
+
+
+    /**
+     * 获取gpt车票信息
+     */
+    @GetMapping("/gpt/userInfo")
+    public Result<ChatGptUserView> getUserInfo(Long relationId) {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        ChatGptUserView user = chatGptAccountService.getUserInfo(userId, relationId);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(user);
+    }
+
+    /**
+     * 获取车队历史记录
+     */
+    @GetMapping("/gpt/car/history")
+    public Result<SearchResult<ChatGptUserConversationRecordHistoryView>> getCarHistory(Long relationId) {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        ChatgptUser carChatGptUser = chatGptAccountService.getCarChatGptUser(userId, relationId);
+        if(carChatGptUser == null){
+            throw BusinessRuntimeException.getInstance("车票出现了异常.");
+        }
+        MapBuilder builder = MapUtils.flatBuilder(request.getParameterMap()).field("r.user_token", carChatGptUser.getUserToken());
+        SearchResult<ChatGptUserConversationRecordHistoryView> search = beanSearcher.search(ChatGptUserConversationRecordHistoryView.class, builder.build());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
 }

File diff ditekan karena terlalu besar
+ 0 - 0
netflix-web/src/main/resources/application-dev.yml


File diff ditekan karena terlalu besar
+ 0 - 0
netflix-web/src/main/resources/application-prd.yml


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini