| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416 |
- package com.cyksj.service.claude.impl;
- import cn.hutool.core.date.DateTime;
- import cn.hutool.core.lang.UUID;
- import cn.hutool.core.util.RandomUtil;
- import cn.hutool.core.util.StrUtil;
- import cn.hutool.http.HttpRequest;
- import cn.hutool.http.HttpResponse;
- import cn.hutool.http.HttpUtil;
- import cn.hutool.http.Method;
- import cn.hutool.json.JSONObject;
- import cn.hutool.json.JSONUtil;
- import com.baomidou.mybatisplus.core.toolkit.Wrappers;
- import com.cyksj.common.exception.BusinessRuntimeException;
- import com.cyksj.common.util.Jsons;
- import com.cyksj.common.util.StringUtil;
- import com.cyksj.mapper.*;
- import com.cyksj.model.entity.*;
- import com.cyksj.model.request.gpt.ConversationRequest;
- import com.cyksj.model.response.ConversationLimitResponse;
- import com.cyksj.model.views.ClaudeUserView;
- import com.cyksj.redis.RedisService;
- import com.cyksj.service.claude.ClaudeService;
- import com.cyksj.service.sys.SysConfigService;
- import com.cyksj.service.user.UserBindRelationService;
- import lombok.RequiredArgsConstructor;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.dao.DuplicateKeyException;
- import org.springframework.stereotype.Service;
- import java.math.BigDecimal;
- import java.math.RoundingMode;
- import java.time.LocalDateTime;
- import java.util.List;
- import java.util.Optional;
- import java.util.Set;
- /**
- * @author chan
- * @date 2024/8/8 18:34
- */
- @Service
- @RequiredArgsConstructor
- @Slf4j
- public class ClaudeServiceImpl implements ClaudeService {
- private final ClaudeUserMapper claudeUserMapper;
- private final GroupsRelationMapper groupsRelationMapper;
- private final GroupsMapper groupsMapper;
- private final GoodsDonSkuMapper skuMapper;
- private final UserBindRelationService userBindRelationService;
- private final UserMapper userMapper;
- private final SysConfigService sysConfigService;
- private final RedisService redisService;
- private final ClaudeUserCarUsedRecordMapper claudeUserCarUsedRecordMapper;
- private final ClaudeUserConversationRecordMapper claudeUserConversationRecordMapper;
- @Override
- public ClaudeUserView getUserInfo(long userId, Long relationId) {
- ClaudeUser claudeUser = checkCarAccount(userId, relationId);
- if (claudeUser != null) {
- GroupsRelation groupsRelation = getGroupsRelation(userId, relationId);
- GroupsTrips groupsTrips = getGroupsTrips(groupsRelation);
- GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId());
- ClaudeUserView view = ClaudeUserView.builder()
- .skuName(goodsDonSku.getSubTitle())
- .expireTime(claudeUser.getExpireTime())
- .limitNum(claudeUser.getLimitNum())
- .limitTime(claudeUser.getLimitTime())
- .isRenewPackage(claudeUser.getRenewOrderId() != null ? true : false)
- .renewSkuId(claudeUser.getRenewSkuId())
- .renewOrderId(claudeUser.getRenewOrderId())
- // .use(Math.min(claudeUser.getLimitNum(), getConversationCount(claudeUser.getUserToken(), claudeUser.getLimitTime())))
- .build();
- if (view.getRenewSkuId() != null) {
- Optional.ofNullable(skuMapper.selectById(view.getRenewSkuId())).ifPresent(renewSku -> view.setRenewSpecVal(renewSku.getSpecVal()));
- }
- return view;
- } else {
- throw BusinessRuntimeException.getInstance("您还未购买该车票");
- }
- }
- @Override
- public ClaudeUser findByUserTokenAndExpireTimeAfter(String userToken, LocalDateTime now) {
- return claudeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeUser.class).eq(ClaudeUser::getUserToken, userToken).gt(ClaudeUser::getExpireTime, now));
- }
- @Override
- public ConversationLimitResponse conversationLimit(String userToken, ClaudeUser user) {
- return isConversationAllowed(userToken, user.getLimitNum(), user.getLimitTime());
- }
- @Override
- public void carLimited(String carId, String userToken, Long expTime) {
- redisService.set("claude:clears_in:" + carId, expTime, expTime);
- try {
- //记录用户触发限制
- ClaudeUserCarUsedRecord claudeUserCarUsedRecord = new ClaudeUserCarUsedRecord();
- claudeUserCarUsedRecord.setUserToken(userToken);
- claudeUserCarUsedRecord.setCarId(carId);
- String carChatKey = RedisService.key.CLAUDE_CAR_CHAT.getName() + carId;
- Long carChatNum = redisService.zCard(carChatKey);
- if (carChatNum != null) {
- claudeUserCarUsedRecord.setNum(Integer.parseInt(carChatNum.toString()));
- }
- claudeUserCarUsedRecordMapper.insert(claudeUserCarUsedRecord);
- log.info("记录userToken:{} 触发限制 carId:{}", userToken, carId);
- } catch (Exception e) {
- log.info("记录userToken:{} 触发限制 carId:{} 操作失败e:{}", userToken, carId, StringUtil.getErrorText(e));
- }
- }
- @Override
- public ClaudeUser getClaudeUser(long userId, Long relationId) {
- GroupsRelation groupsRelation = getGroupsRelation(userId, relationId);
- GroupsTrips groupsTrips = getGroupsTrips(groupsRelation);
- GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId());
- if (goodsDonSku != null && goodsDonSku.getGoodsId() == 75 && goodsDonSku.getIsMirror()) {
- return getClaudeUser(userId, relationId, groupsRelation, goodsDonSku);
- } else {
- throw BusinessRuntimeException.getInstance("服务器出了点问题");
- }
- }
- @Override
- public void saveConversationRecord(String userToken, ConversationRequest conversationRequest) {
- ClaudeUser claudeUser = claudeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeUser.class).eq(ClaudeUser::getUserToken, userToken));
- ClaudeUserConversationRecord claudeUserConversationRecord = new ClaudeUserConversationRecord();
- claudeUserConversationRecord.setUserToken(userToken);
- claudeUserConversationRecord.setCarId(conversationRequest.getCarId());
- if (StringUtils.isNotBlank(conversationRequest.getConversation_id())) {
- claudeUserConversationRecord.setConversationId(conversationRequest.getConversation_id());
- }
- claudeUserConversationRecord.setModel(conversationRequest.getModel());
- claudeUserConversationRecordMapper.insert(claudeUserConversationRecord);
- String key = RedisService.key.CLAUDE_CONVERSATION_LIMIT.getName() + ":" + userToken;
- long currentTimeMillis = System.currentTimeMillis();
- if (redisService.hasKey("claude:clears_in:" + conversationRequest.getCarId())) {
- redisService.del("claude:clears_in:" + conversationRequest.getCarId());
- }
- // 如果未超过限制,记录当前请求的时间戳
- redisService.zAdd(key, currentTimeMillis, currentTimeMillis);
- // 设置ZSet的过期时间,窗口大小加上一段冗余时间
- redisService.expire(key, (claudeUser.getLimitTime() * 60 * 60) + 20);
- if (StringUtils.isNotBlank(conversationRequest.getCarId())) {
- updateExperienceAndScore(conversationRequest.getCarId(), System.currentTimeMillis());
- }
- }
- @Override
- public ClaudeUser checkCarAccount(Long userId, Long relationId) {
- GroupsRelation groupsRelation = getGroupsRelation(userId, relationId);
- GroupsTrips groupsTrips = getGroupsTrips(groupsRelation);
- GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId());
- if (goodsDonSku != null && goodsDonSku.getGoodsId() == 75 && goodsDonSku.getIsMirror() && goodsDonSku.getIsCar()) {
- return getClaudeUser(userId, relationId, groupsRelation, goodsDonSku);
- } else {
- return null;
- }
- }
- @Override
- public String getCarLoginUrl(String domain, Long userId, Long relationId, String carId) {
- GroupsRelation groupsRelation = getGroupsRelation(userId, relationId);
- GroupsTrips groupsTrips = getGroupsTrips(groupsRelation);
- GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId());
- if (goodsDonSku != null && goodsDonSku.getIsMirror() && goodsDonSku.getIsCar()) {
- ClaudeUser claudeUser = getClaudeUser(userId, relationId, groupsRelation, goodsDonSku);
- String DOMAIN = domain;
- if (StrUtil.isEmpty(DOMAIN)) {
- DOMAIN = "https://claudeplus.com.cn";
- try {
- SysConfig sysConfig = sysConfigService.getOne(Wrappers.lambdaQuery(SysConfig.class).eq(SysConfig::getSysKey, "mirror_claude_domain"));
- if (sysConfig != null) {
- String sysValue = sysConfig.getSysValue();
- if (JSONUtil.isJsonArray(sysValue)) {
- List<String> domainList = JSONUtil.parseArray(sysValue).toList(String.class);
- DOMAIN = domainList.get(RandomUtil.randomInt(domainList.size()));
- }
- }
- } catch (Exception e) {
- log.error("随机抽取域名错误. msg:{}", StringUtil.getErrorText(e));
- }
- }
- log.info("domain:{},用户:{},所在车次:{},座位id:{},在{}获取跳转Claude镜像的登录url", DOMAIN, userId, groupsTrips.getId(), relationId, DateTime.now());
- return DOMAIN + "/auth/logintoken?carid=" + carId + "&usertoken=" + claudeUser.getUserToken();
- } else {
- throw BusinessRuntimeException.getInstance("服务器出了点问题");
- }
- }
- /**
- * 获取指定用户ID在滑动窗口内的提问次数
- *
- * @param userToken 用户token
- * @return 滑动窗口内的请求次数
- */
- @Override
- public Long getConversationCount(String userToken, Long limitTime) {
- JSONObject conversationCount = getConversationCount(userToken);
- Long count = Optional.ofNullable(conversationCount.getLong("count")).orElse(0l);
- return new BigDecimal(count).divide(BigDecimal.valueOf(4000),0, RoundingMode.HALF_UP).longValue();
- }
- @Override
- public Long carLimit(String carId) {
- if(redisService.hasKey("claude:clears_in:" + carId)){
- return redisService.getExpire("claude:clears_in:" + carId);
- }
- return 0L;
- }
- @Override
- public void clearNum(String userToken) throws Exception {
- HttpRequest request = new HttpRequest("http://147.79.20.183:9611/claude/cleanUserTokenLimit?userToken=" + userToken);
- HttpResponse execute = request.method(Method.GET).setConnectionTimeout(3000).timeout(3000).execute();
- String body = execute.body();
- JSONObject re = Jsons.parseObject(body, JSONObject.class);
- if (!body.contains("清除成功")) {
- throw BusinessRuntimeException.getInstance(re.getStr("message"));
- }
- }
- @Override
- public void clearNumByRelationId(Long relationId) throws Exception {
- ClaudeUser claudeUser = claudeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeUser.class).eq(ClaudeUser::getRelationId, relationId));
- HttpRequest request = new HttpRequest("http://147.79.20.183:9611/claude/cleanUserTokenLimit?userToken=" + claudeUser.getUserToken());
- HttpResponse execute = request.method(Method.GET).setConnectionTimeout(3000).timeout(3000).execute();
- String body = execute.body();
- JSONObject re = Jsons.parseObject(body, JSONObject.class);
- if (!body.contains("清除成功")) {
- throw BusinessRuntimeException.getInstance(re.getStr("message"));
- }
- }
- /**
- * 获取车位信息
- *
- * @param userId
- * @param relationId
- * @return
- */
- private GroupsRelation getGroupsRelation(Long userId, Long relationId) {
- List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
- GroupsRelation groupsRelation = groupsRelationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class).in(GroupsRelation::getUserId, userIdList).eq(GroupsRelation::getId, relationId));
- if (groupsRelation == null) {
- throw BusinessRuntimeException.getInstance("车票不存在");
- }
- return groupsRelation;
- }
- /**
- * 获取车队信息
- *
- * @param groupsRelation
- * @return
- */
- private GroupsTrips getGroupsTrips(GroupsRelation groupsRelation) {
- GroupsTrips groupsTrips = groupsMapper.selectById(groupsRelation.getGroupsId());
- if (groupsTrips == null) {
- throw BusinessRuntimeException.getInstance("车队异常");
- }
- return groupsTrips;
- }
- private ClaudeUser getClaudeUser(Long userId, Long relationId, GroupsRelation groupsRelation, GoodsDonSku goodsDonSku) {
- ClaudeUser claudeUser = claudeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeUser.class).eq(ClaudeUser::getRelationId, relationId));
- User user = userMapper.selectById(userId);
- if (claudeUser == null) {
- claudeUser = createClaudeUser(groupsRelation, user, goodsDonSku);
- } else {
- updateClaudeUser(groupsRelation, user, claudeUser);
- }
- return claudeUser;
- }
- /**
- * 创建chatgptUser
- */
- private synchronized ClaudeUser createClaudeUser(GroupsRelation groupsRelation, User user, GoodsDonSku goodsDonSku) {
- ClaudeUser claudeUser = new ClaudeUser();
- if (goodsDonSku.getIsCar()) {
- claudeUser.setLimitNum(goodsDonSku.getGptLimitNum());
- claudeUser.setLimitTime(goodsDonSku.getGptLimitTime());
- }
- try {
- claudeUser.setExpireTime(groupsRelation.getExpiryTime());
- claudeUser.setIsPlus(true);
- claudeUser.setName(user.getNickname());
- claudeUser.setRelationId(groupsRelation.getId());
- claudeUser.setUserToken(UUID.randomUUID().toString());
- claudeUserMapper.insert(claudeUser);
- } catch (DuplicateKeyException e) {
- log.error("重复插入Claude镜像用户:{}token,errmsg:{}", user.getNickname(), StringUtil.getErrorText(e));
- }
- return claudeUser;
- }
- private void updateClaudeUser(GroupsRelation groupsRelation, User user, ClaudeUser claudeUser) {
- claudeUser.setName(user.getNickname());
- claudeUser.setExpireTime(groupsRelation.getExpiryTime());
- claudeUserMapper.updateById(claudeUser);
- }
- /**
- * 检查是否允许进行进行提问
- *
- * @param userToken 用户token
- * @return true 如果允许请求,false 如果请求被限制
- */
- public ConversationLimitResponse isConversationAllowed(String userToken, int limit, Long limitTime) {
- String key = RedisService.key.CLAUDE_CONVERSATION_LIMIT.getName() + ":" + userToken;
- long currentTimeMillis = System.currentTimeMillis();
- long windowStartMillis = currentTimeMillis - (limitTime * 60 * 60) * 1000;
- ConversationLimitResponse conversationLimitResponse = new ConversationLimitResponse();
- // 清除时间窗口之前的请求记录
- redisService.zRemoveRangeByScore(key, 0, windowStartMillis);
- Long currentSize = redisService.zCard(key);
- if (currentSize != null && currentSize >= limit) {
- // 如果当前请求次数超过限制,则拒绝请求
- Set<Object> times = redisService.zRangeByScore(key, 0, currentTimeMillis, 0, 1);
- Long oldestTime = (Long) times.stream().findFirst().orElse(null);
- if (oldestTime != null) {
- // 下一次可用时间是最早请求时间之后的3小时
- long nextAvailableTime = oldestTime + (limitTime * 60 * 60 * 1000);
- conversationLimitResponse.setNextAvailableTime(nextAvailableTime);
- }
- conversationLimitResponse.setLimited(true);
- //用户次数使用限制
- ClaudeUserCarUsedRecord claudeUserCarUsedRecord = new ClaudeUserCarUsedRecord();
- claudeUserCarUsedRecord.setUserToken(userToken);
- claudeUserCarUsedRecord.setIsUserLimit(true);
- claudeUserCarUsedRecordMapper.insert(claudeUserCarUsedRecord);
- return conversationLimitResponse;
- } else {
- conversationLimitResponse.setLimited(false);
- return conversationLimitResponse;
- }
- }
- // 更新体验并计算评分
- public void updateExperienceAndScore(String carId, long timestamp) {
- // 定义键名
- String key = RedisService.key.CLAUDE_CAR_CHAT.getName() + carId;
- // 更新体验数据
- redisService.zAdd(key, timestamp, String.valueOf(timestamp));
- // 清理旧数据(可选)和重新计算评分(根据需要实现)
- cleanupOldExperiencesAndRecalculateScore(carId, timestamp);
- }
- // 清理旧数据和重新计算评分
- public void cleanupOldExperiencesAndRecalculateScore(String carId, long currentTimestamp) {
- long threeHoursAgo = currentTimestamp - (6 * 60 * 60 * 1000); // 3小时前的时间戳
- // 清理高级体验旧数据
- redisService.zRemoveRangeByScore(RedisService.key.CLAUDE_CAR_CHAT.getName() + carId, 0, threeHoursAgo);
- // 重新计算评分
- recalculateScore(carId, currentTimestamp);
- }
- // 重新计算指定车队的评分
- private void recalculateScore(String carId, long currentTimestamp) {
- // 实际应用中,你需要根据高级体验和低级体验的数量重新计算得分
- double highExperienceScore = (double) redisService.zCount(RedisService.key.CLAUDE_CAR_CHAT.getName() + carId, currentTimestamp - (6 * 60 * 60 * 1000), currentTimestamp) * 2;
- redisService.zAdd(RedisService.key.CLAUDE_CAR_SCORES.getName(), highExperienceScore, carId);
- }
- public JSONObject getConversationCount(String userToken) {
- try {
- String res = HttpUtil.get("http://147.79.20.183:9611/claude/getConversationCount?userToken=" + userToken);
- return new JSONObject(res);
- }catch (Exception e){
- return new JSONObject("{\"count\": 0}");
- }
- }
- }
|