package com.cyksj.web.controller.claude; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.cyksj.common.constant.Constant; import com.cyksj.common.exception.BusinessRuntimeException; import com.cyksj.common.util.Jsons; import com.cyksj.dto.Result; import com.cyksj.dto.UserSharedDto; import com.cyksj.enums.GatewayResponse; import com.cyksj.mapper.ClaudeCodeUserMapper; import com.cyksj.mapper.GroupsMapper; import com.cyksj.mapper.GroupsRelationMapper; import com.cyksj.mapper.manage.coupon.*; import com.cyksj.mapper.manage.distribute.UserDistributeMapper; import com.cyksj.model.entity.*; import com.cyksj.model.manage.views.OrderDonView; import com.cyksj.model.request.*; import com.cyksj.model.response.*; import com.cyksj.model.response.claudecode.ClaudeCodeResp; import com.cyksj.model.views.*; import com.cyksj.service.claude.AnnouncementService; import com.cyksj.service.claude.ClaudeCodeService; import com.cyksj.service.claude.OAuthClaudeCodeService; import com.cyksj.service.claude.RelayService; import com.cyksj.service.user.UserBindRelationService; import com.cyksj.service.user.UserService; import com.cyksj.web.util.StpUserUtil; import com.ejlchina.searcher.BeanSearcher; import com.ejlchina.searcher.SearchResult; import com.ejlchina.searcher.param.Operator; import com.ejlchina.searcher.util.MapBuilder; import com.ejlchina.searcher.util.MapUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * server/claude code * 项目名: yhlxj11111111 * 文件名: ClaudeCodeController * 创建者: JavaZou * 创建时间:2025/8/8 11:37 */ @Slf4j @RestController @RequestMapping("/applet/claude/code") @RequiredArgsConstructor public class ClaudeCodeController { private final OAuthClaudeCodeService oAuthClaudeCodeService; private final ClaudeCodeService claudeCodeService; private final BeanSearcher beanSearcher; private final HttpServletRequest request; private final UserBindRelationService userBindRelationService; private final AnnouncementService announcementService; private final RelayService relayService; private final ClaudeCodeUserMapper claudeCodeUserMapper; private final GroupsRelationMapper relationMapper; private final GroupsMapper groupsMapper; private final UserService userService; private final UserDistributeMapper userDistributeMapper; private final CouponDistributePopularizeMapper couponDistributePopularizeMapper; private final CouponMapper couponMapper; private final CouponSkuMapper couponSkuMapper; private final CouponRecommendMapper couponRecommendMapper; private final CouponUserMapper couponUserMapper; /** * 用户创建API KEY */ @PostMapping("/create/apiKeys") public ClaudeCodeResp createApiKeys(@RequestBody @Validated ClaudeCodeApiKeysReq req) throws Exception { long userId = StpUserUtil.getLoginIdAsLong(); req.setUserId(userId); ClaudeCodeResp resp = claudeCodeService.createApiKeys(req); return resp; } /** * 用户api keys列表 */ @GetMapping("/get/apiKeys") public Result> getUserApiKeys() throws Exception { long userId = StpUserUtil.getLoginIdAsLong(); List userApiKeys = claudeCodeService.getUserApiKeys(userId); return GatewayResponse.SUCCESS.newBuilder().toResult(userApiKeys); } /** * 删除api key */ @DeleteMapping("/delete/apiKeys") public Result deleteApiKeysById(@RequestBody ClaudeCodeDelReq delReq) throws Exception { long userId = StpUserUtil.getLoginIdAsLong(); delReq.setUserId(userId); claudeCodeService.deleteApiKeysById(delReq); return GatewayResponse.SUCCESS.newBuilder().toResult(); } //================================================用户============================================================ /** * 用户是否存在订阅 */ @GetMapping("/get/user/subs") public Result getUserSubs() { long userId = StpUserUtil.getLoginIdAsLong(); Boolean flag = claudeCodeService.getUserSubs(userId); return GatewayResponse.SUCCESS.newBuilder().toResult(flag); } /** * 用户订阅车票信息 */ @GetMapping("/get/user/subs/info") public Result getUserSubsInfo() { long userId = StpUserUtil.getLoginIdAsLong(); ClaudeCodeUserInfoView claudeCodeUserInfoView = claudeCodeService.getClaudeCodeUserSubsInfo(userId); return GatewayResponse.SUCCESS.newBuilder().toResult(claudeCodeUserInfoView); } /** * 用户订阅历史 */ @GetMapping("/get/user/orders") public Result> getUserOrders() { long userId = StpUserUtil.getLoginIdAsLong(); List userIdList = userBindRelationService.getRelationUserIdList(userId, null); SearchResult search = beanSearcher.search(OrderDonView.class, MapUtils.flatBuilder(request.getParameterMap()) .field(OrderDonView::getUserId, userIdList).op(Operator.InList) .field(OrderDonView::getStatus, Constant.noOrderAllStatus).op(Operator.NotIn) .build()); return GatewayResponse.SUCCESS.newBuilder().toResult(search); } /** * claude 仪表盘聚合 * 返回 当前订阅/余额卡片 + 今日汇总 + 今日请求列表 */ @GetMapping("/get/dashboard") public ClaudeCodeResp getUserDashboard() throws Exception { long userId = StpUserUtil.getLoginIdAsLong(); ClaudeCodeResp resp = claudeCodeService.getUserDashboard(userId); return resp; } /** * 图表 + 明细联动(每小时聚合) * - `start` (string, required): `YYYY-MM-DD HH:MM:SS`,例如 `2025-08-08 12:00:00` * - `end` (string, required): `YYYY-MM-DD HH:MM:SS`,需大于 `start` * - `tz` (string, optional): 时区,默认 `Asia/Shanghai` * - `page` (int, optional): 明细页码,默认 1 * - `limit` (int, optional): 明细每页数量,默认 20,最大 100 * - `order` (string, optional): `asc|desc`,默认 `desc` * - `type` (string, optional): 明细类型过滤,`consume|purchase|recovery|refund|reward|adjustment|all`,默认 `all` */ @GetMapping("/get/credits/analytics") public ClaudeCodeResp getCreditsAnalytics(String start, String end, @RequestParam(defaultValue = "Asia/Shanghai") String tz, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit, @RequestParam(defaultValue = "desc") String order, @RequestParam(defaultValue = "all") String type) throws Exception { long userId = StpUserUtil.getLoginIdAsLong(); ClaudeCodeResp resp = claudeCodeService.getCreditsAnalytics(userId, start, end, tz, page, limit, order, type); return resp; } /** * claude code可选套餐 */ @GetMapping("/get/packages") public Result getClaudeCodePackages(String dsCode) { Long userId = StpUserUtil.getUserIdAfterLogin(); ClaudeCodePackageView claudeCodePackageView = new ClaudeCodePackageView(); if (userId != null) { List userIdList = userBindRelationService.getRelationUserIdList(userId, null); ClaudeCodeUser user = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class) .in(ClaudeCodeUser::getUserId, userIdList) .last("limit 1")); if (user != null) { Integer claudeDailyLimit = user.getClaudeDailyLimit(); GroupsRelation relation = relationMapper.selectById(user.getRelationId()); GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId()); Long skuId = groupsTrips.getSkuId(); List renewUpgradePackageFrontViews = beanSearcher.searchAll(RenewUpgradePackageFrontView.class, MapUtils.builder().field(RenewUpgradePackageFrontView::getSkuId, skuId).build()); renewUpgradePackageFrontViews.forEach(renew -> { try { List upgradeSkuIds = Jsons.parseList(renew.getUpgradeSkuIds(), Long.class); if (user.getRenewSkuId() != null) { upgradeSkuIds.remove(user.getRenewSkuId()); } List renewSkuViews = new ArrayList<>(); if (CollUtil.isNotEmpty(upgradeSkuIds)) { renewSkuViews = beanSearcher.searchAll(RenewUpgradePackageFrontView.RenewSkuView.class, MapUtils.builder() //高套餐 .field(RenewUpgradePackageFrontView.RenewSkuView::getClaudeDailyLimit, claudeDailyLimit).op(Operator.GreaterThan) .field(RenewUpgradePackageFrontView.RenewSkuView::getSkuId, upgradeSkuIds).op(Operator.InList) .build()); } renew.setRenewSkus(renewSkuViews); } catch (Exception e) { } }); claudeCodePackageView.setUpgrades(renewUpgradePackageFrontViews); } } List goodsDonSkuViews = beanSearcher.searchAll(GoodsDonSkuView.class, MapUtils.builder().field(GoodsDonSkuView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID).field(GoodsDonSkuView::getGoodsDonStatus, Boolean.TRUE).field(GoodsDonSkuView::getGoodsDonSkuStatus, Boolean.TRUE).field(GoodsDonSkuView::getUserOwns).op(Operator.IsNull).build()); //渠道推广 setClaudeCodeSkuCouponInfo(dsCode, userId, goodsDonSkuViews); claudeCodePackageView.setSkus(goodsDonSkuViews); return GatewayResponse.SUCCESS.newBuilder().toResult(claudeCodePackageView); } //================================================Claude Code OAuth接口============================================================ /** * OAuth认证验证方法 */ private Long authenticateOAuth(HttpServletRequest request) { String authHeader = request.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer ")) { throw BusinessRuntimeException.getInstance("OAuth token required"); } String tokenValue = authHeader.substring(7); // 移除"Bearer "前缀 // 验证token OAuthToken token = oAuthClaudeCodeService.validateToken(null, tokenValue); if (token == null) { throw BusinessRuntimeException.getInstance("Invalid or expired token"); } return token.getUserId(); } /** * OAuth授权端点 */ @GetMapping("/authorize") public Result authorize(@RequestParam(name = "client_id") String clientId, @RequestParam String state) throws Exception { // 验证参数 if (clientId == null || clientId.isEmpty() || state == null || state.isEmpty()) { throw BusinessRuntimeException.getInstance("缺少必填参数"); } if (!StpUserUtil.isLogin()) { throw BusinessRuntimeException.getInstance("请先登录"); } else { Long userId = StpUserUtil.getLoginIdAsLong(); String token = oAuthClaudeCodeService.generateAccessToken(userId, clientId); if (StrUtil.isNotBlank(token)) { log.info("OAuth授权成功: userId={}, clientId={}, token={}", userId, clientId, token.substring(0, 8) + "..."); } return GatewayResponse.SUCCESS.newBuilder().toResult(token); } } /** * Token验证端点 */ @PostMapping("/verify-token") public Map verifyToken(@RequestBody Map request) { try { String token = request.get("token"); String code = request.get("code"); String clientId = request.get("client_id"); String clientSecret = request.get("client_secret"); // 验证客户端凭据 OAuthClient client = oAuthClaudeCodeService.validateClient(clientId, clientSecret); if (client == null) { throw BusinessRuntimeException.getInstance("Invalid client credentials"); } // 验证token OAuthToken oauthToken = oAuthClaudeCodeService.validateToken(code, token); if (oauthToken == null) { throw BusinessRuntimeException.getInstance("Invalid or expired token"); } User user = userService.getById(oauthToken.getUserId()); // 返回用户信息 Map result = new HashMap<>(); result.put("authenticated", true); result.put("userId", oauthToken.getUserId()); result.put("nickName", user.getNickname()); result.put("headImg", user.getHeadimgurl()); result.put("token", oauthToken.getKeyValue()); result.put("email", getUserEmail(oauthToken.getUserId())); log.info("Token验证成功: userId={}, clientId={}", oauthToken.getUserId(), clientId); return result; } catch (Exception e) { log.error("Token验证失败", e); throw BusinessRuntimeException.getInstance("Token verification failed"); } } /** * 验证客户端ID是否有效 */ private boolean isValidClientId(String clientId) { // 这里可以添加更严格的客户端ID验证逻辑 return "vvxked850tk1ikvhpk07s47a2vnppa7t".equals(clientId); } /** * 获取用户邮箱 */ private String getUserEmail(Long userId) { // 这里需要根据实际的用户表结构来获取用户邮箱 return "user" + userId + "@example.com"; } /** * 获取公告列表 */ @GetMapping("/announcements") public Result> getAnnouncements(@RequestHeader(value = "Accept-Language", defaultValue = "zh-CN") String language) { try { // OAuth认证 authenticateOAuth(request); // 处理语言参数 if (language.startsWith("en")) { language = "en"; } else { language = "zh-CN"; } List announcements = announcementService.getActiveAnnouncements(language); return GatewayResponse.SUCCESS.newBuilder().toResult(announcements); } catch (Exception e) { throw BusinessRuntimeException.getInstance("Failed to fetch announcements"); } } /** * 获取中继服务器列表 */ @GetMapping("/relays") public Result> getRelays(@RequestParam(required = false) String domain) { try { // OAuth认证 authenticateOAuth(request); List relays = relayService.getAvailableRelays(domain); return GatewayResponse.SUCCESS.newBuilder().toResult(relays); } catch (Exception e) { throw BusinessRuntimeException.getInstance("Failed to fetch relay servers"); } } /** * 获取中继服务器信息 */ @GetMapping("/relays/info") public Result> getRelaysInfo(@RequestParam(required = false) String domain) { long userId = StpUserUtil.getLoginIdAsLong(); MapBuilder builder = MapUtils.builder(); if (StrUtil.isNotBlank(domain)) { builder.field(RelayServer::getHost, domain).op(Operator.Contain); } List relayServers = beanSearcher.searchAll(RelayServer.class, builder.field(RelayServer::getStatus, 1).onlySelect(RelayServer::getHost, RelayServer::getLatencyMs).build()); return GatewayResponse.SUCCESS.newBuilder().toResult(relayServers); } public void setClaudeCodeSkuCouponInfo(String dsCode, Long userId, List goodsDonSkuViews) { //渠道推广 Coupon coupon = null; if (dsCode != null) { UserSharedDto popularizeDto = userService.getUserShredDtoByDsCode(dsCode); if (popularizeDto != null) { UserDistribute userDistribute = userDistributeMapper.selectOne(Wrappers.lambdaQuery(UserDistribute.class).eq(UserDistribute::getUserId, popularizeDto.getSharedId()).eq(UserDistribute::getDeleted, Boolean.TRUE).last("limit 1")); //渠道推广 if (userDistribute != null) { Integer selectCount = couponDistributePopularizeMapper.selectCount(Wrappers.lambdaQuery(CouponDistributePopularize.class) .eq(CouponDistributePopularize::getDistributeId, userDistribute.getId()) .eq(CouponDistributePopularize::getCouponId, Constant.DISTRIBUTE_CLAUDE_CODE_COUPON_ID) .eq(CouponDistributePopularize::getDeleted, Boolean.TRUE)); //已配置使用渠道优惠码 未配置使用推荐优惠券 coupon = couponMapper.selectById(selectCount > 0 ? Constant.DISTRIBUTE_CLAUDE_CODE_COUPON_ID : Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID); } } } Boolean isTjB = false; //不存在优惠 查找推荐优惠券 if (coupon == null) { coupon = couponRecommendMapper.getRecommendCouponByGoodsId(Constant.CLAUDE_CODE_GOODS_ID); isTjB = true; } if (coupon == null) { return; } if (isTjB || coupon.getId().intValue() == Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID.intValue()) { int isTj = couponRecommendMapper.checkPresentRecommendByCouponId(coupon.getId()); if (isTj == 0) { return; } } List couponSkuIds = couponSkuMapper.selectList(Wrappers.lambdaQuery(CouponSku.class).eq(CouponSku::getCouponId, coupon.getId()).eq(CouponSku::getDeleted, Boolean.TRUE)).stream().map(CouponSku::getSkuId).collect(Collectors.toList()); //该优惠券是否已经使用 if (userId != null) { Integer selectCount = couponUserMapper.selectCount(Wrappers.lambdaQuery(CouponUser.class).eq(CouponUser::getCouponId, coupon.getId()).eq(CouponUser::getUserId, userId)); if (selectCount > 0) { Integer isUsed = couponUserMapper.selectCount(Wrappers.lambdaQuery(CouponUser.class) .eq(CouponUser::getCouponId, coupon.getId()) .eq(CouponUser::getStatus, CouponUser.Status.unused) .eq(CouponUser::getUserId, userId) .lt(CouponUser::getValidStartTime, DateTime.now()) .gt(CouponUser::getValidEndTime, DateTime.now())); if (isUsed == 0) { return; } } } for (GoodsDonSkuView sku : goodsDonSkuViews) { if (couponSkuIds.contains(sku.getSkuId())) { sku.setCouponId(coupon.getId()); if (coupon.getType() == Coupon.Type.discount) { sku.setDiscount(coupon.getDiscount()); sku.setDiscountPrice(sku.getMoney().multiply(coupon.getDiscount())); } else { sku.setReduceMoney(coupon.getReduceMoney()); } } } } //======================================================================时间倍率=============================================================================== /** * 获取系统中所有的时间倍率配置列表(包括已启用和已禁用的配置) */ @GetMapping("/timeRate/configs") public Result getAllConfigs() { TimeRateConfigsResp resp = claudeCodeService.getAllConfigs(); if (resp != null && CollUtil.isNotEmpty(resp.getConfigs())) { resp.setConfigs(resp.getConfigs().stream().filter(time -> time.getIsActive()).collect(Collectors.toList())); } return GatewayResponse.SUCCESS.newBuilder().toResult(resp); } /** * claude code模型倍率 */ @GetMapping("/get/multiplier") public Result> getMultiplier() { List claudeCodeMultipliers = beanSearcher.searchAll(ClaudeCodeMultiplier.class, MapUtils.builder().build()); return GatewayResponse.SUCCESS.newBuilder().toResult(claudeCodeMultipliers); } /** * 用户使用重置次数 */ @PostMapping("/use/reset-count") public Result> useResetCount() throws Exception { long userId = StpUserUtil.getLoginIdAsLong(); claudeCodeService.useResetCount(userId); return GatewayResponse.SUCCESS.newBuilder().toResult(); } /** * 用户使用每日重置 */ // @PostMapping("/use/day/reset-count") public Result> useDayResetCount() throws Exception { long userId = StpUserUtil.getLoginIdAsLong(); claudeCodeService.useDayResetCount(userId); return GatewayResponse.SUCCESS.newBuilder().toResult(); } /** * 用户使用积分卡 */ @PostMapping("/use/credit-card") public Result useCreditCard() throws Exception { long userId = StpUserUtil.getLoginIdAsLong(); ClaudeCodeCreditCardUseReq req = new ClaudeCodeCreditCardUseReq(); req.setUserId(userId); claudeCodeService.useCreditCard(req); return GatewayResponse.SUCCESS.newBuilder().toResult(); } /** * 获取用户重置次数明细 */ @GetMapping("/reset-records") public Result> getUserResetRecords( @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit) { long userId = StpUserUtil.getLoginIdAsLong(); SearchResult result = claudeCodeService.getUserResetRecords(userId, page, limit); return GatewayResponse.SUCCESS.newBuilder().toResult(result); } /** * 获取用户积分卡使用明细 */ @GetMapping("/credit-card-records") public Result> getUserCreditCardRecords( @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit) { long userId = StpUserUtil.getLoginIdAsLong(); SearchResult result = claudeCodeService.getUserCreditCardRecords(userId, page, limit); return GatewayResponse.SUCCESS.newBuilder().toResult(result); } /** * 获取用户奖励ino */ @GetMapping("/reward-details") public Result getUserRewardDetails(Boolean general) { long userId = StpUserUtil.getLoginIdAsLong(); ClaudeCodeRewardDetailsResp result = claudeCodeService.getUserRewardDetails(userId, general); return GatewayResponse.SUCCESS.newBuilder().toResult(result); } /** * CC普通推广邀请明细 */ @GetMapping("/get/cc/promotion/invitation/details") public Result> getCcPromotionInvitationDetails() { long userId = StpUserUtil.getLoginIdAsLong(); SearchResult search = beanSearcher.search(CcPromotionInvitationDetailView.class, MapUtils.flatBuilder(request.getParameterMap()).field(CcPromotionInvitationDetailView::getSharedId, userId).orderBy(CcPromotionInvitationDetailView::getUpdateTime).desc().build()); search.getDataList().forEach(data -> { DistributeWaitingPointsOrderDetailView detailView = beanSearcher.searchFirst(DistributeWaitingPointsOrderDetailView.class, MapUtils.builder().put("uid", data.getUserId()).field(DistributeWaitingPointsOrderDetailView::getStatus, DistributeWaitingSendPoints.Status.close.name()).op(Operator.NotEqual).build()); if (detailView == null) { detailView = beanSearcher.searchFirst(DistributeWaitingPointsOrderDetailView.class, MapUtils.builder().put("uid", data.getUserId()).field(DistributeWaitingPointsOrderDetailView::getStatus, DistributeWaitingSendPoints.Status.close.name()).build()); } if (detailView != null) { data.setFirstOrderId(detailView.getOrderId()); data.setOrderMoney(detailView.getOrderMoney()); data.setSpecVal(detailView.getSpecVal()); data.setStatus(detailView.getStatus()); data.setReMoney(detailView.getReMoney()); } }); return GatewayResponse.SUCCESS.newBuilder().toResult(search); } }