ChatGptAccountServiceImpl.java 42 KB

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