zoujiajian 1 年間 前
コミット
4d738e0804

+ 5 - 0
netflix-common/src/main/java/com/cyksj/common/constant/Constant.java

@@ -631,4 +631,9 @@ public interface Constant {
 	 * GPT代充平台id
 	 */
 	Long GPT_RECHARGE_GOODS_ID = 40L;
+
+	/**
+	 * claude code平台id
+	 */
+	Long CLAUDE_CODE_GOODS_ID = 102L;
 }

+ 1 - 0
netflix-dao/src/main/java/com/cyksj/mapper/GroupsRelationMapper.java

@@ -84,4 +84,5 @@ public interface GroupsRelationMapper extends BaseMapper<GroupsRelation> {
 
 	List<UserTicketView> selectUserTickets(@Param("userIds") List<Long> userIds, @Param("goodsId") Long goodsId, @Param("skuId") Long skuId);
 
+	Integer selectCountByGoodsId(@Param("userIdList") List<Long> userIdList, @Param("goodsId") Long goodsId, @Param("now") DateTime now);
 }

+ 12 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDonSku.java

@@ -267,4 +267,16 @@ public class GoodsDonSku extends BaseEntity {
      */
     @TableField(updateStrategy = FieldStrategy.IGNORED)
     private String remark;
+
+    /**
+     * claude code积分上限
+     */
+    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    private Integer codePoints;
+
+    /**
+     * claude code每小时恢复积分
+     */
+    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    private Integer codeCreditRecovery;
 }

+ 10 - 0
netflix-dao/src/main/java/com/cyksj/model/manage/request/ReqGoodsSkuInsert.java

@@ -188,6 +188,16 @@ public class ReqGoodsSkuInsert {
          * 备注
          */
         private String remark;
+
+        /**
+         * claude code积分上限
+         */
+        private Integer codePoints;
+
+        /**
+         * claude code每小时恢复积分
+         */
+        private Integer codeCreditRecovery;
     }
 
 }

+ 32 - 0
netflix-dao/src/main/java/com/cyksj/model/request/ClaudeCodeApiKeysReq.java

@@ -0,0 +1,32 @@
+package com.cyksj.model.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotEmpty;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: ClaudeCodeApiKeysReq
+ * 创建者: JavaZou
+ * 创建时间:2025/8/8 11:39
+ */
+@Getter
+@Setter
+public class ClaudeCodeApiKeysReq {
+	/**
+	 * key name
+	 */
+	@NotEmpty
+	private String name;
+
+	/**
+	 * 用户id 不传入
+	 */
+	private Long userId;
+
+	/**
+	 * 过期时长day 不提供永不过期
+	 */
+	private Integer expiresDays;
+}

+ 35 - 0
netflix-dao/src/main/java/com/cyksj/model/request/ClaudeCodeApiKeysStatusReq.java

@@ -0,0 +1,35 @@
+package com.cyksj.model.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: ClaudeCodeApiKeysStatusReq
+ * 创建者: JavaZou
+ * 创建时间:2025/8/8 11:46
+ */
+@Getter
+@Setter
+public class ClaudeCodeApiKeysStatusReq {
+
+	/**
+	 * key id
+	 */
+	@NotEmpty
+	private Integer id;
+
+	/**
+	 * 用户id 不传入
+	 */
+	private Long userId;
+
+	/**
+	 * api key状态
+	 */
+	@NotNull
+	private Boolean status;
+}

+ 18 - 0
netflix-dao/src/main/java/com/cyksj/model/response/claudecode/ClaudeCodeResp.java

@@ -0,0 +1,18 @@
+package com.cyksj.model.response.claudecode;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: ClaudeCodeResp
+ * 创建者: JavaZou
+ * 创建时间:2025/8/8 11:28
+ */
+@Getter
+@Setter
+public class ClaudeCodeResp {
+	private Boolean success;
+
+	private Object data;
+}

+ 67 - 0
netflix-dao/src/main/java/com/cyksj/model/views/ClaudeCodeUserApiKeysView.java

@@ -0,0 +1,67 @@
+package com.cyksj.model.views;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: ClaudeCodeUserApiKeysView
+ * 创建者: JavaZou
+ * 创建时间:2025/8/8 12:13
+ */
+@Getter
+@Setter
+public class ClaudeCodeUserApiKeysView {
+
+	/**
+	 * API Key ID
+	 */
+	private Long id;
+
+	/**
+	 * 用户ID
+	 */
+	private Long userId;
+
+	/**
+	 * API Key值(仅创建时返回)
+	 */
+	private String keyValue;
+
+	/**
+	 * 最后使用时间
+	 */
+	private Date lastUsedAt;
+
+	/**
+	 * 最后使用IP
+	 */
+	private String lastUsedIp;
+
+	/**
+	 * API Key名称
+	 */
+	private String name;
+
+	/**
+	 * 前缀
+	 */
+	private String prefix;
+
+	/**
+	 * 状态
+	 */
+	private Boolean status;
+
+	/**
+	 * 创建时间
+	 */
+	private Date createdAt;
+
+	/**
+	 * 过期时间
+	 */
+	private Date expiresAt;
+}

+ 11 - 0
netflix-dao/src/main/resources/mapper/GroupRelationMapper.xml

@@ -422,4 +422,15 @@
             and gt.sku_id = #{skuId}
         </if>
     </select>
+
+    <select id="selectCountByGoodsId" resultType="java.lang.Integer">
+        select count(1) from groups_relation gr inner join groups_trips gt on gt.id = gr.groups_id inner join
+        goods_don_sku sku on sku.id = gt.sku_id
+        where sku.goods_id = #{goodsId}
+          and gr.user_id in
+        <foreach collection="userIds" item="item" open="(" separator="," close=")">
+            #{item}
+        </foreach>
+        and gr.expiry_time > #{now}
+    </select>
 </mapper>

+ 42 - 0
netflix-service/src/main/java/com/cyksj/service/claude/ClaudeCodeService.java

@@ -0,0 +1,42 @@
+package com.cyksj.service.claude;
+
+import com.cyksj.model.entity.GoodsDonSku;
+import com.cyksj.model.request.ClaudeCodeApiKeysReq;
+import com.cyksj.model.request.ClaudeCodeApiKeysStatusReq;
+import com.cyksj.model.views.ClaudeCodeUserApiKeysView;
+
+import java.util.List;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: ClaudeCodeService
+ * 创建者: JavaZou
+ * 创建时间:2025/8/8 10:55
+ */
+public interface ClaudeCodeService {
+
+	/**
+	 * 生成claude code用户套餐信息
+	 */
+	void generateClaudeCodeUserInfo(Long relationId, Long userId, GoodsDonSku sku);
+
+	/**
+	 * 用户创建api keys
+	 */
+	void createApiKeys(ClaudeCodeApiKeysReq req);
+
+	/**
+	 * 用户更新api key状态
+	 */
+	void updateApiKeysStatus(ClaudeCodeApiKeysStatusReq req);
+
+	/**
+	 * 删除api key
+	 */
+	void deleteApiKeysById(Long userId, Long keyId);
+
+	/**
+	 * 用户api keys列表
+	 */
+	List<ClaudeCodeUserApiKeysView> getUserApiKeys(long userId);
+}

+ 130 - 0
netflix-service/src/main/java/com/cyksj/service/claude/impl/ClaudeCodeServiceImpl.java

@@ -0,0 +1,130 @@
+package com.cyksj.service.claude.impl;
+
+import cn.hutool.core.date.DateTime;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.constant.Constant;
+import com.cyksj.common.util.Jsons;
+import com.cyksj.mapper.GroupsRelationMapper;
+import com.cyksj.model.entity.GoodsDonSku;
+import com.cyksj.model.entity.GroupsRelation;
+import com.cyksj.model.request.ClaudeCodeApiKeysReq;
+import com.cyksj.model.request.ClaudeCodeApiKeysStatusReq;
+import com.cyksj.model.response.claudecode.ClaudeCodeResp;
+import com.cyksj.model.views.ClaudeCodeUserApiKeysView;
+import com.cyksj.service.claude.ClaudeCodeService;
+import com.cyksj.service.user.UserBindRelationService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: ClaudeCodeServiceImpl
+ * 创建者: JavaZou
+ * 创建时间:2025/8/8 10:55
+ */
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class ClaudeCodeServiceImpl implements ClaudeCodeService {
+	private final UserBindRelationService userBindRelationService;
+
+	private final GroupsRelationMapper relationMapper;
+
+	private final static String CLAUDE_CODE_API_PREFIX = "http://104.238.220.246:18080/";
+
+	public static final String CLAUDE_CODE_API_ADMIN_KEY = "claudecodeyhlxjclaude";
+
+	@Override
+	public void generateClaudeCodeUserInfo(Long relationId, Long userId, GoodsDonSku sku) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		GroupsRelation relation = relationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class).eq(GroupsRelation::getId, relationId).in(GroupsRelation::getUserId, userIdList));
+		Date expiryTime = relation.getExpiryTime();
+		Integer codePoints = sku.getCodePoints();
+		Integer codeCreditRecovery = sku.getCodeCreditRecovery();
+		//生成claude code用户套餐
+		try {
+			createOrUpdateClaudeCodeUserPackage(userId, sku.getSpecVal(), codePoints, codeCreditRecovery, expiryTime);
+		} catch (Exception e) {
+
+		}
+	}
+
+	@Override
+	public void createApiKeys(ClaudeCodeApiKeysReq req) {
+		Long userId = req.getUserId();
+		if (checkClaudeCodeUser(userId)) {
+			String apiKeyUrl = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", userId);
+			Map<String, Object> parmas = new HashMap<>();
+			parmas.put("name", req.getName());
+			Integer expiresDays = req.getExpiresDays();
+			if (expiresDays != null) {
+				parmas.put("expires_days", expiresDays);
+			}
+			String responseBody = executeClaudeCodePostApi(apiKeyUrl, parmas.toString());
+		}
+	}
+
+	@Override
+	public void updateApiKeysStatus(ClaudeCodeApiKeysStatusReq req) {
+
+	}
+
+	@Override
+	public void deleteApiKeysById(Long userId, Long keyId) {
+
+	}
+
+	@Override
+	public List<ClaudeCodeUserApiKeysView> getUserApiKeys(long userId) {
+		return null;
+	}
+
+
+	public void createOrUpdateClaudeCodeUserPackage(Long userId, String planName, Integer codePoints, Integer codeCreditRecovery, Date expiryTime) throws Exception {
+		Map<String, Object> parmas = new HashMap<>();
+		parmas.put("plan_name", planName.replaceAll(";", ""));
+		parmas.put("credit_limit", codePoints);
+		parmas.put("credit_recovery", codeCreditRecovery);
+		parmas.put("duration_days", expiryTime);
+		String planUrl = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
+		String responseBody = executeClaudeCodePostApi(planUrl, parmas.toString());
+		ClaudeCodeResp claudeCodeResp = Jsons.parseObject(responseBody, ClaudeCodeResp.class);
+		if (claudeCodeResp.getSuccess()) {
+			log.info("用户claude code用户:{}套餐:{}创建或更新成功", userId, planName);
+		}
+	}
+
+
+	/**
+	 * 校验是否是claude code用户
+	 * @param userId
+	 * @return
+	 */
+	public Boolean checkClaudeCodeUser(Long userId) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		Integer count = relationMapper.selectCountByGoodsId(userIdList, Constant.CLAUDE_CODE_GOODS_ID, DateTime.now());
+		return count > 0;
+	}
+
+
+	/**
+	 * claude code Post请求
+	 */
+	public String executeClaudeCodePostApi(String url, String body) {
+		HttpResponse execute = HttpUtil.createPost(url)
+				.header("X-Admin-Key", CLAUDE_CODE_API_ADMIN_KEY)
+				.setConnectionTimeout(3000)
+				.setReadTimeout(3000)
+				.body(body)
+				.execute();
+		return execute.body();
+	}
+}

+ 12 - 0
netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java

@@ -78,6 +78,7 @@ import com.cyksj.model.manage.views.UpgradeOrderDetailView;
 import com.cyksj.model.request.*;
 import com.cyksj.model.views.*;
 import com.cyksj.redis.RedisService;
+import com.cyksj.service.claude.ClaudeCodeService;
 import com.cyksj.service.coin.UserGalaxyCoinService;
 import com.cyksj.service.corp.WxCorpOps;
 import com.cyksj.service.coupon.CouponFontService;
@@ -380,6 +381,8 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 
 	private final OrderDonGalaxyCoinRenewRelateMapper orderDonGalaxyCoinRenewRelateMapper;
 
+	private final ClaudeCodeService claudeCodeService;
+
 	private final List<String> noChekGoodsTemp = List.of("Apple One", "Youtube", "Spotify");
 
 	@Transactional(rollbackFor = Throwable.class)
@@ -3130,6 +3133,15 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 		if (isNeedReturnGalaxyCoin.get()) {
 			returnGalaxyCoin(orderDon);
 		}
+
+		//购买claude code
+		if (orderDon.getGoodsId() == Constant.CLAUDE_CODE_GOODS_ID) {
+			//首次购买
+			if (orderDon.getOrderType() == 1) {
+				//生成所需的claude code用户信息
+				claudeCodeService.generateClaudeCodeUserInfo(orderDon.getRelationId(), orderDon.getUserId(), sku);
+			}
+		}
 	}
 
 	private void handleUserMirrorSHopOrderDon(OrderDon orderDon) throws Exception {

+ 102 - 0
netflix-web/src/main/java/com/cyksj/web/controller/claude/ClaudeCodeController.java

@@ -0,0 +1,102 @@
+package com.cyksj.web.controller.claude;
+
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.request.ClaudeCodeApiKeysReq;
+import com.cyksj.model.request.ClaudeCodeApiKeysStatusReq;
+import com.cyksj.service.claude.ClaudeCodeService;
+import com.cyksj.web.util.StpUserUtil;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: ClaudeCodeController
+ * 创建者: JavaZou
+ * 创建时间:2025/8/8 11:37
+ */
+@RestController
+@RequestMapping("/applet/claude/code")
+@RequiredArgsConstructor
+public class ClaudeCodeController {
+	private final ClaudeCodeService claudeCodeService;
+
+	/**
+	 * 用户创建API KEY
+	 */
+	@PostMapping("/create/apiKeys")
+	public Result<String> createApiKeys(@RequestBody @Validated ClaudeCodeApiKeysReq req) {
+		long userId = StpUserUtil.getLoginIdAsLong();
+		req.setUserId(userId);
+		claudeCodeService.createApiKeys(req);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 更新api key状态
+	 */
+	@PutMapping("/update/apiKeys/status")
+	public Result<String> updateApiKeysStatus(@RequestBody @Validated ClaudeCodeApiKeysStatusReq req) {
+		long userId = StpUserUtil.getLoginIdAsLong();
+		req.setUserId(userId);
+		claudeCodeService.updateApiKeysStatus(req);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 用户api keys列表
+	 */
+	@GetMapping("/get/apiKeys")
+	public Result<List<Object>> getUserApiKeys() {
+		long userId = StpUserUtil.getLoginIdAsLong();
+		claudeCodeService.getUserApiKeys(userId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 删除api key
+	 */
+	@DeleteMapping("/delete/apiKeys/{id}")
+	public Result<String> deleteApiKeysById(@PathVariable Long id) {
+		long userId = StpUserUtil.getLoginIdAsLong();
+		claudeCodeService.deleteApiKeysById(userId, id);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	//================================================用户积分============================================================
+
+
+	/**
+	 * 查询用户套餐信息
+	 */
+	@GetMapping("/get/user/package")
+	public Result<String> getUserPackage() {
+		long userId = StpUserUtil.getLoginIdAsLong();
+		claudeCodeService.getUserPackage(userId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 用户积分明细
+	 */
+	@GetMapping("/get/user/points/detail")
+	public Result<String> getUserPointsDetail() {
+		long userId = StpUserUtil.getLoginIdAsLong();
+		claudeCodeService.getUserPointsDetail(userId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 用户使用数据统计
+	 */
+	@GetMapping("/get/user/points/statistics/data")
+	public Result<String> getUserPointsStatisticsData() {
+		long userId = StpUserUtil.getLoginIdAsLong();
+		claudeCodeService.getUserPointsStatisticsData(userId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+}