ChatGptAccountServiceImpl.java 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767
  1. package com.cyksj.service.chatgpt.impl;
  2. import cn.hutool.core.date.DateTime;
  3. import cn.hutool.core.lang.UUID;
  4. import cn.hutool.http.HttpRequest;
  5. import cn.hutool.http.HttpResponse;
  6. import cn.hutool.http.HttpUtil;
  7. import cn.hutool.http.Method;
  8. import cn.hutool.json.JSONObject;
  9. import cn.hutool.json.JSONUtil;
  10. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  11. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  12. import com.cyksj.common.exception.BusinessRuntimeException;
  13. import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
  14. import com.cyksj.common.util.StringUtil;
  15. import com.cyksj.mapper.*;
  16. import com.cyksj.model.entity.*;
  17. import com.cyksj.model.request.gpt.ConversationRequest;
  18. import com.cyksj.model.response.ConversationLimitResponse;
  19. import com.cyksj.model.views.ChatGptUserConversationRecordHistoryView;
  20. import com.cyksj.model.views.ChatGptUserView;
  21. import com.cyksj.model.views.ChatgptCarInfoView;
  22. import com.cyksj.redis.RedisService;
  23. import com.cyksj.service.chatgpt.ChatGptAccountService;
  24. import com.cyksj.service.user.UserBindRelationService;
  25. import com.ejlchina.searcher.BeanSearcher;
  26. import com.ejlchina.searcher.SearchResult;
  27. import com.ejlchina.searcher.util.MapUtils;
  28. import lombok.RequiredArgsConstructor;
  29. import lombok.extern.slf4j.Slf4j;
  30. import org.apache.commons.lang3.StringUtils;
  31. import org.springframework.beans.factory.annotation.Value;
  32. import org.springframework.data.redis.core.ZSetOperations;
  33. import org.springframework.stereotype.Service;
  34. import javax.annotation.PostConstruct;
  35. import javax.imageio.stream.FileImageOutputStream;
  36. import java.nio.file.Files;
  37. import java.nio.file.Path;
  38. import java.time.LocalDateTime;
  39. import java.util.*;
  40. import java.util.stream.Collectors;
  41. /**
  42. * @author chan
  43. * @date 2024/3/19 11:01
  44. */
  45. @Service
  46. @Slf4j
  47. @RequiredArgsConstructor
  48. public class ChatGptAccountServiceImpl implements ChatGptAccountService {
  49. @Value("${chatgpt.domain}")
  50. private String GPT_DOMAIN;
  51. @Value("${chatgpt.car.domain}")
  52. private String CAR_GPT_DOMAIN;
  53. private static final String GPT_PROXY = "https://chat-chan-87jztgkf257d.xyhelper.net";
  54. private static final int MAX_REQUESTS = 40; // 3小时内最大请求次数
  55. private static final long WINDOW_SIZE = 3 * 60 * 60; // 3小时窗口的秒数
  56. private static final GlobalThreadPoolTaskExecutor TASK_EXECUTOR = GlobalThreadPoolTaskExecutor.getInstance();
  57. private final RedisService redisService;
  58. private final ChatgptUserMapper chatgptUserMapper;
  59. private final ChatgptSessionMapper chatgptSessionMapper;
  60. private final GroupsRelationMapper groupsRelationMapper;
  61. private final GroupsMapper groupsMapper;
  62. private final GoodsDonSkuMapper skuMapper;
  63. private final UserMapper userMapper;
  64. private final UserBindRelationService userBindRelationService;
  65. private final ChatgptUserConversationRecordMapper chatgptUserConversationRecordMapper;
  66. private final ChatgptUserConversationMapper chatgptUserConversationMapper;
  67. private final AccountMapper accountMapper;
  68. private final BeanSearcher beanSearcher;
  69. @Override
  70. public String addAccount(Account account) {
  71. if (isAccountExists(account)) {
  72. throw BusinessRuntimeException.getInstance("镜像服务该账号已存在.");
  73. }
  74. String refreshToken = "";
  75. String officialSession = "";
  76. try {
  77. JSONObject loginResult = getLoginResult(account);
  78. if (StringUtils.isBlank(loginResult.getStr("accessToken"))) {
  79. throw BusinessRuntimeException.getInstance(loginResult.getStr("detail"));
  80. }
  81. refreshToken = loginResult.getStr("refresh_token");
  82. officialSession = loginResult.toString();
  83. } catch (Exception e) {
  84. throw BusinessRuntimeException.getInstance("登录获取token错误 error:" + e.getMessage());
  85. }
  86. createChatgptSession(account, officialSession);
  87. return refreshToken;
  88. }
  89. @Override
  90. public String getGptSession(String account, String password) {
  91. String officialSession = "";
  92. try {
  93. Account at = new Account();
  94. at.setAccount(account);
  95. at.setPassword(password);
  96. JSONObject loginResult = getLoginResult(at);
  97. if (StringUtils.isBlank(loginResult.getStr("accessToken"))) {
  98. throw BusinessRuntimeException.getInstance(loginResult.getStr("detail"));
  99. }
  100. officialSession = loginResult.toString();
  101. } catch (Exception e) {
  102. throw BusinessRuntimeException.getInstance("登录获取token错误 error:" + e.getMessage());
  103. }
  104. return officialSession;
  105. }
  106. /**
  107. * 判断账号是否存在
  108. *
  109. * @param account 账号
  110. * @return 是否存在
  111. */
  112. private boolean isAccountExists(Account account) {
  113. Integer count = chatgptSessionMapper.selectCount(Wrappers.lambdaQuery(ChatgptSession.class).eq(ChatgptSession::getEmail, account.getAccount()));
  114. return count > 0;
  115. }
  116. /**
  117. * 获取登录结果
  118. *
  119. * @param account
  120. * @return
  121. * @throws Exception
  122. */
  123. private JSONObject getLoginResult(Account account) throws Exception {
  124. HttpRequest request = new HttpRequest(GPT_PROXY + "/getsession");
  125. request.form("username", account.getAccount());
  126. request.form("password", account.getPassword());
  127. request.header("Content-Type", "application/x-www-form-urlencoded");
  128. HttpResponse execute = request.method(Method.POST).execute();
  129. return new JSONObject(execute.body());
  130. }
  131. /**
  132. * 创建chatgptSession
  133. *
  134. * @param account
  135. * @param officialSession
  136. */
  137. private void createChatgptSession(Account account, String officialSession) {
  138. ChatgptSession chatgptSession = new ChatgptSession();
  139. chatgptSession.setOfficialSession(officialSession);
  140. chatgptSession.setEmail(account.getAccount());
  141. chatgptSession.setPassword(account.getPassword());
  142. chatgptSession.setIsPlus(1);
  143. chatgptSession.setAccountId(account.getId());
  144. chatgptSession.setStatus(1);
  145. chatgptSessionMapper.insert(chatgptSession);
  146. }
  147. /**
  148. * 更新账号
  149. *
  150. * @param account
  151. */
  152. @Override
  153. public void upAccount(Account account) {
  154. ChatgptSession chatgptSession = chatgptSessionMapper.selectOne(Wrappers.lambdaQuery(ChatgptSession.class).eq(ChatgptSession::getEmail, account.getAccount()));
  155. if (chatgptSession != null) {
  156. if (StringUtils.isNotBlank(account.getGptRefreshToken())) {
  157. chatgptSession.setOfficialSession(account.getGptRefreshToken());
  158. }
  159. chatgptSession.setEmail(account.getAccount());
  160. chatgptSession.setPassword(account.getPassword());
  161. chatgptSession.setIsPlus(1);
  162. chatgptSession.setAccountId(account.getId());
  163. chatgptSession.setStatus(1);
  164. chatgptSessionMapper.updateById(chatgptSession);
  165. }
  166. }
  167. /**
  168. * 获取登录url
  169. *
  170. * @param userId
  171. * @param relationId
  172. * @return
  173. */
  174. @Override
  175. public String getLoginUrl(Long userId, Long relationId) {
  176. GroupsRelation groupsRelation = getGroupsRelation(userId, relationId);
  177. GroupsTrips groupsTrips = getGroupsTrips(groupsRelation);
  178. GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId());
  179. if (goodsDonSku != null && goodsDonSku.getIsMirror()) {
  180. ChatgptUser chatgptUser = getChatgptUser(userId, relationId, groupsRelation, groupsTrips, goodsDonSku);
  181. return GPT_DOMAIN + "/login_token?access_token=" + chatgptUser.getUserToken();
  182. } else {
  183. throw BusinessRuntimeException.getInstance("服务器出了点问题");
  184. }
  185. }
  186. /**
  187. * 获取车队登录url
  188. *
  189. * @param userId
  190. * @param carId
  191. * @return
  192. */
  193. @Override
  194. public String getCarLoginUrl(Long userId, Long relationId, String carId) {
  195. GroupsRelation groupsRelation = getGroupsRelation(userId, relationId);
  196. GroupsTrips groupsTrips = getGroupsTrips(groupsRelation);
  197. GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId());
  198. if (goodsDonSku != null && goodsDonSku.getIsMirror() && goodsDonSku.getIsCar()) {
  199. ChatgptUser chatgptUser = getChatgptCarUser(userId, relationId, groupsRelation, goodsDonSku);
  200. return CAR_GPT_DOMAIN + "/auth/logintoken?carid=" + carId + "&usertoken=" + chatgptUser.getUserToken();
  201. } else {
  202. throw BusinessRuntimeException.getInstance("服务器出了点问题");
  203. }
  204. }
  205. /**
  206. * 获取车位信息
  207. *
  208. * @param userId
  209. * @param relationId
  210. * @return
  211. */
  212. private GroupsRelation getGroupsRelation(Long userId, Long relationId) {
  213. List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
  214. GroupsRelation groupsRelation = groupsRelationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class).in(GroupsRelation::getUserId, userIdList).eq(GroupsRelation::getId, relationId));
  215. if (groupsRelation == null) {
  216. throw BusinessRuntimeException.getInstance("车票不存在");
  217. }
  218. return groupsRelation;
  219. }
  220. /**
  221. * 获取车队信息
  222. *
  223. * @param groupsRelation
  224. * @return
  225. */
  226. private GroupsTrips getGroupsTrips(GroupsRelation groupsRelation) {
  227. GroupsTrips groupsTrips = groupsMapper.selectById(groupsRelation.getGroupsId());
  228. if (groupsTrips == null) {
  229. throw BusinessRuntimeException.getInstance("车队异常");
  230. }
  231. return groupsTrips;
  232. }
  233. private ChatgptUser getChatgptUser(Long userId, Long relationId, GroupsRelation groupsRelation, GroupsTrips groupsTrips, GoodsDonSku goodsDonSku) {
  234. ChatgptUser chatgptUser = chatgptUserMapper.selectOne(Wrappers.lambdaQuery(ChatgptUser.class).eq(ChatgptUser::getRelationId, relationId));
  235. User user = userMapper.selectById(userId);
  236. if (chatgptUser == null) {
  237. chatgptUser = createChatgptUser(groupsRelation, groupsTrips, user, goodsDonSku);
  238. } else {
  239. updateChatgptUser(groupsRelation, user, chatgptUser);
  240. }
  241. return chatgptUser;
  242. }
  243. private ChatgptUser getChatgptCarUser(Long userId, Long relationId, GroupsRelation groupsRelation, GoodsDonSku goodsDonSku) {
  244. ChatgptUser chatgptUser = chatgptUserMapper.selectOne(Wrappers.lambdaQuery(ChatgptUser.class).eq(ChatgptUser::getRelationId, relationId));
  245. User user = userMapper.selectById(userId);
  246. if (chatgptUser == null) {
  247. chatgptUser = createChatgptUser(groupsRelation, null, user, goodsDonSku);
  248. } else {
  249. updateChatgptUser(groupsRelation, user, chatgptUser);
  250. }
  251. return chatgptUser;
  252. }
  253. /**
  254. * 创建chatgptUser
  255. */
  256. private synchronized ChatgptUser createChatgptUser(GroupsRelation groupsRelation, GroupsTrips groupsTrips, User user, GoodsDonSku goodsDonSku) {
  257. ChatgptUser chatgptUser = new ChatgptUser();
  258. if (!goodsDonSku.getIsCar()) {
  259. ChatgptSession chatgptSession = chatgptSessionMapper.selectOne(Wrappers.lambdaQuery(ChatgptSession.class).eq(ChatgptSession::getAccountId, groupsTrips.getAccountId()));
  260. if (chatgptSession == null) {
  261. Account account = accountMapper.selectById(groupsTrips.getAccountId());
  262. String officialSession = "";
  263. try {
  264. JSONObject loginResult = getLoginResult(account);
  265. if (StringUtils.isBlank(loginResult.getStr("accessToken"))) {
  266. throw BusinessRuntimeException.getInstance(loginResult.getStr("detail"));
  267. }
  268. officialSession = loginResult.toString();
  269. } catch (Exception e) {
  270. throw BusinessRuntimeException.getInstance("配置账号错误 msg: " + e.getMessage());
  271. }
  272. createChatgptSession(account, officialSession);
  273. chatgptSession = chatgptSessionMapper.selectOne(Wrappers.lambdaQuery(ChatgptSession.class).eq(ChatgptSession::getAccountId, groupsTrips.getAccountId()));
  274. }
  275. chatgptUser.setSessionId(chatgptSession.getId());
  276. }
  277. if (goodsDonSku.getIsCar()) {
  278. chatgptUser.setIsCar(true);
  279. chatgptUser.setLimitNum(goodsDonSku.getGptLimitNum());
  280. chatgptUser.setLimitTime(goodsDonSku.getGptLimitTime());
  281. }
  282. chatgptUser.setExpireTime(groupsRelation.getExpiryTime());
  283. chatgptUser.setIsPlus(1);
  284. chatgptUser.setName(user.getNickname());
  285. chatgptUser.setImg(getWxImg(user.getHeadimgurl(), user));
  286. chatgptUser.setRelationId(groupsRelation.getId());
  287. chatgptUser.setUserToken(UUID.randomUUID().toString());
  288. chatgptUserMapper.insert(chatgptUser);
  289. return chatgptUser;
  290. }
  291. private void updateChatgptUser(GroupsRelation groupsRelation, User user, ChatgptUser chatgptUser) {
  292. chatgptUser.setName(user.getNickname());
  293. chatgptUser.setImg(getWxImg(user.getHeadimgurl(), user));
  294. chatgptUser.setExpireTime(groupsRelation.getExpiryTime());
  295. chatgptUserMapper.updateById(chatgptUser);
  296. }
  297. /**
  298. * 更换wx头像至oss
  299. */
  300. private String getWxImg(String headimgurl, User user) {
  301. if (headimgurl.contains("thirdwx.qlogo.cn")) {
  302. try {
  303. byte[] body = HttpUtil.downloadBytes(headimgurl);
  304. Path tempFile = Files.createTempFile("wxheadimg-" + user.getId(), ".jpeg");
  305. try (FileImageOutputStream imageOutput = new FileImageOutputStream(tempFile.toFile())) {
  306. imageOutput.write(body, 0, body.length);
  307. }
  308. Map<String, Object> paramMap = new HashMap<>();
  309. paramMap.put("file", tempFile.toFile());
  310. JSONObject result = JSONUtil.parseObj(HttpUtil.post("https://files.liuliangbang.vip/pic/ups", paramMap));
  311. return result.getJSONObject("value").getJSONArray("saved").getJSONObject(0).getJSONObject("info").getStr("cdnUrl");
  312. } catch (Exception ex) {
  313. log.error("上传微信头像错误!msg:{}", StringUtil.getErrorText(ex));
  314. return "./avatars.png";
  315. }
  316. } else {
  317. return user.getHeadimgurl();
  318. }
  319. }
  320. @Override
  321. public ChatgptUser findByUserTokenAndExpireTimeAfter(String userToken, LocalDateTime now) {
  322. return chatgptUserMapper.selectOne(Wrappers.lambdaQuery(ChatgptUser.class).eq(ChatgptUser::getUserToken, userToken).gt(ChatgptUser::getExpireTime, now));
  323. }
  324. @Override
  325. public void saveConversationRecord(String userToken, ConversationRequest conversationRequest) {
  326. LambdaQueryWrapper<ChatgptSession> wrapper = Wrappers.lambdaQuery(ChatgptSession.class);
  327. if(StringUtils.isNotBlank(conversationRequest.getCarId())){
  328. wrapper.eq(ChatgptSession::getCarId, conversationRequest.getCarId());
  329. }else {
  330. ChatgptUser chatgptUser = chatgptUserMapper.selectOne(Wrappers.lambdaQuery(ChatgptUser.class).eq(ChatgptUser::getUserToken, userToken));
  331. if (chatgptUser.getSessionId() != null) {
  332. wrapper.eq(ChatgptSession::getId, chatgptUser.getSessionId());
  333. }
  334. }
  335. ChatgptSession chatgptSession = chatgptSessionMapper.selectOne(wrapper);
  336. ChatgptUserConversationRecord chatgptUserConversationRecord = new ChatgptUserConversationRecord();
  337. chatgptUserConversationRecord.setUserToken(userToken);
  338. chatgptUserConversationRecord.setCarId(chatgptSession.getCarId());
  339. chatgptUserConversationRecord.setCarName(chatgptSession.getCarName());
  340. if (StringUtils.isBlank(conversationRequest.getConversation_id())) {
  341. chatgptUserConversationRecord.setMessageId(conversationRequest.getMessages().get(0).getId());
  342. } else {
  343. chatgptUserConversationRecord.setConversationId(conversationRequest.getConversation_id());
  344. }
  345. chatgptUserConversationRecord.setModel(conversationRequest.getModel());
  346. chatgptUserConversationRecordMapper.insert(chatgptUserConversationRecord);
  347. if(StringUtils.isNotBlank(conversationRequest.getCarId())){
  348. updateExperienceAndScore(conversationRequest.getCarId(), !"text-davinci-002-render-sha".equals(conversationRequest.getModel()), System.currentTimeMillis());
  349. }
  350. }
  351. @Override
  352. public ConversationLimitResponse conversationLimit(String userToken, String model, String carId, ChatgptUser chatgptUser, Boolean isCar) {
  353. ConversationLimitResponse conversationLimitResponse = new ConversationLimitResponse();
  354. conversationLimitResponse.setLimited(false);
  355. if (!"text-davinci-002-render-sha".equals(model)) {
  356. //如果不为车队 直接返回
  357. if (chatgptUser.getIsCar()) {
  358. conversationLimitResponse = isConversationAllowed(userToken, chatgptUser.getLimitNum(), chatgptUser.getLimitTime());
  359. }
  360. if(carId == null){
  361. if (StringUtils.isNotBlank(userToken)) {
  362. if (chatgptUser.getSessionId() != null) {
  363. ChatgptSession chatgptSession = chatgptSessionMapper.selectById(chatgptUser.getSessionId());
  364. if(chatgptSession != null){
  365. carId = chatgptSession.getCarId();
  366. }
  367. }
  368. }
  369. }
  370. //车队次数记录
  371. String finalCarId = carId;
  372. TASK_EXECUTOR.execute(() -> {
  373. cardConversationRecord(finalCarId);
  374. });
  375. }
  376. return conversationLimitResponse;
  377. }
  378. /**
  379. * 检查是否允许进行进行提问
  380. *
  381. * @param userToken 用户token
  382. * @return true 如果允许请求,false 如果请求被限制
  383. */
  384. public ConversationLimitResponse isConversationAllowed(String userToken, int limit, Long limitTime) {
  385. String key = RedisService.key.CHATGPT_CONVERSATION_LIMIT.getName() + ":" + userToken;
  386. long currentTimeMillis = System.currentTimeMillis();
  387. long windowStartMillis = currentTimeMillis - (limitTime * 60 * 60) * 1000;
  388. ConversationLimitResponse conversationLimitResponse = new ConversationLimitResponse();
  389. // 清除时间窗口之前的请求记录
  390. redisService.zRemoveRangeByScore(key, 0, windowStartMillis);
  391. Long currentSize = redisService.zCard(key);
  392. if (currentSize != null && currentSize >= limit) {
  393. // 如果当前请求次数超过限制,则拒绝请求
  394. Set<Object> times = redisService.zRangeByScore(key, 0, currentTimeMillis, 0, 1);
  395. Long oldestTime = (Long) times.stream().findFirst().orElse(null);
  396. if (oldestTime != null) {
  397. // 下一次可用时间是最早请求时间之后的3小时
  398. long nextAvailableTime = oldestTime + (limitTime * 60 * 60 * 1000);
  399. conversationLimitResponse.setNextAvailableTime(nextAvailableTime);
  400. }
  401. conversationLimitResponse.setLimited(true);
  402. return conversationLimitResponse;
  403. } else {
  404. // 如果未超过限制,记录当前请求的时间戳
  405. redisService.zAdd(key, currentTimeMillis, currentTimeMillis);
  406. // 设置ZSet的过期时间,窗口大小加上一段冗余时间
  407. redisService.expire(key, (limitTime * 60 * 60) + 20);
  408. conversationLimitResponse.setLimited(false);
  409. return conversationLimitResponse;
  410. }
  411. }
  412. /**
  413. * 记录车队的提问次数
  414. */
  415. public boolean cardConversationRecord(String carid) {
  416. String key = RedisService.key.CHATGPT_CAR_CONVERSATION_LIMIT.getName() + ":" + carid;
  417. long currentTimeMillis = System.currentTimeMillis();
  418. long windowStartMillis = currentTimeMillis - (WINDOW_SIZE) * 1000;
  419. // 清除时间窗口之前的请求记录
  420. redisService.zRemoveRangeByScore(key, 0, windowStartMillis);
  421. Long currentSize = redisService.zCard(key);
  422. if (currentSize != null && currentSize >= MAX_REQUESTS) {
  423. // 如果当前请求次数超过限制,则拒绝请求
  424. return false;
  425. } else {
  426. // 如果未超过限制,记录当前请求的时间戳
  427. redisService.zAdd(key, currentTimeMillis, currentTimeMillis);
  428. // 设置ZSet的过期时间,窗口大小加上一段冗余时间
  429. redisService.expire(key, (WINDOW_SIZE * 60 * 60) + 20);
  430. return true;
  431. }
  432. }
  433. /**
  434. * 获取指定用户ID在滑动窗口内的提问次数
  435. *
  436. * @param userToken 用户token
  437. * @return 滑动窗口内的请求次数
  438. */
  439. @Override
  440. public Long getConversationCount(String userToken, Long limitTime) {
  441. String key = RedisService.key.CHATGPT_CONVERSATION_LIMIT.getName() + ":" + userToken;
  442. long currentTimeMillis = System.currentTimeMillis();
  443. long windowStartMillis = currentTimeMillis - (limitTime * 60 * 60) * 1000;
  444. // 清除时间窗口之前的请求记录(可选,根据需要决定是否在此处清理过期记录)
  445. redisService.zRemoveRangeByScore(key, 0, windowStartMillis);
  446. // 获取当前窗口内的请求次数
  447. Long currentSize = redisService.zCard(key);
  448. return currentSize != null ? currentSize : 0L;
  449. }
  450. /**
  451. * 获取指定车队ID在滑动窗口内的提问次数
  452. *
  453. * @param carId 车队ID
  454. * @return 滑动窗口内的请求次数
  455. */
  456. private Long getCarConversationCount(String carId, Long limitTime) {
  457. String key = RedisService.key.CHATGPT_CAR_CONVERSATION_LIMIT.getName() + ":" + carId;
  458. long currentTimeMillis = System.currentTimeMillis();
  459. long windowStartMillis = currentTimeMillis - (limitTime * 60 * 60) * 1000;
  460. // 清除时间窗口之前的请求记录(可选,根据需要决定是否在此处清理过期记录)
  461. redisService.zRemoveRangeByScore(key, 0, windowStartMillis);
  462. // 获取当前窗口内的请求次数
  463. Long currentSize = redisService.zCard(key);
  464. return currentSize != null ? currentSize : 0L;
  465. }
  466. @Override
  467. public ChatgptSession checkSession(String carId) {
  468. ChatgptSession chatgptSession = chatgptSessionMapper.selectOne(Wrappers.lambdaQuery(ChatgptSession.class).eq(ChatgptSession::getCarId, carId).eq(ChatgptSession::getIsCar, true).last(" limit 1"));
  469. if (chatgptSession == null) {
  470. throw BusinessRuntimeException.getInstance("车队不存在");
  471. }
  472. return chatgptSession;
  473. }
  474. private Long carOaiLimit(String carId) {
  475. if (redisService.hasKey("chatgpt:clears_in:" + carId)) {
  476. return redisService.getExpire("chatgpt:clears_in:" + carId);
  477. }
  478. return 0L;
  479. }
  480. @Override
  481. public Boolean checkCarAccount(long userId, Long relationId) {
  482. GroupsRelation groupsRelation = getGroupsRelation(userId, relationId);
  483. GroupsTrips groupsTrips = getGroupsTrips(groupsRelation);
  484. GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId());
  485. if (goodsDonSku != null && goodsDonSku.getIsMirror() && goodsDonSku.getIsCar()) {
  486. ChatgptUser chatgptUser = getChatgptCarUser(userId, relationId, groupsRelation, goodsDonSku);
  487. return true;
  488. } else {
  489. return false;
  490. }
  491. }
  492. @Override
  493. public ChatgptUser getCarChatGptUser(long userId, Long relationId) {
  494. GroupsRelation groupsRelation = getGroupsRelation(userId, relationId);
  495. GroupsTrips groupsTrips = getGroupsTrips(groupsRelation);
  496. GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId());
  497. if (goodsDonSku != null && goodsDonSku.getIsMirror() && goodsDonSku.getIsCar()) {
  498. ChatgptUser chatgptUser = getChatgptCarUser(userId, relationId, groupsRelation, goodsDonSku);
  499. return chatgptUser;
  500. }
  501. return null;
  502. }
  503. /**
  504. * 获取车队信息
  505. */
  506. @Override
  507. public Set<ChatgptCarInfoView> getCarInfoList(String userToken) {
  508. SearchResult<ChatGptUserConversationRecordHistoryView> search = beanSearcher.search(ChatGptUserConversationRecordHistoryView.class, MapUtils.builder().field("userToken", userToken).limit(0, 2).build());
  509. Set<ChatgptCarInfoView> res = search.getDataList().stream()
  510. .map((historyView) -> {
  511. ChatgptCarInfoView chatgptCarInfoView = buildChatgptCarInfoView(historyView.getCarId(), historyView.getCarName(), historyView.getIsPLus(), redisService.zScore(RedisService.key.CHATGPT_CAR_SCORES.getName(), historyView.getCarId()));
  512. chatgptCarInfoView.setIsHistory(true);
  513. chatgptCarInfoView.setScore(Math.min(redisService.zScore(RedisService.key.CHATGPT_CAR_SCORES.getName(), historyView.getCarId()), 100));
  514. return chatgptCarInfoView;
  515. }).collect(Collectors.toCollection(LinkedHashSet::new));
  516. Set<ChatgptCarInfoView> lowestScoreFleets = getLowestScoreFleets(10);
  517. res.addAll(lowestScoreFleets);
  518. return res;
  519. }
  520. // 更新体验并计算评分
  521. public void updateExperienceAndScore(String carId, boolean isHighLevel, long timestamp) {
  522. // 定义键名
  523. String key = isHighLevel ? RedisService.key.CHATGPT_CAR_HIGH_CHAT.getName() + carId : RedisService.key.CHATGPT_CAR_LOW_CHAT.getName() + carId;
  524. double scoreToAdd = isHighLevel ? 2.0 : 1.0; // 分数更新规则
  525. // 更新体验数据
  526. redisService.zAdd(key, timestamp, String.valueOf(timestamp));
  527. // 更新车队评分
  528. redisService.zIncrementScore(RedisService.key.CHATGPT_CAR_SCORES.getName(), carId, scoreToAdd);
  529. // 清理旧数据(可选)和重新计算评分(根据需要实现)
  530. cleanupOldExperiencesAndRecalculateScore(carId, timestamp);
  531. }
  532. // 清理旧数据和重新计算评分
  533. public void cleanupOldExperiencesAndRecalculateScore(String carId, long currentTimestamp) {
  534. long threeHoursAgo = currentTimestamp - (3 * 60 * 60 * 1000); // 3小时前的时间戳
  535. // 清理高级体验旧数据
  536. redisService.zRemoveRangeByScore(RedisService.key.CHATGPT_CAR_HIGH_CHAT.getName() + carId, 0, threeHoursAgo);
  537. // 清理低级体验旧数据
  538. redisService.zRemoveRangeByScore(RedisService.key.CHATGPT_CAR_LOW_CHAT.getName() + carId, 0, threeHoursAgo);
  539. // 重新计算评分
  540. recalculateScore(carId, currentTimestamp);
  541. }
  542. // 重新计算指定车队的评分
  543. private void recalculateScore(String carId, long currentTimestamp) {
  544. // 实际应用中,你需要根据高级体验和低级体验的数量重新计算得分
  545. Double highExperienceScore = (double) redisService.zCount(RedisService.key.CHATGPT_CAR_HIGH_CHAT.getName() + carId, currentTimestamp - (3 * 60 * 60 * 1000), currentTimestamp) * 2;
  546. Double lowExperienceScore = (double) redisService.zCount(RedisService.key.CHATGPT_CAR_LOW_CHAT.getName() + carId, currentTimestamp - (3 * 60 * 60 * 1000), currentTimestamp);
  547. double newScore = highExperienceScore + lowExperienceScore;
  548. redisService.zAdd(RedisService.key.CHATGPT_CAR_SCORES.getName(), newScore, carId);
  549. }
  550. // 获取得分最低的N个车队的方法
  551. public Set<ChatgptCarInfoView> getLowestScoreFleets(int count) {
  552. Set<ZSetOperations.TypedTuple<Object>> lowestScorecarIds;
  553. Long fleetsSize = redisService.zCard(RedisService.key.CHATGPT_CAR_SCORES.getName());
  554. // 构建ChatgptCarInfoView集合
  555. if (fleetsSize != null && fleetsSize > 10) {
  556. TASK_EXECUTOR.execute(() -> {
  557. initFleets(true);
  558. });
  559. } else {
  560. initFleets(false);
  561. }
  562. lowestScorecarIds = redisService.zRangeWithScores(RedisService.key.CHATGPT_CAR_SCORES.getName(), 0L, count - 1);
  563. Map<Object, Double> collect = lowestScorecarIds.stream().collect(Collectors.toMap(ZSetOperations.TypedTuple::getValue, ZSetOperations.TypedTuple::getScore));
  564. List<ChatgptSession> chatgptSessions = chatgptSessionMapper.selectList(Wrappers.lambdaQuery(ChatgptSession.class).in(ChatgptSession::getCarId, collect.keySet()));
  565. return chatgptSessions.stream().map((chatgptSession)-> buildChatgptCarInfoView(chatgptSession.getCarId(),chatgptSession.getCarName(), chatgptSession.getIsPlus(), collect.get(chatgptSession.getCarId())))
  566. .sorted(Comparator.comparing(ChatgptCarInfoView::getScore)).collect(Collectors.toCollection(LinkedHashSet::new));
  567. }
  568. public void initFleets(Boolean flag) {
  569. LambdaQueryWrapper<ChatgptSession> wrapper = Wrappers.lambdaQuery(ChatgptSession.class);
  570. if (flag) {
  571. Set<ZSetOperations.TypedTuple<Object>> fleets = redisService.zRangeWithScores(RedisService.key.CHATGPT_CAR_SCORES.getName(), 0L, -1);
  572. List<Object> collect = fleets.stream().map(ZSetOperations.TypedTuple::getValue).collect(Collectors.toList());
  573. wrapper.notIn(collect.size() > 0, ChatgptSession::getCarId, collect);
  574. }
  575. //如果存在未存在redis中的车辆则同步进 chatgpt:car:scores
  576. List<ChatgptSession> chatgptSessions = chatgptSessionMapper.selectList(wrapper.eq(ChatgptSession::getIsCar, true));
  577. chatgptSessions.forEach((chatgptSession) -> {
  578. if (!redisService.checkValueExistsInZSet(RedisService.key.CHATGPT_CAR_SCORES.getName(), chatgptSession.getCarId())) {
  579. redisService.zAdd(RedisService.key.CHATGPT_CAR_SCORES.getName(), 0, chatgptSession.getCarId());
  580. }
  581. });
  582. }
  583. // 构建ChatgptCarInfoView对象
  584. private ChatgptCarInfoView buildChatgptCarInfoView(String carId, String carName, Integer isPlus, Double score) {
  585. // 在这里根据carId获取相关信息并填充到ChatgptCarInfoView对象中
  586. ChatgptCarInfoView view = new ChatgptCarInfoView();
  587. view.setCarId(carId);
  588. // 假设以下方法从Redis或其他服务获取数据
  589. view.setScore(Math.min(score,100));
  590. view.setStatus(score >= 50 ? "繁忙" : "空闲"); // 或“繁忙”
  591. view.setType(isPlus == 1 ? "plus" : "3.5");
  592. view.setGptLimit(40); // 假设值
  593. int use = getCarConversationCount(carId, 3L).intValue();
  594. view.setUse(Math.min(use, 40));
  595. Long aLong = carOaiLimit(carId);
  596. if (aLong != 0L) {
  597. view.setStatus("停运");
  598. view.setExpTime(new DateTime(System.currentTimeMillis() + aLong * 1000));
  599. }
  600. view.setCarName(carName);
  601. return view;
  602. }
  603. @Override
  604. public ChatGptUserView getUserInfo(long userId, Long relationId) {
  605. if (checkCarAccount(userId, relationId)) {
  606. GroupsRelation groupsRelation = getGroupsRelation(userId, relationId);
  607. GroupsTrips groupsTrips = getGroupsTrips(groupsRelation);
  608. GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId());
  609. ChatgptUser chatgptUser = chatgptUserMapper.selectOne(Wrappers.lambdaQuery(ChatgptUser.class).eq(ChatgptUser::getRelationId, relationId));
  610. return ChatGptUserView.builder()
  611. .skuName(goodsDonSku.getSubTitle())
  612. .expireTime(chatgptUser.getExpireTime())
  613. .limitNum(chatgptUser.getLimitNum())
  614. .limitTime(chatgptUser.getLimitTime())
  615. .use(getConversationCount(chatgptUser.getUserToken(), chatgptUser.getLimitTime()))
  616. .build();
  617. } else {
  618. throw BusinessRuntimeException.getInstance("您还未购买该车票");
  619. }
  620. }
  621. @Override
  622. public void genTitleSync(String conversationId, String carid, String userToken) {
  623. //同步对话
  624. if (!StringUtils.isAnyBlank(conversationId, carid, userToken)) {
  625. ChatgptUserConversationRecord chatgptUserConversationRecord = chatgptUserConversationRecordMapper.selectOne(Wrappers.lambdaQuery(ChatgptUserConversationRecord.class)
  626. .isNull(ChatgptUserConversationRecord::getConversationId)
  627. .eq(ChatgptUserConversationRecord::getCarId, carid)
  628. .eq(ChatgptUserConversationRecord::getUserToken, userToken)
  629. .orderByDesc(ChatgptUserConversationRecord::getId)
  630. .last(" limit 1"));
  631. chatgptUserConversationRecord.setConversationId(conversationId);
  632. chatgptUserConversationRecordMapper.updateById(chatgptUserConversationRecord);
  633. }
  634. }
  635. @Override
  636. public void carLimited(String carId, String userToken, Integer expTime) {
  637. if(carId == null){
  638. if (StringUtils.isNotBlank(userToken)) {
  639. ChatgptUser chatgptUser = chatgptUserMapper.selectOne(Wrappers.lambdaQuery(ChatgptUser.class).eq(ChatgptUser::getUserToken, userToken));
  640. if (chatgptUser != null && chatgptUser.getSessionId() != null) {
  641. ChatgptSession chatgptSession = chatgptSessionMapper.selectById(chatgptUser.getSessionId());
  642. if(chatgptSession != null){
  643. carId = chatgptSession.getCarId();
  644. }
  645. }
  646. }
  647. }
  648. redisService.set("chatgpt:clears_in:" + carId, expTime);
  649. }
  650. @PostConstruct
  651. public void init() {
  652. //如果存在未存在redis中的车辆则同步进 chatgpt:car:scores
  653. TASK_EXECUTOR.execute(() -> {
  654. Set<ZSetOperations.TypedTuple<Object>> lowestScorecarIds = redisService.zRangeWithScores(RedisService.key.CHATGPT_CAR_SCORES.getName(), 0L, -1);
  655. List<Object> collect = lowestScorecarIds.stream().map(ZSetOperations.TypedTuple::getValue).collect(Collectors.toList());
  656. List<ChatgptSession> chatgptSessions = chatgptSessionMapper.selectList(Wrappers.lambdaQuery(ChatgptSession.class).notIn(collect.size() > 0, ChatgptSession::getCarId, collect).eq(ChatgptSession::getIsCar, true));
  657. chatgptSessions.forEach((chatgptSession) -> {
  658. if (!redisService.checkValueExistsInZSet(RedisService.key.CHATGPT_CAR_SCORES.getName(), chatgptSession.getCarId())) {
  659. redisService.zAdd(RedisService.key.CHATGPT_CAR_SCORES.getName(), 0, chatgptSession.getCarId());
  660. }
  661. });
  662. });
  663. }
  664. }