Ver código fonte

Merge branch 'codex_func'

zoujiajian 11 meses atrás
pai
commit
d53ddf274e
29 arquivos alterados com 1196 adições e 44 exclusões
  1. 14 2
      netflix-common/src/main/java/com/cyksj/common/constant/Constant.java
  2. 15 0
      netflix-dao/src/main/java/com/cyksj/mapper/CodexUserMapper.java
  3. 56 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CodexUser.java
  4. 22 0
      netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDonSku.java
  5. 22 0
      netflix-dao/src/main/java/com/cyksj/model/manage/request/ReqGoodsSkuInsert.java
  6. 24 0
      netflix-dao/src/main/java/com/cyksj/model/request/UpdateDailyLimitRequest.java
  7. 27 0
      netflix-dao/src/main/java/com/cyksj/model/request/codex/CodexPlanReq.java
  8. 49 0
      netflix-dao/src/main/java/com/cyksj/model/request/codex/CodexUserPackageReq.java
  9. 13 0
      netflix-dao/src/main/java/com/cyksj/model/request/codex/CreateKeyRequest.java
  10. 40 0
      netflix-dao/src/main/java/com/cyksj/model/response/codex/CodexPlanResp.java
  11. 47 0
      netflix-dao/src/main/java/com/cyksj/model/response/codex/CodexUserPackageResp.java
  12. 5 0
      netflix-dao/src/main/java/com/cyksj/model/views/ClaudeCodeUserInfoView.java
  13. 88 0
      netflix-dao/src/main/java/com/cyksj/model/views/CodexUserInfoView.java
  14. 20 0
      netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuFrontView.java
  15. 23 0
      netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuRenewView.java
  16. 24 0
      netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuView.java
  17. 24 0
      netflix-dao/src/main/java/com/cyksj/model/views/RenewUpgradePackageFrontView.java
  18. 15 0
      netflix-dao/src/main/java/com/cyksj/model/views/TicketRenewUpgradeView.java
  19. 24 0
      netflix-dao/src/main/java/com/cyksj/model/views/YhShopGoodsSkuFrontView.java
  20. 1 1
      netflix-service/src/main/java/com/cyksj/service/claude/ClaudeCodeService.java
  21. 85 38
      netflix-service/src/main/java/com/cyksj/service/claude/impl/ClaudeCodeServiceImpl.java
  22. 52 0
      netflix-service/src/main/java/com/cyksj/service/codex/CodexService.java
  23. 408 0
      netflix-service/src/main/java/com/cyksj/service/codex/impl/CodexServiceImpl.java
  24. 3 0
      netflix-service/src/main/java/com/cyksj/service/mange/impl/CmsOrderDonServiceImpl.java
  25. 3 1
      netflix-service/src/main/java/com/cyksj/service/mange/impl/MirrorUserCleatServiceImpl.java
  26. 4 0
      netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationClearServiceImpl.java
  27. 7 1
      netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationFrontServiceImpl.java
  28. 1 1
      netflix-web/src/main/java/com/cyksj/web/controller/claude/ClaudeCodeController.java
  29. 80 0
      netflix-web/src/main/java/com/cyksj/web/controller/codex/CodexController.java

+ 14 - 2
netflix-common/src/main/java/com/cyksj/common/constant/Constant.java

@@ -82,7 +82,7 @@ public interface Constant {
 	 */
 	String SYS_DEFAULT_SHOP_KEY = "sys_default_shop_key";
 
-	String INITIAL_PASSWORD ="qwer1234";
+	String INITIAL_PASSWORD = "qwer1234";
 
 	List<String> serviceKeys = List.of("service_img_set", "service_name_set");
 
@@ -354,7 +354,7 @@ public interface Constant {
 	/**
 	 * 设备意向标签组
 	 */
-	String EP_INTENTION_GROUP_NAME= "设备群发标签";
+	String EP_INTENTION_GROUP_NAME = "设备群发标签";
 
 	/**
 	 * gpt日卡活动 体验车票基础规格
@@ -703,4 +703,16 @@ public interface Constant {
 	 * 推广claude code分销链接
 	 */
 	String DISTRIBUTE_NANO_URL = "https://nano.yhlxj.com/%s";
+
+	/**
+	 * cc api domain
+	 */
+	String CLAUDE_CODE_API_PREFIX = "https://relay01.yhlxj.com/";
+
+	String CLAUDE_CODE_HEARD_KEY = "X-Admin-Key";
+
+	String CLAUDE_CODE_API_ADMIN_KEY = "claudecodeyhlxjclaude";
+
+	//连接超时时间 毫秒
+	int CONNECT_MILLISECONDS = 10000;
 }

+ 15 - 0
netflix-dao/src/main/java/com/cyksj/mapper/CodexUserMapper.java

@@ -0,0 +1,15 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.CodexUser;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CodexUserMapper
+ * 创建者: Claude
+ * 创建时间:2025/9/15
+ */
+@Mapper
+public interface CodexUserMapper extends BaseMapper<CodexUser> {
+}

+ 56 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CodexUser.java

@@ -0,0 +1,56 @@
+package com.cyksj.model.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
+import com.baomidou.mybatisplus.annotation.TableField;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CodexUser
+ * 创建者: Claude
+ * 创建时间:2025/9/15
+ */
+@Getter
+@Setter
+public class CodexUser extends BaseEntity {
+    
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 套餐名
+     */
+    private String planName;
+    
+    /**
+     * 每日预算上限
+     */
+    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    private Integer openaiDailyLimit;
+    
+    /**
+     * 每月预算上限
+     */
+    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    private Integer openaiQuota;
+    
+    /**
+     * 座位ID
+     */
+    private Long relationId;
+
+    /**
+     * 过期时间
+     */
+    private Date expiryTime;
+
+    /**
+     * 是否重试更新
+     */
+    private Boolean isRetry;
+}

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

@@ -291,4 +291,26 @@ public class GoodsDonSku extends BaseEntity {
      */
     @TableField(updateStrategy = FieldStrategy.IGNORED)
     private Integer highestUsage;
+
+    /**
+     * 是否赠送codex
+     */
+    private Boolean isCodex;
+
+    /**
+     * codex套餐名
+     */
+    private String codexPlanName;
+
+    /**
+     * 每日预算上限
+     */
+    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    private Integer openaiDailyLimit;
+
+    /**
+     * 每月预算上限
+     */
+    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    private Integer openaiQuota;
 }

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

@@ -212,6 +212,28 @@ public class ReqGoodsSkuInsert {
          */
         @TableField(updateStrategy = FieldStrategy.IGNORED)
         private Integer highestUsage;
+
+        /**
+         * 是否赠送codex
+         */
+        private Boolean isCodex;
+
+        /**
+         * codex套餐名
+         */
+        private String codexPlanName;
+
+        /**
+         * 每日预算上限
+         */
+        @TableField(updateStrategy = FieldStrategy.IGNORED)
+        private Integer openaiDailyLimit;
+
+        /**
+         * 每月预算上限
+         */
+        @TableField(updateStrategy = FieldStrategy.IGNORED)
+        private Integer openaiQuota;
     }
 
 }

+ 24 - 0
netflix-dao/src/main/java/com/cyksj/model/request/UpdateDailyLimitRequest.java

@@ -0,0 +1,24 @@
+package com.cyksj.model.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: UpdateDailyLimitRequest
+ * 创建者: JavaZou
+ * 创建时间:2025/9/16 18:15
+ */
+@Getter
+@Setter
+public class UpdateDailyLimitRequest {
+	/**
+	 * 每日美金限额
+	 */
+	private Integer dailyLimit;
+
+	/**
+	 * 总美金额度
+	 */
+	private Integer totalQuota;
+}

+ 27 - 0
netflix-dao/src/main/java/com/cyksj/model/request/codex/CodexPlanReq.java

@@ -0,0 +1,27 @@
+package com.cyksj.model.request.codex;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CodexPlanReq
+ * 创建者: Claude
+ * 创建时间:2025/9/16
+ */
+@Getter
+@Setter
+@Builder
+public class CodexPlanReq {
+    
+    /**
+     * 每日预算上限
+     */
+    private Integer openaiDailyLimit;
+    
+    /**
+     * 每月预算上限
+     */
+    private Integer openaiQuota;
+}

+ 49 - 0
netflix-dao/src/main/java/com/cyksj/model/request/codex/CodexUserPackageReq.java

@@ -0,0 +1,49 @@
+package com.cyksj.model.request.codex;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CodexUserPackageReq
+ * 创建者: Claude
+ * 创建时间:2025/9/16
+ */
+@Getter
+@Setter
+@Builder
+public class CodexUserPackageReq {
+
+    /**
+     * 套餐名
+     */
+    private String planName;
+    
+    /**
+     * 每日预算上限
+     */
+    private Integer openaiDailyLimit;
+    
+    /**
+     * 每月预算上限
+     */
+    private Integer openaiQuota;
+    
+    /**
+     * 座位ID (内部设置,用户请求时不需要)
+     */
+    private Long relationId;
+    
+    /**
+     * 用户ID (内部设置,用户请求时不需要)
+     */
+    private Long userId;
+
+    /**
+     * 过期时间
+     */
+    private Date expiryTime;
+}

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/model/request/codex/CreateKeyRequest.java

@@ -0,0 +1,13 @@
+package com.cyksj.model.request.codex;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 创建密钥请求参数
+ */
+@Getter
+@Setter
+public class CreateKeyRequest {
+	private String keyName;
+}

+ 40 - 0
netflix-dao/src/main/java/com/cyksj/model/response/codex/CodexPlanResp.java

@@ -0,0 +1,40 @@
+package com.cyksj.model.response.codex;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CodexPlanResp
+ * 创建者: Claude
+ * 创建时间:2025/9/16
+ */
+@Getter
+@Setter
+public class CodexPlanResp {
+    
+    /**
+     * 成功标识
+     */
+    private Boolean success;
+    
+    /**
+     * 错误消息
+     */
+    private String message;
+    
+    /**
+     * 用户ID
+     */
+    private String userId;
+    
+    /**
+     * 每日预算上限
+     */
+    private Integer openaiDailyLimit;
+    
+    /**
+     * 每月预算上限
+     */
+    private Integer openaiQuota;
+}

+ 47 - 0
netflix-dao/src/main/java/com/cyksj/model/response/codex/CodexUserPackageResp.java

@@ -0,0 +1,47 @@
+package com.cyksj.model.response.codex;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CodexUserPackageResp
+ * 创建者: Claude
+ * 创建时间:2025/9/16
+ */
+@Getter
+@Setter
+@Builder
+public class CodexUserPackageResp {
+    
+    /**
+     * 用户ID
+     */
+    private Long userId;
+    
+    /**
+     * 每日预算上限
+     */
+    private Integer openaiDailyLimit;
+    
+    /**
+     * 每月预算上限
+     */
+    private Integer openaiQuota;
+    
+    /**
+     * 座位ID
+     */
+    private Long relationId;
+    
+    /**
+     * 操作结果消息
+     */
+    private String message;
+    
+    /**
+     * 是否为新创建的用户
+     */
+    private Boolean isNewUser;
+}

+ 5 - 0
netflix-dao/src/main/java/com/cyksj/model/views/ClaudeCodeUserInfoView.java

@@ -54,4 +54,9 @@ public class ClaudeCodeUserInfoView {
 	 * cc最高约可使用次数
 	 */
 	private Integer highestUsage;
+
+	/**
+	 * codex info
+	 */
+	private CodexUserInfoView codexUserInfo;
 }

+ 88 - 0
netflix-dao/src/main/java/com/cyksj/model/views/CodexUserInfoView.java

@@ -0,0 +1,88 @@
+package com.cyksj.model.views;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CodexUserInfoView
+ * 创建者: Claude
+ * 创建时间:2025/9/15
+ */
+@Getter
+@Setter
+@Builder
+public class CodexUserInfoView {
+
+    @JsonIgnore
+    private Long userId;
+    
+    /**
+     * 套餐名
+     */
+    private String planName;
+    
+    /**
+     * 每日预算上限
+     */
+    private Integer openaiDailyLimit;
+    
+    /**
+     * 每月预算上限
+     */
+    private Integer openaiQuota;
+    
+    /**
+     * 座位ID
+     */
+    private Long relationId;
+    
+    /**
+     * Claude Code套餐信息
+     */
+    private ClaudeCodeInfo claudeCodeInfo;
+    
+    @Getter
+    @Setter
+    @Builder
+    public static class ClaudeCodeInfo {
+        @JsonIgnore
+        private Long userId;
+
+        /**
+         * 套餐名
+         */
+        private String planName;
+
+        private Long relationId;
+        
+        /**
+         * claude code积分上限
+         */
+        private Integer codePoints;
+        
+        /**
+         * claude code每小时恢复积分
+         */
+        private Integer codeCreditRecovery;
+        
+        /**
+         * code 有效期
+         */
+        private Date expiryTime;
+        
+        /**
+         * 续费升级skuId
+         */
+        private Long renewSkuId;
+        
+        /**
+         * 续费升级过期时间
+         */
+        private Date renewExpiryTime;
+    }
+}

+ 20 - 0
netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuFrontView.java

@@ -246,4 +246,24 @@ public class GoodsDonSkuFrontView {
 	 * cc最高约可使用次数
 	 */
 	private Integer highestUsage;
+
+	/**
+	 * 是否赠送codex
+	 */
+	private Boolean isCodex;
+
+	/**
+	 * codex套餐名
+	 */
+	private String codexPlanName;
+
+	/**
+	 * 每日预算上限
+	 */
+	private Integer openaiDailyLimit;
+
+	/**
+	 * 每月预算上限
+	 */
+	private Integer openaiQuota;
 }

+ 23 - 0
netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuRenewView.java

@@ -106,4 +106,27 @@ public class GoodsDonSkuRenewView {
 	@DbField("sku.highest_usage")
 	private Integer highestUsage;
 
+	/**
+	 * 是否赠送codex
+	 */
+	@DbField("sku.is_codex")
+	private Boolean isCodex;
+
+	/**
+	 * codex套餐名
+	 */
+	@DbField("sku.codex_plan_name")
+	private String codexPlanName;
+
+	/**
+	 * 每日预算上限
+	 */
+	@DbField("sku.openai_daily_limit")
+	private Integer openaiDailyLimit;
+
+	/**
+	 * 每月预算上限
+	 */
+	@DbField("sku.openai_quota")
+	private Integer openaiQuota;
 }

+ 24 - 0
netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuView.java

@@ -92,6 +92,30 @@ public class GoodsDonSkuView {
 	@DbField("gdk.highest_usage")
 	private Integer highestUsage;
 
+	/**
+	 * 是否赠送codex
+	 */
+	@DbField("gdk.is_codex")
+	private Boolean isCodex;
+
+	/**
+	 * codex套餐名
+	 */
+	@DbField("gdk.codex_plan_name")
+	private String codexPlanName;
+
+	/**
+	 * 每日预算上限
+	 */
+	@DbField("gdk.openai_daily_limit")
+	private Integer openaiDailyLimit;
+
+	/**
+	 * 每月预算上限
+	 */
+	@DbField("gdk.openai_quota")
+	private Integer openaiQuota;
+
 	/**
 	 * 优惠券id
 	 */

+ 24 - 0
netflix-dao/src/main/java/com/cyksj/model/views/RenewUpgradePackageFrontView.java

@@ -108,5 +108,29 @@ public class RenewUpgradePackageFrontView {
 		 */
 		@DbField("sku.highest_usage")
 		private Integer highestUsage;
+
+		/**
+		 * 是否赠送codex
+		 */
+		@DbField("sku.is_codex")
+		private Boolean isCodex;
+
+		/**
+		 * codex套餐名
+		 */
+		@DbField("sku.codex_plan_name")
+		private String codexPlanName;
+
+		/**
+		 * 每日预算上限
+		 */
+		@DbField("sku.openai_daily_limit")
+		private Integer openaiDailyLimit;
+
+		/**
+		 * 每月预算上限
+		 */
+		@DbField("sku.openai_quota")
+		private Integer openaiQuota;
 	}
 }

+ 15 - 0
netflix-dao/src/main/java/com/cyksj/model/views/TicketRenewUpgradeView.java

@@ -95,4 +95,19 @@ public class TicketRenewUpgradeView {
 	 * cc最高约可使用次数
 	 */
 	private Integer highestUsage;
+
+	/**
+	 * codex套餐名
+	 */
+	private String codexPlanName;
+
+	/**
+	 * 每日预算上限
+	 */
+	private Integer openaiDailyLimit;
+
+	/**
+	 * 每月预算上限
+	 */
+	private Integer openaiQuota;
 }

+ 24 - 0
netflix-dao/src/main/java/com/cyksj/model/views/YhShopGoodsSkuFrontView.java

@@ -208,4 +208,28 @@ public class YhShopGoodsSkuFrontView {
 	 */
 	@DbField("sku.highest_usage")
 	private Integer highestUsage;
+
+	/**
+	 * 是否赠送codex
+	 */
+	@DbField("sku.is_codex")
+	private Boolean isCodex;
+
+	/**
+	 * codex套餐名
+	 */
+	@DbField("sku.codex_plan_name")
+	private String codexPlanName;
+
+	/**
+	 * 每日预算上限
+	 */
+	@DbField("sku.openai_daily_limit")
+	private Integer openaiDailyLimit;
+
+	/**
+	 * 每月预算上限
+	 */
+	@DbField("sku.openai_quota")
+	private Integer openaiQuota;
 }

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

@@ -63,7 +63,7 @@ public interface ClaudeCodeService {
 
 	Boolean checkClaudeCodeUser(Long userId);
 
-	void updateClaudeCodeUserPackageInfo(ClaudeCodeUser claudeCodeUser, Date expiryTime);
+	void updateClaudeCodeUserPackageInfo(ClaudeCodeUser claudeCodeUser, Date expiryTime, GoodsDonSku sku);
 
 	/**
 	 * 删除用户claude code 套餐

+ 85 - 38
netflix-service/src/main/java/com/cyksj/service/claude/impl/ClaudeCodeServiceImpl.java

@@ -27,17 +27,16 @@ import com.cyksj.model.request.ClaudeCodeApiKeysReq;
 import com.cyksj.model.request.ClaudeCodeCreditCardUseReq;
 import com.cyksj.model.request.ClaudeCodeDelReq;
 import com.cyksj.model.request.CreditResetReq;
+import com.cyksj.model.request.codex.CodexUserPackageReq;
 import com.cyksj.model.response.ClaudeCodePointsHistoryResp;
 import com.cyksj.model.response.ClaudeCodeRewardDetailsResp;
 import com.cyksj.model.response.CreditResetResp;
 import com.cyksj.model.response.TimeRateConfigsResp;
 import com.cyksj.model.response.claudecode.ClaudeCodeResp;
-import com.cyksj.model.views.ClaudeCodeUserApiKeysView;
-import com.cyksj.model.views.ClaudeCodeUserCreditCardRecordView;
-import com.cyksj.model.views.ClaudeCodeUserInfoView;
-import com.cyksj.model.views.ClaudeCodeUserResetRecordView;
+import com.cyksj.model.views.*;
 import com.cyksj.redis.RedisService;
 import com.cyksj.service.claude.ClaudeCodeService;
+import com.cyksj.service.codex.CodexService;
 import com.cyksj.service.user.UserBindRelationService;
 import com.ejlchina.searcher.BeanSearcher;
 import com.ejlchina.searcher.SearchResult;
@@ -100,14 +99,9 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 
 	private final RedisService redisService;
 
-	private final static String CLAUDE_CODE_API_PREFIX = "https://relay01.yhlxj.com/";
-
-	public static final String CLAUDE_CODE_HEARD_KEY = "X-Admin-Key";
-
-	public static final String CLAUDE_CODE_API_ADMIN_KEY = "claudecodeyhlxjclaude";
-
-	//连接超时时间 毫秒
-	public static final int CONNECT_MILLISECONDS = 10000;
+	private final CodexService codexService;
+	
+	private final CodexUserMapper codexUserMapper;
 
 	@Override
 	public void generateOrUpdateClaudeCodeUserInfo(Long orderId, Long relationId, Long userId, GoodsDonSku sku, Integer orderType) {
@@ -148,7 +142,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 			//获取最新的claude code user
 			claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
 			//生成claude code或更新用户套餐
-			ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(relation.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), claudeCodeUser.getExpiryTime());
+			ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(relation.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), claudeCodeUser.getExpiryTime(), claudeCodeUser.getRelationId(), sku);
 			if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
 				//重试更新code userInfo数据
 				setClaudeCodeUserRetryUpdateInfo(relationId);
@@ -206,7 +200,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 		if (checkClaudeCodeUser(userId)) {
 			Long claudeCodeUserId = getClaudeCodeUserId(userId);
 			Assert.notNull(claudeCodeUserId, "您的claude code账号已过期");
-			String apiKeyUrl = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", claudeCodeUserId);
+			String apiKeyUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", claudeCodeUserId);
 			Map<String, Object> parmas = new HashMap<>();
 			parmas.put("name", req.getName());
 			Integer expiresDays = req.getExpiresDays();
@@ -228,8 +222,8 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 		Long keyId = delReq.getKeyId();
 		if (checkClaudeCodeUser(userId)) {
 			Long claudeCodeUserId = getClaudeCodeUserId(userId);
-			String url = String.format(CLAUDE_CODE_API_PREFIX + "api/keys/%s/%s", claudeCodeUserId, keyId);
-			HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(CLAUDE_CODE_HEARD_KEY, CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(CONNECT_MILLISECONDS).execute();
+			String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/keys/%s/%s", claudeCodeUserId, keyId);
+			HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(Constant.CONNECT_MILLISECONDS).execute();
 			String responseBody = execute.body();
 			ClaudeCodeResp claudeCodeResp = Jsons.parseObject(responseBody, ClaudeCodeResp.class);
 			if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
@@ -243,7 +237,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 	public List<ClaudeCodeUserApiKeysView> getUserApiKeys(long userId) throws Exception {
 		Long claudeCodeUserId = getClaudeCodeUserId(userId);
 		if (claudeCodeUserId != null) {
-			String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", claudeCodeUserId);
+			String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", claudeCodeUserId);
 			ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
 			return Jsons.parseList(claudeCodeResp.getData(), ClaudeCodeUserApiKeysView.class);
 		}
@@ -254,7 +248,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 	public ClaudeCodePointsHistoryResp getUserPointsDetail(Long userId, String type, Integer page, Integer limit) throws Exception {
 		Long claudeCodeUserId = getClaudeCodeUserId(userId);
 		if (claudeCodeUserId != null) {
-			String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/history?page=%s&limit=%s&type=%s", claudeCodeUserId, page, limit, type);
+			String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/history?page=%s&limit=%s&type=%s", claudeCodeUserId, page, limit, type);
 			ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
 			ClaudeCodePointsHistoryResp historyResp = Jsons.parseObject(claudeCodeResp.getData(), ClaudeCodePointsHistoryResp.class);
 			return historyResp;
@@ -266,7 +260,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 	public ClaudeCodeResp getUserDashboard(Long userId) throws Exception {
 		Long claudeCodeUserId = getClaudeCodeUserId(userId);
 		if (claudeCodeUserId != null) {
-			String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/dashboard", claudeCodeUserId);
+			String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/dashboard", claudeCodeUserId);
 			ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
 			return claudeCodeResp;
 		}
@@ -277,7 +271,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 	public ClaudeCodeResp getUserBalance(Long userId) throws Exception {
 		Long claudeCodeUserId = getClaudeCodeUserId(userId);
 		if (claudeCodeUserId != null) {
-			String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/balance", claudeCodeUserId);
+			String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/balance", claudeCodeUserId);
 			ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
 			return claudeCodeResp;
 		}
@@ -293,7 +287,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 			end = now.toString();
 		}
 		if (claudeCodeUserId != null) {
-			String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/analytics?start=%s&end=%s&tz=%s&page=%s&limit=%s&order=%s&type=%s", claudeCodeUserId, start, end, tz, page, limit, order, type);
+			String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/analytics?start=%s&end=%s&tz=%s&page=%s&limit=%s&order=%s&type=%s", claudeCodeUserId, start, end, tz, page, limit, order, type);
 			ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
 			return claudeCodeResp;
 		}
@@ -306,7 +300,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 	 */
 	@Override
 	public void invalidateUserCache(Long userId) {
-		String url = String.format(CLAUDE_CODE_API_PREFIX + "api/cache/invalidate/user/%s", userId);
+		String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/cache/invalidate/user/%s", userId);
 		try {
 			executeClaudeCodePostApi(url, null);
 		} catch (Exception e) {
@@ -331,6 +325,8 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 				//清除用户套餐
 				if (delUserPackages(claudeCodeUser.getUserId())) {
 					claudeCodeUserMapper.deleteById(claudeCodeUser.getId());
+					//codex
+					codexUserMapper.delete(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, claudeCodeUser.getRelationId()).in(CodexUser::getUserId, userIdList));
 				}
 			}
 			return null;
@@ -359,13 +355,17 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 			claudeCodeUser.setExpiryTime(groupsRelationView.getExpiryTime());
 			claudeCodeUserMapper.updateById(claudeCodeUser);
 		}
+		//续费升级
+		if (claudeCodeUser.getRenewSkuId() != null) {
+			sku = skuMapper.selectById(claudeCodeUser.getRenewSkuId());
+		}
 		if (sku == null) {
 			sku = skuMapper.selectById(groupsRelationView.getSkuId());
 		}
 		//是否需要重试更新 cladueCode userInfo信息
 		if (claudeCodeUser.getIsRetry()) {
 			try {
-				ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), claudeCodeUser.getExpiryTime());
+				ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), claudeCodeUser.getExpiryTime(), claudeCodeUser.getRelationId(), sku);
 				if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
 					claudeCodeUser.setIsRetry(Boolean.TRUE);
 					claudeCodeUserMapper.updateById(claudeCodeUser);
@@ -377,6 +377,25 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 
 			}
 		}
+		CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, claudeCodeUser.getRelationId()).in(CodexUser::getUserId, userIdList).last("limit 1"));
+		if (codexUser == null) {
+			//是否赠送codex
+			if (sku.getIsCodex()) {
+				CodexUserPackageReq codexPackageReq = CodexUserPackageReq.builder()
+						.userId(claudeCodeUser.getUserId())
+						.relationId(claudeCodeUser.getRelationId())
+						.planName(sku.getCodexPlanName())
+						.openaiDailyLimit(sku.getOpenaiDailyLimit())
+						.openaiQuota(sku.getOpenaiQuota())
+						.expiryTime(claudeCodeUser.getExpiryTime()).build();
+				codexService.createOrUpdateUserPackage(codexPackageReq);
+			}
+		}
+		CodexUserInfoView codexUserInfoView = null;
+		if (codexUser != null) {
+			codexUserInfoView = CodexUserInfoView.builder().planName(codexUser.getPlanName()).openaiDailyLimit(codexUser.getOpenaiDailyLimit()).openaiQuota(codexUser.getOpenaiQuota()).relationId(codexUser.getRelationId()).build();
+		}
+
 		return ClaudeCodeUserInfoView.builder()
 				.planName(claudeCodeUser.getPlanName())
 				.relationId(claudeCodeUser.getRelationId())
@@ -387,19 +406,41 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 				.renewOrderId(claudeCodeUser.getRenewOrderId())
 				.renewExpiryTime(claudeCodeUser.getRenewExpiryTime())
 				.highestUsage(sku.getHighestUsage())
+				.codexUserInfo(codexUserInfoView)
 				.build();
 	}
 
-	public ClaudeCodeResp createOrUpdateClaudeCodeUserPackage(Long userId, String planName, Integer codePoints, Integer codeCreditRecovery, Date expiryTime) throws Exception {
+	public ClaudeCodeResp createOrUpdateClaudeCodeUserPackage(Long userId, String planName, Integer codePoints, Integer codeCreditRecovery, Date expiryTime, Long relationId, GoodsDonSku sku) 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("expire_time", DateUtil.format(expiryTime,"yyyy-MM-dd HH:mm:ss"));
-		String planUrl = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
+		if (sku.getIsCodex()) {
+			parmas.put("openai_daily_limit", sku.getOpenaiDailyLimit());
+			parmas.put("openai_quota", sku.getOpenaiQuota());
+		} else {
+			parmas.put("openai_daily_limit", 0);
+			parmas.put("openai_quota", 0);
+		}
+		parmas.put("expire_time", DateUtil.format(expiryTime, "yyyy-MM-dd HH:mm:ss"));
+		String planUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
 		ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(planUrl, Jsons.toJson(parmas));
 		if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
 			log.info("用户claude code用户:{}套餐:{}创建或更新失败,info:{}", userId, planName, claudeCodeResp.getMessage());
+		} else {
+			//是否赠送codex
+			if (sku.getIsCodex()) {
+				CodexUserPackageReq codexPackageReq = CodexUserPackageReq.builder()
+						.userId(userId)
+						.relationId(relationId)
+						.planName(sku.getCodexPlanName())
+						.openaiDailyLimit(sku.getOpenaiDailyLimit())
+						.openaiQuota(sku.getOpenaiQuota())
+						.expiryTime(expiryTime).build();
+				codexService.createOrUpdateUserPackage(codexPackageReq);
+			} else {
+				codexService.delCodexUser(userId, relationId);
+			}
 		}
 		return claudeCodeResp;
 	}
@@ -418,9 +459,9 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 	}
 
 	@Override
-	public void updateClaudeCodeUserPackageInfo(ClaudeCodeUser claudeCodeUser, Date expiryTime) {
+	public void updateClaudeCodeUserPackageInfo(ClaudeCodeUser claudeCodeUser, Date expiryTime, GoodsDonSku sku) {
 		try {
-			ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), expiryTime);
+			ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), expiryTime, claudeCodeUser.getRelationId(), sku);
 			if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
 				//重试标记
 				claudeCodeUser.setIsRetry(Boolean.TRUE);
@@ -437,7 +478,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 
 	@Override
 	public Boolean delUserPackages(Long userId) {
-		String url = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
+		String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
 		try {
 			ClaudeCodeResp claudeCodeResp = executeClaudeCodeDeletedApi(url);
 			if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
@@ -459,7 +500,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 	@Override
 	public TimeRateConfigsResp getAllConfigs() {
 		try {
-			String url = CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs";
+			String url = Constant.CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs";
 			ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
 			return Jsons.parseObject(claudeCodeResp.getData(), TimeRateConfigsResp.class);
 		} catch (Exception e) {
@@ -474,7 +515,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 			throw BusinessRuntimeException.getInstance("用户ID无效");
 		}
 
-		String url = CLAUDE_CODE_API_PREFIX + "api/users/" + userId + "/credits/reset";
+		String url = Constant.CLAUDE_CODE_API_PREFIX + "api/users/" + userId + "/credits/reset";
 		String body = req != null ? Jsons.toJson(req) : "{}";
 		ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(url, body);
 		if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
@@ -523,8 +564,8 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 			return build.call(() -> {
 				try {
 					HttpResponse execute = HttpUtil.createGet(url)
-							.header(CLAUDE_CODE_HEARD_KEY, CLAUDE_CODE_API_ADMIN_KEY)
-							.setConnectionTimeout(CONNECT_MILLISECONDS)
+							.header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY)
+							.setConnectionTimeout(Constant.CONNECT_MILLISECONDS)
 							.execute();
 					ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
 					if (!Constant.SUCCESS.equals(resp.getMessage())) {
@@ -551,8 +592,8 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 			return build.call(() -> {
 				try {
 					HttpResponse execute = HttpUtil.createPost(url)
-							.header(CLAUDE_CODE_HEARD_KEY, CLAUDE_CODE_API_ADMIN_KEY)
-							.setConnectionTimeout(CONNECT_MILLISECONDS)
+							.header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY)
+							.setConnectionTimeout(Constant.CONNECT_MILLISECONDS)
 							.body(body)
 							.execute();
 					ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
@@ -578,7 +619,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 		try {
 			return build.call(() -> {
 				try {
-					HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(CLAUDE_CODE_HEARD_KEY, CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(CONNECT_MILLISECONDS).execute();
+					HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(Constant.CONNECT_MILLISECONDS).execute();
 					ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
 					if (!Constant.SUCCESS.equals(resp.getMessage())) {
 						log.error("claude code DELETE URL:{}接口返回msg:{}", url, resp.getMessage());
@@ -607,7 +648,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 	}
 
 	/**
-	 * claude code用户信息重试更新信息
+	 * claude code、codex user用户信息重试更新信息
 	 */
 	public void setClaudeCodeUserRetryUpdateInfo(Long relationId) {
 		ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
@@ -615,6 +656,12 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 			claudeCodeUser.setIsRetry(Boolean.TRUE);
 			claudeCodeUserMapper.updateById(claudeCodeUser);
 		}
+
+		CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1"));
+		if (codexUser != null) {
+			codexUser.setIsRetry(Boolean.TRUE);
+			codexUserMapper.updateById(codexUser);
+		}
 	}
 
 	public Retryer getApiRetryer(Integer sleepSecond, Integer attemptNum) {
@@ -1033,7 +1080,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 	}
 
 	private void adjClaudeCodeUserPoints(Long userId, Integer points, String type, String description) throws Exception {
-		String apiKeyUrl = String.format(CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/adjust", userId);
+		String apiKeyUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/adjust", userId);
 		Map<String, Object> parmas = new HashMap<>();
 		parmas.put("amount", points);
 		parmas.put("type", type);

+ 52 - 0
netflix-service/src/main/java/com/cyksj/service/codex/CodexService.java

@@ -0,0 +1,52 @@
+package com.cyksj.service.codex;
+
+import com.cyksj.model.request.UpdateDailyLimitRequest;
+import com.cyksj.model.request.codex.CodexUserPackageReq;
+import com.cyksj.model.response.claudecode.ClaudeCodeResp;
+import com.cyksj.model.views.CodexUserInfoView;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CodexService
+ * 创建者: Claude
+ * 创建时间:2025/9/15
+ */
+public interface CodexService {
+    
+    /**
+     * 获取用户Codex信息
+     */
+    CodexUserInfoView getCodexUserInfo(Long userId);
+    
+    /**
+     * 创建或更新用户套餐
+     */
+    void createOrUpdateUserPackage(CodexUserPackageReq req);
+    
+    /**
+     * 获取用户OpenAI使用统计
+     */
+    ClaudeCodeResp getUserOpenAIUsage(Long userId, String period);
+    
+    /**
+     * 更新用户OpenAI每日美金限额
+     */
+    ClaudeCodeResp updateUserOpenAIDailyLimit(Long userId, UpdateDailyLimitRequest request);
+    
+    /**
+     * 获取系统OpenAI指标统计
+     */
+    ClaudeCodeResp getSystemMetrics(String period, String metric);
+    
+    /**
+     * 获取Java系统专用的用户OpenAI控制台数据
+     */
+    ClaudeCodeResp getJavaUserDashboard(Long userId);
+    
+    /**
+     * 获取Java系统专用的用户OpenAI图表+明细联动数据
+     */
+    ClaudeCodeResp getJavaUserAnalytics(Long userId, String start, String end, Integer page, Integer limit, String order);
+
+    void delCodexUser(Long userId, Long relationId);
+}

+ 408 - 0
netflix-service/src/main/java/com/cyksj/service/codex/impl/CodexServiceImpl.java

@@ -0,0 +1,408 @@
+package com.cyksj.service.codex.impl;
+
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.http.Method;
+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.common.util.StringUtil;
+import com.cyksj.mapper.*;
+import com.cyksj.model.entity.*;
+import com.cyksj.model.request.UpdateDailyLimitRequest;
+import com.cyksj.model.request.codex.CodexUserPackageReq;
+import com.cyksj.model.response.claudecode.ClaudeCodeResp;
+import com.cyksj.model.views.CodexUserInfoView;
+import com.cyksj.service.codex.CodexService;
+import com.cyksj.service.user.UserBindRelationService;
+import com.github.rholder.retry.Retryer;
+import com.github.rholder.retry.RetryerBuilder;
+import com.github.rholder.retry.StopStrategies;
+import com.github.rholder.retry.WaitStrategies;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CodexServiceImpl
+ * 创建者: Claude
+ * 创建时间:2025/9/15
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class CodexServiceImpl implements CodexService {
+
+	private final CodexUserMapper codexUserMapper;
+
+	private final ClaudeCodeUserMapper claudeCodeUserMapper;
+
+	private final UserBindRelationService userBindRelationService;
+
+	private final GoodsDonSkuMapper skuMapper;
+
+	private final GroupsRelationMapper relationMapper;
+
+	private final GroupsMapper groupsMapper;
+
+	@Override
+	public CodexUserInfoView getCodexUserInfo(Long userId) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
+		// 查询Claude Code信息
+		CodexUserInfoView.ClaudeCodeInfo claudeCodeInfo = getClaudeCodeInfo(userId);
+
+		if (codexUser == null) {
+			//是否有claude code
+			if (claudeCodeInfo != null && claudeCodeInfo.getCodePoints() != null) {
+				GoodsDonSku sku = null;
+				if (claudeCodeInfo.getRenewSkuId() != null) {
+					sku = skuMapper.selectById(claudeCodeInfo.getRenewSkuId());
+				}
+				//续费升级规格不存在 或者 不赠送codex
+				if (sku == null || !sku.getIsCodex()) {
+					GroupsRelation relation = relationMapper.selectById(claudeCodeInfo.getRelationId());
+					GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId());
+					sku = skuMapper.selectById(groupsTrips.getSkuId());
+				}
+
+				//sku 存在并且开启赠送codex
+				if (sku != null && sku.getIsCodex()) {
+					CodexUserPackageReq codexPackageReq = CodexUserPackageReq.builder()
+							.userId(claudeCodeInfo.getUserId())
+							.planName(sku.getCodexPlanName())
+							.openaiDailyLimit(sku.getOpenaiDailyLimit())
+							.openaiQuota(sku.getOpenaiQuota())
+							.expiryTime(claudeCodeInfo.getExpiryTime())
+							.build();
+					createOrUpdateUserPackage(codexPackageReq);
+					codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
+				}
+			}
+			if (codexUser == null) {
+				return null;
+			}
+		}
+
+
+		return CodexUserInfoView.builder().planName(codexUser.getPlanName()).openaiDailyLimit(codexUser.getOpenaiDailyLimit()).openaiQuota(codexUser.getOpenaiQuota()).relationId(codexUser.getRelationId()).claudeCodeInfo(claudeCodeInfo).build();
+	}
+
+	private CodexUserInfoView.ClaudeCodeInfo getClaudeCodeInfo(Long userId) {
+		// 获取用户关联的ID列表
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+
+		ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1"));
+
+		if (claudeCodeUser == null) {
+			return null;
+		}
+
+		return CodexUserInfoView.ClaudeCodeInfo.builder().userId(claudeCodeUser.getUserId()).planName(claudeCodeUser.getPlanName()).relationId(claudeCodeUser.getRelationId()).codePoints(claudeCodeUser.getCodePoints()).codeCreditRecovery(claudeCodeUser.getCodeCreditRecovery()).expiryTime(claudeCodeUser.getExpiryTime()).renewSkuId(claudeCodeUser.getRenewSkuId()).renewExpiryTime(claudeCodeUser.getRenewExpiryTime()).build();
+	}
+
+	@Override
+	public void createOrUpdateUserPackage(CodexUserPackageReq req) {
+		Long userId = req.getUserId();
+		// 构建完整的套餐参数
+		String planName = req.getPlanName();
+		Date expiryTime = req.getExpiryTime();
+		Integer openaiDailyLimit = req.getOpenaiDailyLimit();
+		Integer openaiQuota = req.getOpenaiQuota();
+		Long relationId = req.getRelationId();
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		CodexUser existingCodexUser = Optional.ofNullable(codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"))).orElse(new CodexUser());
+
+		existingCodexUser.setPlanName(planName);
+		existingCodexUser.setRelationId(relationId);
+		existingCodexUser.setUserId(userId);
+		existingCodexUser.setOpenaiDailyLimit(openaiDailyLimit);
+		existingCodexUser.setOpenaiQuota(openaiQuota);
+		existingCodexUser.setExpiryTime(expiryTime);
+
+		if (existingCodexUser.getId() == null) {
+			try {
+				codexUserMapper.insert(existingCodexUser);
+			} catch (DuplicateKeyException e) {
+
+			}
+			return;
+		}
+		codexUserMapper.updateById(existingCodexUser);
+	}
+
+	/**
+	 * 创建或更新Codex用户套餐
+	 */
+	public ClaudeCodeResp createOrUpdateCodexUserPackage(Long userId, String planName, Integer openaiDailyLimit, Integer openaiQuota, Date expiryTime) throws Exception {
+		Map<String, Object> params = new HashMap<>();
+		params.put("plan_name", planName.replaceAll(";", ""));
+		params.put("openai_daily_limit", openaiDailyLimit);
+		params.put("openai_quota", openaiQuota);
+		params.put("end_date", DateUtil.format(expiryTime, "yyyy-MM-dd HH:mm:ss"));
+
+		String planUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
+		ClaudeCodeResp codexResp = executeCodexPostApi(planUrl, Jsons.toJson(params));
+
+		if (!"success".equals(codexResp.getMessage())) {
+			log.info("用户codex用户:{}套餐:{}创建或更新失败,info:{}", userId, planName, codexResp.getMessage());
+			throw BusinessRuntimeException.getInstance("调用codex 套餐接口失败");
+		}
+
+		return codexResp;
+	}
+
+
+	private Retryer<ClaudeCodeResp> getApiRetryer(int wait, int stop) {
+		return RetryerBuilder.<ClaudeCodeResp>newBuilder().retryIfResult(result -> result == null).retryIfException().withWaitStrategy(WaitStrategies.fixedWait(wait, TimeUnit.SECONDS)).withStopStrategy(StopStrategies.stopAfterAttempt(stop)).build();
+	}
+
+	/**
+	 * Codex POST请求 - 复制ClaudeCodeServiceImpl的实现模式
+	 */
+	public ClaudeCodeResp executeCodexPostApi(String url, String body) {
+		Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
+		try {
+			return build.call(() -> {
+				try {
+					HttpResponse execute = HttpUtil.createPost(url).header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(Constant.CONNECT_MILLISECONDS).body(body).execute();
+					ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
+					if (!"success".equals(resp.getMessage())) {
+						log.error("codex POST URL:{}接口返回msg:{}", url, resp.getMessage());
+					}
+					return resp;
+				} catch (Exception e) {
+					log.error("codex POST请求异常: url={}, error={}", url, StringUtil.getErrorText(e));
+					return null;
+				}
+			});
+		} catch (ExecutionException | com.github.rholder.retry.RetryException e) {
+			log.info("重试调用codex POST请求 url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
+			throw BusinessRuntimeException.getInstance("创建或更新codex user失败");
+		}
+	}
+
+	/**
+	 * codex用户信息重试更新信息
+	 */
+	public void setCodexUserRetryUpdateInfo(Long relationId) {
+		CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1"));
+		if (codexUser != null) {
+			codexUser.setIsRetry(Boolean.TRUE);
+			codexUserMapper.updateById(codexUser);
+		}
+	}
+
+	@Override
+	public ClaudeCodeResp getUserOpenAIUsage(Long userId, String period) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
+
+		if (codexUser == null) {
+			return null;
+		}
+		// 调用Codex API获取实时使用统计
+		String usageUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/usage?period=%s", codexUser.getUserId(), period);
+		ClaudeCodeResp codexResp = executeCodexGetApi(usageUrl);
+		return codexResp;
+	}
+
+	@Override
+	public ClaudeCodeResp updateUserOpenAIDailyLimit(Long userId, UpdateDailyLimitRequest request) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
+
+		if (codexUser == null) {
+			throw new BusinessRuntimeException("用户未开通Codex服务");
+		}
+		Integer dailyLimit = request.getDailyLimit();
+		Integer totalQuota = request.getTotalQuota();
+
+		// 验证每日限额参数
+		if (dailyLimit == null || dailyLimit <= 0) {
+			throw new BusinessRuntimeException("每日限额必须大于0");
+		}
+		
+		// 保存原始值用于回滚
+		Integer originalDailyLimit = codexUser.getOpenaiDailyLimit();
+		
+		// 更新数据库中的每日限额
+		codexUser.setOpenaiDailyLimit(dailyLimit);
+		codexUserMapper.updateById(codexUser);
+		
+		try {
+			// 调用Codex API更新每日限额
+			String updateUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/limit", codexUser.getUserId());
+			Map<String, Object> params = new HashMap<>();
+			params.put("daily_limit", dailyLimit);
+			params.put("total_quota", totalQuota);
+
+			ClaudeCodeResp codexResp = executeCodexPostApi(updateUrl, Jsons.toJson(params));
+			
+			if (!"success".equals(codexResp.getMessage())) {
+				log.error("更新用户{}每日限额{}失败: {}", userId, dailyLimit, codexResp.getMessage());
+				// API调用失败时回滚数据库更新
+				codexUser.setOpenaiDailyLimit(originalDailyLimit);
+				codexUserMapper.updateById(codexUser);
+				throw new BusinessRuntimeException("更新每日限额失败");
+			}
+			
+			log.info("成功更新用户{}每日限额为{}", userId, dailyLimit);
+			return codexResp;
+			
+		} catch (Exception e) {
+			log.error("更新用户{}每日限额{}异常: {}", userId, dailyLimit, StringUtil.getErrorText(e));
+			// 异常时回滚数据库更新
+			codexUser.setOpenaiDailyLimit(originalDailyLimit);
+			codexUserMapper.updateById(codexUser);
+			throw new BusinessRuntimeException("更新每日限额失败");
+		}
+	}
+
+	@Override
+	public ClaudeCodeResp getSystemMetrics(String period, String metric) {
+		try {
+			// 构建系统指标API URL
+			String metricsUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/system/metrics");
+			
+			// 添加查询参数
+			List<String> queryParams = new ArrayList<>();
+			if (period != null && !period.isEmpty()) {
+				queryParams.add("period=" + period);
+			}
+			if (metric != null && !metric.isEmpty()) {
+				queryParams.add("metric=" + metric);
+			}
+			
+			if (!queryParams.isEmpty()) {
+				metricsUrl += "?" + String.join("&", queryParams);
+			}
+			
+			// 调用Codex API获取系统指标
+			ClaudeCodeResp codexResp = executeCodexGetApi(metricsUrl);
+			
+			if (codexResp == null) {
+				log.error("获取系统指标失败: API返回null");
+				throw new BusinessRuntimeException("获取系统指标失败");
+			}
+			
+			log.info("成功获取系统指标, period={}, metric={}", period, metric);
+			return codexResp;
+			
+		} catch (Exception e) {
+			log.error("获取系统指标异常, period={}, metric={}, error={}", period, metric, StringUtil.getErrorText(e));
+			throw new BusinessRuntimeException("获取系统指标失败");
+		}
+	}
+	
+	@Override
+	public ClaudeCodeResp getJavaUserDashboard(Long userId) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
+
+		if (codexUser == null) {
+			return null;
+		}
+
+		// 调用Codex API获取用户控制台数据
+		String dashboardUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/dashboard", codexUser.getUserId());
+		ClaudeCodeResp codexResp = executeCodexGetApi(dashboardUrl);
+		return codexResp;
+	}
+	
+	@Override
+	public ClaudeCodeResp getJavaUserAnalytics(Long userId, String start, String end, Integer page, Integer limit, String order) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
+
+		if (codexUser == null) {
+			return null;
+		}
+
+		if (start == null) {
+			DateTime now = DateTime.now();
+			start = DateUtil.offsetDay(now, -1).toString();
+			end = now.toString();
+		}
+
+		// 调用Codex API获取用户分析数据
+		String analyticsUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/analytics?start=%s&end=%s&page=%s&limit=%s&order=%s", codexUser.getUserId(), start, end, page, limit, order);
+		ClaudeCodeResp codexResp = executeCodexGetApi(analyticsUrl);
+
+		return codexResp;
+	}
+
+	@Override
+	public void delCodexUser(Long userId, Long relationId) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).eq(CodexUser::getRelationId, relationId).last("limit 1"));
+		if (codexUser != null) {
+			codexUserMapper.deleteById(codexUser.getId());
+		}
+	}
+
+	/**
+	 * Codex DELETE请求
+	 */
+	public ClaudeCodeResp executeCodexDeleteApi(String url) {
+		Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
+		try {
+			return build.call(() -> {
+				try {
+					HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url)
+							.header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY)
+							.setConnectionTimeout(Constant.CONNECT_MILLISECONDS)
+							.execute();
+					ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
+					if (!"success".equals(resp.getMessage())) {
+						log.error("codex DELETE URL:{}接口返回msg:{}", url, resp.getMessage());
+					}
+					return resp;
+				} catch (Exception e) {
+					log.error("codex DELETE请求异常: url={}, error={}", url, StringUtil.getErrorText(e));
+					return null;
+				}
+			});
+		} catch (ExecutionException | com.github.rholder.retry.RetryException e) {
+			log.error("重试调用codex DELETE请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
+			throw BusinessRuntimeException.getInstance("删除操作失败");
+		}
+	}
+
+	/**
+	 * Codex GET请求
+	 */
+	public ClaudeCodeResp executeCodexGetApi(String url) {
+		Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
+		try {
+			return build.call(() -> {
+				try {
+					HttpResponse execute = HttpUtil.createGet(url)
+							.header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY)
+							.setConnectionTimeout(Constant.CONNECT_MILLISECONDS)
+							.execute();
+					ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
+					if (!"success".equals(resp.getMessage())) {
+						log.error("codex GET URL:{}接口返回msg:{}", url, resp.getMessage());
+					}
+					return resp;
+				} catch (Exception e) {
+					log.error("codex GET请求异常: url={}, error={}", url, StringUtil.getErrorText(e));
+					return null;
+				}
+			});
+		} catch (ExecutionException | com.github.rholder.retry.RetryException e) {
+			log.error("重试调用codex GET请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
+			throw BusinessRuntimeException.getInstance("获取codex使用统计失败");
+		}
+	}
+}

+ 3 - 0
netflix-service/src/main/java/com/cyksj/service/mange/impl/CmsOrderDonServiceImpl.java

@@ -53,6 +53,7 @@ import com.cyksj.model.request.OrderRefundReq;
 import com.cyksj.redis.RedisService;
 import com.cyksj.service.chatgpt.GptUserRechargeRecordService;
 import com.cyksj.service.claude.ClaudeCodeService;
+import com.cyksj.service.codex.CodexService;
 import com.cyksj.service.coin.UserGalaxyCoinService;
 import com.cyksj.service.coupon.CouponFontService;
 import com.cyksj.service.distribute.equipment.EquipmentDistributeService;
@@ -231,6 +232,8 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
 
     private final ClaudeCodeService claudeCodeService;
 
+    private final CodexService codexService;
+
     private final NanoUserQuotaMapper nanoUserQuotaMapper;
 
     private final AccountNetflixFrozenTicketReceivedRecordMapper receivedRecordMapper;

+ 3 - 1
netflix-service/src/main/java/com/cyksj/service/mange/impl/MirrorUserCleatServiceImpl.java

@@ -66,6 +66,8 @@ public class MirrorUserCleatServiceImpl implements MirrorUserCleatService {
 
 	private final OrderDonMapper orderDonMapper;
 
+	private final CodexUserMapper codexUserMapper;
+
 	/**
 	 * 删除镜像User
 	 */
@@ -206,7 +208,7 @@ public class MirrorUserCleatServiceImpl implements MirrorUserCleatService {
 			log.info("还原镜像claude code user套餐成功");
 			//调用api更新claude code用户信息
 			Date expiryTime = relation.getExpiryTime();
-			claudeCodeService.updateClaudeCodeUserPackageInfo(claudeCodeUser, expiryTime);
+			claudeCodeService.updateClaudeCodeUserPackageInfo(claudeCodeUser, expiryTime, finalSku);
 			return;
 		}
 		OrderDonRenewRecord orderDonRenewRecord = orderDonRenewRecordMapper.selectOne(Wrappers.lambdaQuery(OrderDonRenewRecord.class)

+ 4 - 0
netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationClearServiceImpl.java

@@ -47,6 +47,8 @@ public class GroupRelationClearServiceImpl implements GroupRelationClearService
 
 	private final NanoUserQuotaMapper nanoUserQuotaMapper;
 
+	private final CodexUserMapper codexUserMapper;
+
 	@Override
 	public void clearTicket(GroupsRelation relation, UserTicketClearedRecord.Source source) {
 		Long relationId = relation.getId();
@@ -146,6 +148,8 @@ public class GroupRelationClearServiceImpl implements GroupRelationClearService
 					claudeCodeUserMapper.delete(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId));
 					//清除claude code tokens
 					oAuthTokenMapper.delete(Wrappers.lambdaQuery(OAuthToken.class).eq(OAuthToken::getUserId, userId));
+					//清除codex
+					codexUserMapper.delete(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId));
 				}
 				//nano
 				if (source != UserTicketClearedRecord.Source.vip_deduct && groupsRelation.getGoodsId() != null && groupsRelation.getGoodsId() == Constant.NANO_GOODS_ID) {

+ 7 - 1
netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationFrontServiceImpl.java

@@ -2038,12 +2038,18 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 			//claude code
 			if (groupsRelationView.getGoodsId() == Constant.CLAUDE_CODE_GOODS_ID) {
 				try {
-					//gpt用户镜像
+					//cc用户镜像
 					ClaudeCodeUserInfoView userInfo = claudeCodeService.getClaudeCodeUserSubsInfo(userId);
 					ticketRenewUpgradeView.setCodePoints(userInfo.getCodePoints());
 					ticketRenewUpgradeView.setCodeCreditRecovery(userInfo.getCodeCreditRecovery());
 					ticketRenewUpgradeView.setIsRenewPackage(userInfo.getRenewSkuId() != null ? true : false);
 					ticketRenewUpgradeView.setHighestUsage(userInfo.getHighestUsage());
+					CodexUserInfoView codexUserInfo = userInfo.getCodexUserInfo();
+					if (codexUserInfo != null) {
+						ticketRenewUpgradeView.setCodexPlanName(codexUserInfo.getPlanName());
+						ticketRenewUpgradeView.setOpenaiDailyLimit(codexUserInfo.getOpenaiDailyLimit());
+						ticketRenewUpgradeView.setOpenaiQuota(codexUserInfo.getOpenaiQuota());
+					}
 					Long renewSkuId = userInfo.getRenewSkuId();
 					if (renewSkuId != null) {
 						GoodsDonSkuRenewView renewSku = beanSearcher.searchFirst(GoodsDonSkuRenewView.class, MapUtils.builder().field(GoodsDonSkuRenewView::getSkuId, renewSkuId).build());

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

@@ -253,7 +253,7 @@ public class ClaudeCodeController {
 				claudeCodePackageView.setUpgrades(renewUpgradePackageFrontViews);
 			}
 		}
-		List<GoodsDonSkuView> goodsDonSkuViews = beanSearcher.searchAll(GoodsDonSkuView.class, MapUtils.builder().field(GoodsDonSkuView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID).field(GoodsDonSkuView::getUserOwns).op(Operator.IsNull).build());
+		List<GoodsDonSkuView> 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);

+ 80 - 0
netflix-web/src/main/java/com/cyksj/web/controller/codex/CodexController.java

@@ -0,0 +1,80 @@
+package com.cyksj.web.controller.codex;
+
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.request.UpdateDailyLimitRequest;
+import com.cyksj.model.response.claudecode.ClaudeCodeResp;
+import com.cyksj.model.views.CodexUserInfoView;
+import com.cyksj.service.codex.CodexService;
+import com.cyksj.web.util.StpUserUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * server/codex
+ * 项目名: yhlxj11111111
+ * 文件名: CodexController
+ * 创建者: Claude
+ * 创建时间:2025/9/15
+ */
+@Slf4j
+@RestController
+@RequestMapping("/applet/codex")
+@RequiredArgsConstructor
+public class CodexController {
+    
+    private final CodexService codexService;
+    
+    /**
+     * 获取用户Codex信息
+     */
+    @GetMapping("/info")
+    public Result<CodexUserInfoView> getCodexUserInfo() {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        CodexUserInfoView codexUserInfoView = codexService.getCodexUserInfo(userId);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(codexUserInfoView);
+    }
+
+    /**
+     * 获取用户OpenAI使用统计
+     * period 时间周期(today/7d/30d)
+     */
+    @GetMapping("/users/usage")
+    public ClaudeCodeResp getUserOpenAIUsage(@RequestParam(defaultValue = "today") String period) {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        ClaudeCodeResp claudeCodeResp = codexService.getUserOpenAIUsage(userId, period);
+        return claudeCodeResp;
+    }
+    
+    /**
+     * 更新用户OpenAI每日美金限额
+     */
+    @PostMapping("/users/daily-limit")
+    public Result<ClaudeCodeResp> updateUserOpenAIDailyLimit(@RequestBody UpdateDailyLimitRequest request) {
+        // 验证请求用户是否为当前登录用户或有权限访问
+        long userId = StpUserUtil.getLoginIdAsLong();
+        ClaudeCodeResp response = codexService.updateUserOpenAIDailyLimit(userId, request);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(response);
+    }
+    
+    /**
+     * 用户OpenAI控制台数据 (Java系统专用接口)
+     */
+    @GetMapping("/openai/users/dashboard")
+    public ClaudeCodeResp getJavaUserDashboard() {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        ClaudeCodeResp dashboard = codexService.getJavaUserDashboard(userId);
+        return dashboard;
+    }
+
+    /**
+     * 用户OpenAI图表+明细联动 (Java系统专用接口)
+     */
+    @GetMapping("/openai/users/analytics")
+    public ClaudeCodeResp getJavaUserAnalytics(String start, String end, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit, @RequestParam(defaultValue = "desc") String order) {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        ClaudeCodeResp analytics = codexService.getJavaUserAnalytics(userId, start, end, page, limit, order);
+        return analytics;
+    }
+}