ChatGptAccountServiceImpl.java 39 KB

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