package com.cyksj.service.gemini.impl; import cn.hutool.core.date.DateTime; 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.StringUtil; import com.cyksj.mapper.*; import com.cyksj.model.entity.*; import com.cyksj.model.response.ConversationLimitResponse; import com.cyksj.model.response.GeminiCarAccount; import com.cyksj.model.response.GeminiCarPageResponse; import com.cyksj.model.response.GeminiCarStatusResponse; import com.cyksj.model.response.GeminiConversationCountResponse; import com.cyksj.model.views.GeminiCarInfoView; import com.cyksj.model.views.GeminiUserView; import com.cyksj.redis.RedisService; import com.cyksj.service.gemini.GeminiService; import com.cyksj.service.sys.SysConfigService; import com.cyksj.service.user.UserBindRelationService; import com.ejlchina.searcher.SearchResult; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.Comparator; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.UUID; /** * Gemini镜像服务实现 * * @author chan * @date 2025/1/4 */ @Service @RequiredArgsConstructor @Slf4j public class GeminiServiceImpl implements GeminiService { private final GeminiUserMapper geminiUserMapper; 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 String getDefaultDomain(String domain) { if (StrUtil.isNotEmpty(domain)) { return domain; } String defaultDomain = "https://gemini.bestaistore.com"; try { SysConfig sysConfig = sysConfigService.getOne( Wrappers.lambdaQuery(SysConfig.class).eq(SysConfig::getSysKey, "mirror_gemini_domain") ); if (sysConfig != null) { String sysValue = sysConfig.getSysValue(); if (JSONUtil.isJsonArray(sysValue)) { List domainList = JSONUtil.parseArray(sysValue).toList(String.class); defaultDomain = domainList.get(RandomUtil.randomInt(domainList.size())); } } } catch (Exception e) { log.error("获取Gemini域名错误. msg:{}", StringUtil.getErrorText(e)); } return defaultDomain; } @Override public GeminiUserView getUserInfo(long userId, Long relationId) { GeminiUser geminiUser = checkCarAccount(userId, relationId); if (geminiUser != null) { GroupsRelation groupsRelation = getGroupsRelation(userId, relationId); GroupsTrips groupsTrips = getGroupsTrips(groupsRelation); GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId()); GeminiUserView view = GeminiUserView.builder() .skuName(goodsDonSku.getSubTitle()) .expireTime(geminiUser.getExpireTime()) .userToken(geminiUser.getUserToken()) .limitNum(geminiUser.getLimitNum()) .limitTime(geminiUser.getLimitTime()) .build(); return view; } else { throw BusinessRuntimeException.getInstance("您还未购买该车票"); } } @Override public GeminiUser findByUserTokenAndExpireTimeAfter(String userToken, LocalDateTime now) { return geminiUserMapper.selectOne( Wrappers.lambdaQuery(GeminiUser.class) .eq(GeminiUser::getUserToken, userToken) .gt(GeminiUser::getExpireTime, now) ); } @Override public GeminiUser checkCarAccount(Long userId, Long relationId) { GroupsRelation groupsRelation = getGroupsRelation(userId, relationId); GroupsTrips groupsTrips = getGroupsTrips(groupsRelation); GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId()); if (goodsDonSku != null && goodsDonSku.getIsMirror() && goodsDonSku.getIsCar()) { return getGeminiUser(userId, relationId, groupsRelation, goodsDonSku); } else { return null; } } @Override public GeminiUser getGeminiUser(long userId, Long relationId) { GroupsRelation groupsRelation = getGroupsRelation(userId, relationId); GroupsTrips groupsTrips = getGroupsTrips(groupsRelation); GoodsDonSku goodsDonSku = skuMapper.selectById(groupsTrips.getSkuId()); if (goodsDonSku != null && goodsDonSku.getIsMirror()) { return getGeminiUser(userId, relationId, groupsRelation, goodsDonSku); } else { throw BusinessRuntimeException.getInstance("服务器出了点问题"); } } @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()) { GeminiUser geminiUser = getGeminiUser(userId, relationId, groupsRelation, goodsDonSku); String DOMAIN = getDefaultDomain(domain); log.info("domain:{},用户:{},所在车次:{},座位id:{},在{}获取跳转Gemini镜像的登录url", DOMAIN, userId, groupsTrips.getId(), relationId, DateTime.now()); return DOMAIN + "/logintoken?access_token=" + geminiUser.getUserToken() + "&account=" + carId; } else { throw BusinessRuntimeException.getInstance("服务器出了点问题"); } } /** * Gemini镜像端API域名 */ private static final String GEMINI_MIRROR_API = "https://saas.galaxyva.com"; @Override public void clearNum(String userToken) throws Exception { String url = GEMINI_MIRROR_API + "/gemini/cleanUserTokenLimit?userToken=" + userToken; HttpRequest request = new HttpRequest(url); HttpResponse execute = request.method(Method.GET).setConnectionTimeout(3000).timeout(3000).execute(); String body = execute.body(); JSONObject re = JSONUtil.parseObj(body); if (!body.contains("清除成功")) { throw BusinessRuntimeException.getInstance(re.getStr("message")); } } @Override public void clearNumByRelationId(Long relationId) throws Exception { String url = GEMINI_MIRROR_API + "/gemini/cleanUserTokenLimit?relationId=" + relationId; HttpRequest request = new HttpRequest(url); HttpResponse execute = request.method(Method.GET).setConnectionTimeout(3000).timeout(3000).execute(); String body = execute.body(); JSONObject re = JSONUtil.parseObj(body); if (!body.contains("清除成功")) { throw BusinessRuntimeException.getInstance(re.getStr("message")); } } @Override public GeminiCarPageResponse getCarPage(String domain, int page, int size) { String DOMAIN = getDefaultDomain(domain); try { JSONObject requestBody = new JSONObject(); requestBody.put("page", page); requestBody.put("size", size); String res = HttpUtil.post(DOMAIN + "/carpage", requestBody.toString()); if (!JSONUtil.isJson(res)) { res = res.substring(2); } return JSONUtil.toBean(res, GeminiCarPageResponse.class); } catch (Exception e) { log.error("调用Gemini /carpage 接口失败: {}", StringUtil.getErrorText(e)); GeminiCarPageResponse response = new GeminiCarPageResponse(); response.setCode(-1); response.setMessages("获取车队列表失败"); return response; } } @Override public GeminiCarStatusResponse getCarStatus(String domain, String carId) { String DOMAIN = getDefaultDomain(domain); try { String res = HttpUtil.get(DOMAIN + "/status?carid=" + carId); return JSONUtil.toBean(res, GeminiCarStatusResponse.class); } catch (Exception e) { log.error("调用Gemini /status 接口失败: {}", StringUtil.getErrorText(e)); GeminiCarStatusResponse response = new GeminiCarStatusResponse(); response.setAccountReady(false); return response; } } /** * 获取车位信息 */ private GroupsRelation getGroupsRelation(Long userId, Long relationId) { List 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; } /** * 获取车队信息 */ private GroupsTrips getGroupsTrips(GroupsRelation groupsRelation) { GroupsTrips groupsTrips = groupsMapper.selectById(groupsRelation.getGroupsId()); if (groupsTrips == null) { throw BusinessRuntimeException.getInstance("车队异常"); } return groupsTrips; } private GeminiUser getGeminiUser(Long userId, Long relationId, GroupsRelation groupsRelation, GoodsDonSku goodsDonSku) { GeminiUser geminiUser = geminiUserMapper.selectOne( Wrappers.lambdaQuery(GeminiUser.class).eq(GeminiUser::getRelationId, relationId) ); User user = userMapper.selectById(userId); if (geminiUser == null) { geminiUser = createGeminiUser(groupsRelation, user, goodsDonSku); } else { updateGeminiUser(groupsRelation, user, geminiUser); } return geminiUser; } /** * 创建GeminiUser */ private synchronized GeminiUser createGeminiUser(GroupsRelation groupsRelation, User user, GoodsDonSku goodsDonSku) { GeminiUser geminiUser = new GeminiUser(); if (goodsDonSku.getIsCar()) { geminiUser.setLimitNum(goodsDonSku.getGptLimitNum()); geminiUser.setLimitTime(goodsDonSku.getGptLimitTime()); } try { geminiUser.setExpireTime(groupsRelation.getExpiryTime()); geminiUser.setIsPlus(true); geminiUser.setName(user.getNickname()); geminiUser.setRelationId(groupsRelation.getId()); geminiUser.setUserToken(UUID.randomUUID().toString()); geminiUserMapper.insert(geminiUser); } catch (DuplicateKeyException e) { log.error("重复插入Gemini镜像用户:{}token,errmsg:{}", user.getNickname(), StringUtil.getErrorText(e)); } return geminiUser; } private void updateGeminiUser(GroupsRelation groupsRelation, User user, GeminiUser geminiUser) { geminiUser.setName(user.getNickname()); geminiUser.setExpireTime(groupsRelation.getExpiryTime()); geminiUserMapper.updateById(geminiUser); } @Override @SuppressWarnings("unchecked") public SearchResult getCarInfoList(String domain, int page, int size) { RedisService.key cacheEnum = RedisService.key.GEMINI_CAR_LIST_CACHE; RedisService.key lockEnum = RedisService.key.GEMINI_CAR_LIST_LOCK; String cacheKey = cacheEnum.getNameFormat(domain); String lockKey = lockEnum.getNameFormat(domain); // 1. 先查缓存 Object cached = redisService.get(cacheKey); if (cached != null) { List cachedList = (List) cached; SearchResult result = new SearchResult<>(cachedList); result.setTotalCount(cachedList.size()); return result; } // 2. 缓存不存在,尝试获取锁,单线程去拉取 boolean locked = redisService.setNx(lockKey, "1", lockEnum.getTimeout()); if (!locked) { // 没拿到锁,说明其他线程正在拉取,返回空列表 SearchResult result = new SearchResult<>(new ArrayList<>()); result.setTotalCount(0); return result; } try { // 双重检查,拿到锁后再查一次缓存 cached = redisService.get(cacheKey); if (cached != null) { List cachedList = (List) cached; SearchResult result = new SearchResult<>(cachedList); result.setTotalCount(cachedList.size()); return result; } // 3. 真正去请求数据 List carInfoList = fetchCarInfoListFromRemote(domain, page, size); // 4. 存入缓存 if (carInfoList.isEmpty()) { // 空值也缓存,防穿透,缓存5秒 redisService.set(cacheKey, carInfoList, RedisService.key.GEMINI_CAR_LIST_EMPTY_CACHE.getTimeout()); } else { redisService.set(cacheKey, carInfoList, cacheEnum.getTimeout()); } SearchResult result = new SearchResult<>(carInfoList); result.setTotalCount(carInfoList.size()); return result; } finally { redisService.del(lockKey); } } /** * 从远程接口拉取车队列表(carpage + status) */ private List fetchCarInfoListFromRemote(String domain, int page, int size) { GeminiCarPageResponse carPageResponse = getCarPage(domain, page, size); List carInfoList = new ArrayList<>(); if (carPageResponse.getCode() != null && carPageResponse.getCode() == 1000 && carPageResponse.getData() != null && carPageResponse.getData().getList() != null) { for (GeminiCarAccount account : carPageResponse.getData().getList()) { // 停运的账号不返回 if (account.getStatus() == null || !account.getStatus()) { continue; } // 调用status接口获取详细信息(分数等) GeminiCarInfoView carInfo = getCarInfo(domain, account.getCarID()); // status接口返回停运的也过滤掉 if ("停运".equals(carInfo.getStatus())) { continue; } carInfo.setCarName(account.getNickName()); // 根据carType设置isPlus: Ultra=3, Pro=2, Free=1 if ("Ultra".equalsIgnoreCase(account.getCarType())) { carInfo.setIsPlus(3); } else if ("Pro".equalsIgnoreCase(account.getCarType())) { carInfo.setIsPlus(2); } else { carInfo.setIsPlus(1); } carInfoList.add(carInfo); } } // 按score从低到高排序 carInfoList.sort(Comparator.comparingDouble(c -> c.getScore() != null ? c.getScore() : 0.0)); return carInfoList; } @Override public GeminiCarInfoView getCarInfo(String domain, String carId) { GeminiCarStatusResponse statusResponse = getCarStatus(domain, carId); GeminiCarInfoView carInfo = new GeminiCarInfoView(); carInfo.setCarId(carId); carInfo.setCarName(carId); if (statusResponse.getAccountReady() != null && statusResponse.getAccountReady()) { carInfo.setAccountReady(true); carInfo.setClearsIn(statusResponse.getClearsIn() != null ? statusResponse.getClearsIn().longValue() : 0L); carInfo.setUse(statusResponse.getCount()); // 根据isPlus设置账号类型 if (statusResponse.getIsPlus() != null && statusResponse.getIsPlus()) { carInfo.setIsPlus(2); // pro } else { carInfo.setIsPlus(1); // free } if (carInfo.getClearsIn() > 0) { carInfo.setStatus("停运"); carInfo.setExpTime(new DateTime(System.currentTimeMillis() + carInfo.getClearsIn() * 1000)); } else { carInfo.setScore(statusResponse.getCount() != null ? statusResponse.getCount().doubleValue() : 0.0); carInfo.setStatus(carInfo.getScore() > 50 ? "繁忙" : "空闲"); } } else { carInfo.setAccountReady(false); carInfo.setStatus("停运"); } return carInfo; } @Override public GeminiConversationCountResponse getConversationCountDetail(String userToken) { try { String url = GEMINI_MIRROR_API + "/gemini/getConversationCount?userToken=" + userToken; String res = HttpUtil.get(url, 5000); JSONObject json = JSONUtil.parseObj(res); // 检查是否有错误 if (json.containsKey("message")) { throw BusinessRuntimeException.getInstance(json.getStr("message")); } GeminiConversationCountResponse response = new GeminiConversationCountResponse(); response.setCount(json.getLong("count")); response.setLimit(json.getInt("limit")); response.setRemaining(json.getLong("remaining")); response.setTime(json.getLong("time")); response.setIsVip(json.getBool("isVip")); return response; } catch (BusinessRuntimeException e) { throw e; } catch (Exception e) { log.error("调用Gemini getConversationCount接口失败: {}", StringUtil.getErrorText(e)); throw BusinessRuntimeException.getInstance("获取对话次数失败"); } } }