Bläddra i källkod

GPT镜像车队功能

chenbiao 2 år sedan
förälder
incheckning
2238d4c675
27 ändrade filer med 1584 tillägg och 15 borttagningar
  1. 5 0
      netflix-common/src/main/java/com/cyksj/common/constant/Constant.java
  2. 11 0
      netflix-dao/src/main/java/com/cyksj/mapper/ChatgptUserConversationRecordMapper.java
  3. 5 0
      netflix-dao/src/main/java/com/cyksj/mapper/ChatgptUserMapper.java
  4. 15 0
      netflix-dao/src/main/java/com/cyksj/model/entity/ChatgptSession.java
  5. 14 0
      netflix-dao/src/main/java/com/cyksj/model/entity/ChatgptUser.java
  6. 5 0
      netflix-dao/src/main/java/com/cyksj/model/entity/ChatgptUserConversation.java
  7. 49 0
      netflix-dao/src/main/java/com/cyksj/model/entity/ChatgptUserConversationRecord.java
  8. 15 0
      netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDonSku.java
  9. 105 0
      netflix-dao/src/main/java/com/cyksj/model/request/gpt/ConversationRequest.java
  10. 23 0
      netflix-dao/src/main/java/com/cyksj/model/response/ConversationLimitResponse.java
  11. 56 0
      netflix-dao/src/main/java/com/cyksj/model/views/ChatGptUserConversationRecordHistoryView.java
  12. 40 0
      netflix-dao/src/main/java/com/cyksj/model/views/ChatGptUserView.java
  13. 76 0
      netflix-dao/src/main/java/com/cyksj/model/views/ChatgptCarInfoView.java
  14. 66 0
      netflix-dao/src/main/java/com/cyksj/model/views/ChatgptCarSessionView.java
  15. 5 0
      netflix-dao/src/main/java/com/cyksj/model/views/RenewalView.java
  16. 152 0
      netflix-service/src/main/java/com/cyksj/redis/RedisService.java
  17. 53 0
      netflix-service/src/main/java/com/cyksj/service/chatgpt/ChatGptAccountService.java
  18. 517 9
      netflix-service/src/main/java/com/cyksj/service/chatgpt/impl/ChatGptAccountServiceImpl.java
  19. 14 0
      netflix-service/src/main/java/com/cyksj/service/groups/impl/GroupFunServiceImpl.java
  20. 2 0
      netflix-service/src/main/java/com/cyksj/service/mange/CmsGoodsDonSkuService.java
  21. 30 0
      netflix-service/src/main/java/com/cyksj/service/mange/goods/CmsGoodsDonSkuServiceImpl.java
  22. 0 2
      netflix-service/src/main/java/com/cyksj/service/order/post/impl/OEPostDataService.java
  23. 119 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/account/CmsGptCarController.java
  24. 1 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/goods/CmsGoodsDonSkuController.java
  25. 206 4
      netflix-web/src/main/java/com/cyksj/web/controller/mirror/MirrorController.java
  26. 0 0
      netflix-web/src/main/resources/application-dev.yml
  27. 0 0
      netflix-web/src/main/resources/application-prd.yml

+ 5 - 0
netflix-common/src/main/java/com/cyksj/common/constant/Constant.java

@@ -270,4 +270,9 @@ public interface Constant {
 	BigDecimal TWO_THOUSAND = BigDecimal.valueOf(200000);
 
 	Long CALUDE_GOODS_ID = 73l;
+
+	/**
+	 * GPT 镜像车队唯一账号
+	 */
+	String GPT_CAR_ONLY_ACCOUNT = "gptcaroo21213";
 }

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

+ 5 - 0
netflix-dao/src/main/java/com/cyksj/mapper/ChatgptUserMapper.java

@@ -2,10 +2,15 @@ package com.cyksj.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.cyksj.model.entity.ChatgptUser;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Update;
 
 /**
  * @author chan
  * @date 2024/3/19 10:44
  */
 public interface ChatgptUserMapper extends BaseMapper<ChatgptUser> {
+
+	@Update("update chatgpt_user cu set limit_num = #{gptLimitNum}, limit_time = #{gptLimitTime} where exists(select 1 from (select id from groups_trips where sku_id = #{skuId}) gt inner join groups_relation gr on gr.groups_id = gt.id where gr.id = cu.relationId)")
+	void updateChatGptCarUserNumAndLimitTime(@Param("skuId") Long skuId, @Param("gptLimitNum") Integer gptLimitNum, @Param("gptLimitTime") Long gptLimitTime);
 }

+ 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; // 以毫秒为单位
+
+}

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

@@ -0,0 +1,56 @@
+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, chatgpt_session s "
+        ,where = "r.conversation_id = c.conversationId AND s.carId = r.car_id "
+        ,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("r.car_id")
+    private String carId;
+
+    /**
+     * 车队名称
+     */
+    @DbField("r.car_name")
+    private String carName;
+
+    @DbField("s.isPlus")
+    private Integer isPLus;
+
+}

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

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

@@ -0,0 +1,76 @@
+package com.cyksj.model.views;
+
+import lombok.Data;
+
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * @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;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ChatgptCarInfoView that = (ChatgptCarInfoView) o;
+        return Objects.equals(carId, that.carId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(carId);
+    }
+}

+ 66 - 0
netflix-dao/src/main/java/com/cyksj/model/views/ChatgptCarSessionView.java

@@ -0,0 +1,66 @@
+package com.cyksj.model.views;
+
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: ChatgptCarSessionView
+ * 创建者: JavaZou
+ * 创建时间:2024/4/12 10:28
+ */
+@Getter
+@Setter
+@SearchBean(tables = "chatgpt_session cs",
+		where = "cs.isCar = 1")
+public class ChatgptCarSessionView {
+	@DbField("cs.id")
+	private Long id;
+
+	/**
+	 * 账号
+	 */
+	@DbField("cs.email")
+	private String email;
+
+	@DbField("cs.password")
+	private String password;
+
+	@DbField("cs.carID")
+	private String carID;
+
+
+	/**
+	 * 状态 1有效 1无效
+	 */
+	@DbField("cs.status")
+	private Integer status;
+
+	/**
+	 * 是否为plus会员
+	 */
+	@DbField("isPlus")
+	private Integer isPlus;
+
+	@DbField("isCar")
+	private Boolean isCar;
+
+	/**
+	 * 车名称
+	 */
+	@DbField("cs.car_name")
+	private String carName;
+
+	/**
+	 * 备注
+	 */
+	@DbField("cs.remark")
+	private String remark;
+
+	@DbField("cs.created_time")
+	private Date createdTime;
+}

+ 5 - 0
netflix-dao/src/main/java/com/cyksj/model/views/RenewalView.java

@@ -172,4 +172,9 @@ public class RenewalView {
      */
     @DbField("sku.is_mirror")
     private Boolean isMirror;
+    /**
+     * 是否为镜像车队服务
+     */
+    @DbField("sku.is_car")
+    private Boolean isCar;
 }

+ 152 - 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,13 @@ 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:","chatGpt gpt4 对话次数", 48 * 60 * 60L),
+        CHATGPT_CAR_LOW_CHAT("chatgpt:car:low:chat:","chatGpt 3.5 对话次数", 48 * 60 * 60L),
+
         ;
 
         private String name;

+ 53 - 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,48 @@ 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, ChatgptUser user, Boolean isCar);
+
+    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);
+
+    /**
+     * oai 触发车队限制
+     */
+    void carLimited(String carId, String userToken, Integer expTime);
+
+    /**
+     * 获取GPT session
+     */
+   String getGptSession(String account, String password);
 }

+ 517 - 9
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,38 @@ 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.SearchResult;
+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.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * @author chan
@@ -39,8 +53,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 +80,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) {
@@ -80,6 +112,24 @@ public class ChatGptAccountServiceImpl implements ChatGptAccountService {
         return refreshToken;
     }
 
+    @Override
+    public String getGptSession(String account, String password) {
+        String officialSession = "";
+        try {
+            Account at = new Account();
+            at.setAccount(account);
+            at.setPassword(password);
+            JSONObject loginResult = getLoginResult(at);
+            if (StringUtils.isBlank(loginResult.getStr("accessToken"))) {
+                throw BusinessRuntimeException.getInstance(loginResult.getStr("detail"));
+            }
+            officialSession = loginResult.toString();
+        } catch (Exception e) {
+            throw BusinessRuntimeException.getInstance("登录获取token错误 error:" + e.getMessage());
+        }
+        return officialSession;
+    }
+
     /**
      * 判断账号是否存在
      *
@@ -159,13 +209,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 CAR_GPT_DOMAIN + "/auth/logintoken?carid=" + carId + "&usertoken=" + chatgptUser.getUserToken();
+        } else {
+            throw BusinessRuntimeException.getInstance("服务器出了点问题");
+        }
+    }
+
     /**
      * 获取车位信息
      *
@@ -196,11 +266,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, 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, groupsTrips, user);
+            chatgptUser = createChatgptUser(groupsRelation, null, user, goodsDonSku);
         } else {
             updateChatgptUser(groupsRelation, user, chatgptUser);
         }
@@ -210,12 +291,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 synchronized 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 +361,407 @@ 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) {
+        LambdaQueryWrapper<ChatgptSession> wrapper = Wrappers.lambdaQuery(ChatgptSession.class);
+
+        if(StringUtils.isNotBlank(conversationRequest.getCarId())){
+            wrapper.eq(ChatgptSession::getCarId, conversationRequest.getCarId());
+        }else {
+            ChatgptUser chatgptUser = chatgptUserMapper.selectOne(Wrappers.lambdaQuery(ChatgptUser.class).eq(ChatgptUser::getUserToken, userToken));
+            if (chatgptUser.getSessionId() != null) {
+                wrapper.eq(ChatgptSession::getId, chatgptUser.getSessionId());
+            }
+        }
+        ChatgptSession chatgptSession = chatgptSessionMapper.selectOne(wrapper);
+        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);
+
+        if(StringUtils.isNotBlank(conversationRequest.getCarId())){
+            updateExperienceAndScore(conversationRequest.getCarId(), !"text-davinci-002-render-sha".equals(conversationRequest.getModel()), System.currentTimeMillis());
+        }
+
+    }
+
+
+    @Override
+    public ConversationLimitResponse conversationLimit(String userToken, String model, String carId, ChatgptUser chatgptUser, Boolean isCar) {
+        ConversationLimitResponse conversationLimitResponse = new ConversationLimitResponse();
+        conversationLimitResponse.setLimited(false);
+        if (!"text-davinci-002-render-sha".equals(model)) {
+            //如果不为车队 直接返回
+            if (chatgptUser.getIsCar()) {
+                conversationLimitResponse = isConversationAllowed(userToken, chatgptUser.getLimitNum(), chatgptUser.getLimitTime());
+            }
+            if(carId == null){
+                if (StringUtils.isNotBlank(userToken)) {
+                    if (chatgptUser.getSessionId() != null) {
+                        ChatgptSession chatgptSession = chatgptSessionMapper.selectById(chatgptUser.getSessionId());
+                        if(chatgptSession != null){
+                            carId = chatgptSession.getCarId();
+                        }
+                    }
+                }
+            }
+            //车队次数记录
+            String finalCarId = carId;
+            TASK_EXECUTOR.execute(() -> {
+                cardConversationRecord(finalCarId);
+            });
+        }
+
+        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("chatgpt:clears_in:" + carId)) {
+            return redisService.getExpire("chatgpt: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) {
+        SearchResult<ChatGptUserConversationRecordHistoryView> search = beanSearcher.search(ChatGptUserConversationRecordHistoryView.class, MapUtils.builder().field("userToken", userToken).limit(0, 2).build());
+        Set<ChatgptCarInfoView> res = search.getDataList().stream()
+                .map((historyView) -> {
+                    ChatgptCarInfoView chatgptCarInfoView = buildChatgptCarInfoView(historyView.getCarId(), historyView.getCarName(), historyView.getIsPLus(), redisService.zScore(RedisService.key.CHATGPT_CAR_SCORES.getName(), historyView.getCarId()));
+                    chatgptCarInfoView.setIsHistory(true);
+                    chatgptCarInfoView.setScore(Math.min(redisService.zScore(RedisService.key.CHATGPT_CAR_SCORES.getName(), historyView.getCarId()), 100));
+
+                    return chatgptCarInfoView;
+                }).collect(Collectors.toCollection(LinkedHashSet::new));
+
+        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) * 2;
+        Double lowExperienceScore = (double) redisService.zCount(RedisService.key.CHATGPT_CAR_LOW_CHAT.getName() + carId, currentTimestamp - (3 * 60 * 60 * 1000), currentTimestamp);
+        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);
+        Map<Object, Double> collect = lowestScorecarIds.stream().collect(Collectors.toMap(ZSetOperations.TypedTuple::getValue, ZSetOperations.TypedTuple::getScore));
+        List<ChatgptSession> chatgptSessions = chatgptSessionMapper.selectList(Wrappers.lambdaQuery(ChatgptSession.class).in(ChatgptSession::getCarId, collect.keySet()));
+        return chatgptSessions.stream().map((chatgptSession)-> buildChatgptCarInfoView(chatgptSession.getCarId(),chatgptSession.getCarName(), chatgptSession.getIsPlus(), collect.get(chatgptSession.getCarId())))
+                .sorted(Comparator.comparing(ChatgptCarInfoView::getScore)).collect(Collectors.toCollection(LinkedHashSet::new));
+    }
+
+    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, String carName, Integer isPlus, Double score) {
+        // 在这里根据carId获取相关信息并填充到ChatgptCarInfoView对象中
+        ChatgptCarInfoView view = new ChatgptCarInfoView();
+        view.setCarId(carId);
+        // 假设以下方法从Redis或其他服务获取数据
+        view.setScore(Math.min(score,100));
+        view.setStatus(score >= 50 ? "繁忙" : "空闲"); // 或“繁忙”
+
+        view.setType(isPlus == 1 ? "plus" : "3.5");
+        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));
+        }
+        view.setCarName(carName);
+        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.getSubTitle())
+                    .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 conversationId, String carid, String userToken) {
+        //同步对话
+        if (!StringUtils.isAnyBlank(conversationId, carid, userToken)) {
+            ChatgptUserConversationRecord chatgptUserConversationRecord = chatgptUserConversationRecordMapper.selectOne(Wrappers.lambdaQuery(ChatgptUserConversationRecord.class)
+                    .isNull(ChatgptUserConversationRecord::getConversationId)
+                    .eq(ChatgptUserConversationRecord::getCarId, carid)
+                    .eq(ChatgptUserConversationRecord::getUserToken, userToken)
+                    .orderByDesc(ChatgptUserConversationRecord::getId)
+                    .last(" limit 1"));
+            chatgptUserConversationRecord.setConversationId(conversationId);
+            chatgptUserConversationRecordMapper.updateById(chatgptUserConversationRecord);
+        }
+    }
+
+    @Override
+    public void carLimited(String carId, String userToken, Integer expTime) {
+        if(carId == null){
+            if (StringUtils.isNotBlank(userToken)) {
+                ChatgptUser chatgptUser = chatgptUserMapper.selectOne(Wrappers.lambdaQuery(ChatgptUser.class).eq(ChatgptUser::getUserToken, userToken));
+                if (chatgptUser != null && chatgptUser.getSessionId() != null) {
+                    ChatgptSession chatgptSession = chatgptSessionMapper.selectById(chatgptUser.getSessionId());
+                    if(chatgptSession != null){
+                        carId = chatgptSession.getCarId();
+                    }
+                }
+            }
+        }
+        redisService.set("chatgpt:clears_in:" + carId, expTime);
+    }
+
+    @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());
+                }
+            });
+        });
+    }
+
 }
 

+ 14 - 0
netflix-service/src/main/java/com/cyksj/service/groups/impl/GroupFunServiceImpl.java

@@ -1,5 +1,6 @@
 package com.cyksj.service.groups.impl;
 
+import com.cyksj.common.constant.Constant;
 import com.cyksj.mapper.GroupsMapper;
 import com.cyksj.mapper.GroupsRelationMapper;
 import com.cyksj.model.entity.GoodsDonSku;
@@ -61,6 +62,19 @@ public class GroupFunServiceImpl implements GroupsFuncService {
 		if (sku.getId() == 178l || sku.getId() == 423l) {
 			availableParkingNum = 4;
 		}
+		//如果是GPT镜像车队
+		//设置唯一账号
+		if (sku.getIsCar()) {
+			AccountView accountView = beanSearcher.searchFirst(AccountView.class, MapUtils.builder()
+					.field(AccountView::getGoodsId, sku.getGoodsId())
+					.field(AccountView::getAccount, Constant.GPT_CAR_ONLY_ACCOUNT).
+					onlySelect(AccountView::getId).build());
+			if (accountView != null) {
+				groups.setAccountId(accountView.getId());
+				groups.setStatus(GroupsTrips.Status.validity);
+				groups.setRemark("已设置镜像车队唯一账号");
+			}
+		}
 		groups.setAvailableParkingNum(availableParkingNum);
 
 		groupsMapper.insert(groups);

+ 2 - 0
netflix-service/src/main/java/com/cyksj/service/mange/CmsGoodsDonSkuService.java

@@ -12,4 +12,6 @@ import java.util.List;
  */
 public interface CmsGoodsDonSkuService extends IService<GoodsDonSku> {
 	void handleDiscountPurchaseGoods(GoodsDonSku sku, List<GoodsDiscountPlusPurchaseRelation> discountPlusPurchaseGoods);
+
+	void updateMirrorCarUserNum(GoodsDonSku sku);
 }

+ 30 - 0
netflix-service/src/main/java/com/cyksj/service/mange/goods/CmsGoodsDonSkuServiceImpl.java

@@ -1,9 +1,11 @@
 package com.cyksj.service.mange.goods;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.mapper.ChatgptUserMapper;
 import com.cyksj.mapper.GoodsDiscountPlusPurchaseRelationMapper;
 import com.cyksj.mapper.GoodsDonSkuMapper;
 import com.cyksj.model.entity.GoodsDiscountPlusPurchaseRelation;
@@ -32,6 +34,8 @@ public class CmsGoodsDonSkuServiceImpl extends ServiceImpl<GoodsDonSkuMapper,Goo
 
 	private final GoodsDonSkuMapper skuMapper;
 
+	private final ChatgptUserMapper chatgptUserMapper;
+
 	@Override
 	public void handleDiscountPurchaseGoods(GoodsDonSku sku, List<GoodsDiscountPlusPurchaseRelation> discountPlusPurchaseGoods) {
 		Long skuId = sku.getId();
@@ -85,6 +89,32 @@ public class CmsGoodsDonSkuServiceImpl extends ServiceImpl<GoodsDonSkuMapper,Goo
 		});
 	}
 
+	@Override
+	public void updateMirrorCarUserNum(GoodsDonSku sku) {
+		if (sku.getIsCar()) {
+			Long id = sku.getId();
+			GoodsDonSku dbSku = skuMapper.selectById(id);
+			Integer gptLimitNum = sku.getGptLimitNum();
+			Long gptLimitTime = sku.getGptLimitTime();
+
+			Integer dbGptLimitNum = dbSku.getGptLimitNum();
+			Long dbGptLimitTime = dbSku.getGptLimitTime();
+
+			if (gptLimitNum == null || gptLimitNum < 0) {
+				throw BusinessRuntimeException.getInstance("请输入正确的限制次数");
+			}
+
+			if (gptLimitTime == null || gptLimitTime < 0) {
+				throw BusinessRuntimeException.getInstance("请输入正确的限制时间");
+			}
+
+			if (!ObjectUtil.equal(gptLimitNum, dbGptLimitNum) || !ObjectUtil.equal(gptLimitTime, dbGptLimitTime)) {
+				//更新该规格镜像车队的用户次数 时间
+				chatgptUserMapper.updateChatGptCarUserNumAndLimitTime(sku.getId(), gptLimitNum, gptLimitTime);
+			}
+		}
+	}
+
 	public void checkPurchaseParamsAndSaveOrUpdate(Long goodsId, Long skuId, GoodsDiscountPlusPurchaseRelation discountGoods) {
 		Long disSid = discountGoods.getDisSid();
 		if (disSid == null) {

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

+ 119 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/account/CmsGptCarController.java

@@ -0,0 +1,119 @@
+package com.cyksj.web.controller.manage.account;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.ChatgptSessionMapper;
+import com.cyksj.model.entity.ChatgptSession;
+import com.cyksj.model.views.ChatgptCarSessionView;
+import com.cyksj.redis.RedisService;
+import com.cyksj.service.chatgpt.ChatGptAccountService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Optional;
+
+/** admin/镜像车队账号管理
+ * 项目名: yhlxj
+ * 文件名: CmsGptCarController
+ * 创建者: JavaZou
+ * 创建时间:2024/4/12 15:12
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RequestMapping("/manage/account/gptCar")
+@RestController
+public class CmsGptCarController {
+	private final ChatGptAccountService chatGptAccountService;
+
+	private final ChatgptSessionMapper chatgptSessionMapper;
+
+	private final RedisService redisService;
+
+	private final BeanSearcher beanSearcher;
+
+	private final HttpServletRequest request;
+
+	/**
+	 * 获取镜像车队账号列表
+	 */
+	@GetMapping("/get")
+	public Result<SearchResult<ChatgptCarSessionView>> getMirrorCarSession() {
+		SearchResult<ChatgptCarSessionView> search = beanSearcher.search(ChatgptCarSessionView.class, MapUtils.flatBuilder(request.getParameterMap())
+				.orderBy(ChatgptCarSessionView::getId).desc()
+				.build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 编辑镜像车队账号列表
+	 */
+	@PutMapping("/update")
+	public Result<String> updateMirrorCarSession(@RequestBody ChatgptSession chatgptSession) {
+		Long id = chatgptSession.getId();
+		ChatgptSession dbChatSession = chatgptSessionMapper.selectById(id);
+		if (!ObjectUtil.equal(dbChatSession.getEmail(), chatgptSession.getEmail()) || !ObjectUtil.equal(dbChatSession.getPassword(), chatgptSession.getPassword())) {
+			String gptSession = chatGptAccountService.getGptSession(dbChatSession.getEmail(), dbChatSession.getPassword());
+			chatgptSession.setOfficialSession(gptSession);
+		}
+		Integer selectCount = chatgptSessionMapper.selectCount(Wrappers.lambdaQuery(ChatgptSession.class)
+				.and(e -> e.eq(ChatgptSession::getEmail, chatgptSession.getEmail())
+						.or().eq(ChatgptSession::getCarId, chatgptSession.getCarId()))
+				.ne(ChatgptSession::getId, chatgptSession.getId()));
+		if (selectCount > 0) {
+			throw BusinessRuntimeException.getInstance("请检验车队ID或账号是否已存在");
+		}
+		chatgptSessionMapper.updateById(chatgptSession);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 删除镜像车队账号
+	 */
+	@DeleteMapping("/delete/{id}")
+	public Result<String> deleteMirrorCarSession(@PathVariable Long id) {
+		Optional.ofNullable(chatgptSessionMapper.selectById(id))
+				.ifPresent(e -> {
+					String carId = e.getCarId();
+					if (StrUtil.isNotBlank(carId)) {
+						chatgptSessionMapper.deleteById(e.getId());
+
+						String carConversationKey = RedisService.key.CHATGPT_CAR_CONVERSATION_LIMIT.getName() + ":" + carId;
+						String highCarChatKey = RedisService.key.CHATGPT_CAR_HIGH_CHAT.getName() + carId;
+						String lowCarChatKey = RedisService.key.CHATGPT_CAR_LOW_CHAT.getName() + carId;
+						//从zSet中删除车队
+						String carScoresKey = RedisService.key.CHATGPT_CAR_SCORES.getName();
+						redisService.zRemove(carScoresKey, carId);
+						redisService.del(highCarChatKey);
+						redisService.del(lowCarChatKey);
+						redisService.del(carConversationKey);
+					}
+				});
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 新增镜像车队账号
+	 */
+	@PostMapping("/post/mirror/car/session")
+	public Result<String> postMirrorCarSession(@RequestBody ChatgptSession chatgptSession) {
+		Integer selectCount = chatgptSessionMapper.selectCount(Wrappers.lambdaQuery(ChatgptSession.class)
+				.eq(ChatgptSession::getCarId, chatgptSession.getCarId()).or().eq(ChatgptSession::getEmail, chatgptSession.getEmail()));
+		if (selectCount > 0) {
+			throw BusinessRuntimeException.getInstance("请检验车队ID或账号是否已存在");
+		}
+		String gptSession = chatGptAccountService.getGptSession(chatgptSession.getEmail(), chatgptSession.getPassword());
+		chatgptSession.setIsCar(true);
+		chatgptSession.setOfficialSession(gptSession);
+		chatgptSessionMapper.insert(chatgptSession);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+}

+ 1 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/goods/CmsGoodsDonSkuController.java

@@ -161,6 +161,7 @@ public class CmsGoodsDonSkuController {
             Set<GoodsDonSku> benefitsSkuIds = Jsons.parseSet(benefitsList, GoodsDonSku.class);
             sku.setBenefitsList(Jsons.toJson(benefitsSkuIds));
         }
+        goodsDonSkuService.updateMirrorCarUserNum(sku);
         goodsDonSkuService.saveOrUpdate(sku);
         //给平台更新额外属性
         goodsDonService.updateGoodsSkuPrice(sku.getGoodsId(), null);

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

@@ -1,19 +1,40 @@
 package com.cyksj.web.controller.mirror;
 
+import cn.hutool.json.JSONObject;
+import com.baomidou.mybatisplus.extension.api.R;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
+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.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.apache.commons.lang3.StringUtils;
+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
  */
@@ -23,13 +44,20 @@ import java.io.IOException;
 @RequestMapping("/applets/mirror")
 public class MirrorController {
 
+    private static final GlobalThreadPoolTaskExecutor TASK_EXECUTOR = GlobalThreadPoolTaskExecutor.getInstance();
+
     private final ChatGptAccountService chatGptAccountService;
 
     private final HttpServletResponse response;
 
+    private final HttpServletRequest request;
+
+    private final BeanSearcher beanSearcher;
+
 
     /**
      * GPT车票跳转登录
+     *
      * @param relationId 车票id
      */
     @GetMapping("/chatGptMirror/{relationId}")
@@ -38,4 +66,178 @@ public class MirrorController {
 
         response.sendRedirect(chatGptAccountService.getLoginUrl(userId, relationId));
     }
+
+    /**
+     * GPT车票跳转登录
+     *
+     * @param userToken 用户token
+     * @param carId     车票id
+     * @return 登陆结果
+     */
+    @RequestMapping("/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 (session.getIsCar()) {
+                return GatewayResponse.SUCCESS.newBuilder().setMsg("登陆成功").setCode(1).toResult();
+            }
+        } catch (Exception e) {
+            log.error("服务器错误", e);
+            return GatewayResponse.FAIL.newBuilder().setMsg("服务器错误").setCode(0).toResult();
+        }
+        return GatewayResponse.FAIL.newBuilder().setMsg("服务器错误").setCode(0).toResult();
+    }
+
+
+    /**
+     * 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);
+        TASK_EXECUTOR.execute(() -> {
+            chatGptAccountService.genTitleSync(messageId, carid, userToken);
+        });
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+
+    /**
+     * 会话限制
+     */
+    @RequestMapping("/gpt/conversation/limit")
+    public void conversationLimit(@RequestParam(value = "isCar", defaultValue = "false") Boolean isCar, @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);
+        }
+        if(StringUtils.isNotBlank(conversationRequest.getCarId())){
+            //4.获取gpt-4 的规则限制
+            //text-davinci-002-render-sha 3.5
+            ConversationLimitResponse conversationLimitResponse = chatGptAccountService.conversationLimit(userToken, conversationRequest.getModel(), conversationRequest.getCarId(), user, isCar);
+
+            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);
+            }
+        }
+
+        //记录提问时间,车次,标题和模型
+        TASK_EXECUTOR.execute(() -> {
+            chatGptAccountService.saveConversationRecord(userToken, conversationRequest);
+        });
+        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("userToken", carChatGptUser.getUserToken());
+        SearchResult<ChatGptUserConversationRecordHistoryView> search = beanSearcher.search(ChatGptUserConversationRecordHistoryView.class, builder.build());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+    /**
+     * oai触发车队限制
+     */
+    @GetMapping("/gpt/car/limited")
+    public Result<String> carLimited(String carId, Integer expTime) {
+        String authorization = request.getHeader("Authorization");
+        String userToken = "";
+        if(StringUtils.isNotBlank(authorization)){
+            try {
+                userToken = authorization.substring(7);
+            }catch (Exception e){
+               log.error("获取用户token失败",e);
+               return GatewayResponse.SUCCESS.newBuilder().toResult();
+            }
+        }
+
+        String finalUserToken = userToken;
+        TASK_EXECUTOR.execute(()->{
+            chatGptAccountService.carLimited(carId, finalUserToken, expTime);
+        });
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
 }

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
netflix-web/src/main/resources/application-dev.yml


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
netflix-web/src/main/resources/application-prd.yml


Vissa filer visades inte eftersom för många filer har ändrats