Sfoglia il codice sorgente

claude code时间倍率;非渠道推广展示推荐优惠券折扣

zoujiajian 1 anno fa
parent
commit
1a435dc1ee

+ 15 - 0
netflix-dao/src/main/java/com/cyksj/model/request/CreditResetReq.java

@@ -0,0 +1,15 @@
+package com.cyksj.model.request;
+
+import lombok.Data;
+
+import javax.validation.constraints.Size;
+
+/**
+ * 积分重置请求参数
+ */
+@Data
+public class CreditResetReq {
+
+    @Size(max = 500, message = "重置说明最多500个字符")
+    private String description;
+}

+ 39 - 0
netflix-dao/src/main/java/com/cyksj/model/request/TimeRateConfigReq.java

@@ -0,0 +1,39 @@
+package com.cyksj.model.request;
+
+import lombok.Data;
+
+import javax.validation.constraints.*;
+
+/**
+ * 时间倍率配置请求参数
+ */
+@Data
+public class TimeRateConfigReq {
+
+    @NotBlank(message = "配置名称不能为空")
+    @Size(min = 1, max = 100, message = "配置名称长度必须在1-100字符之间")
+    private String name;
+
+    @NotNull(message = "开始小时不能为空")
+    @Min(value = 0, message = "开始小时必须在0-23之间")
+    @Max(value = 23, message = "开始小时必须在0-23之间")
+    private Integer startHour;
+
+    @NotNull(message = "结束小时不能为空")
+    @Min(value = 0, message = "结束小时必须在0-23之间")
+    @Max(value = 23, message = "结束小时必须在0-23之间")
+    private Integer endHour;
+
+    @NotNull(message = "倍率系数不能为空")
+    @DecimalMin(value = "0.001", message = "倍率系数必须在0.001-10.000之间")
+    @DecimalMax(value = "10.000", message = "倍率系数必须在0.001-10.000之间")
+    private Double multiplier;
+
+    @Size(max = 200, message = "配置描述最多200个字符")
+    private String description;
+
+    private Boolean isActive = true;
+
+    @Min(value = 0, message = "优先级必须大于等于0")
+    private Integer priority = 0;
+}

+ 34 - 0
netflix-dao/src/main/java/com/cyksj/model/response/CreditResetResp.java

@@ -0,0 +1,34 @@
+package com.cyksj.model.response;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 积分重置响应
+ */
+@Data
+public class CreditResetResp {
+
+    private Long userId;
+    
+    private String description;
+    
+    private CreditStatus beforeReset;
+    
+    private CreditStatus afterReset;
+    
+    private Long creditLimit;
+    
+    private Long restoredCredits;
+    
+    private LocalDateTime resetAt;
+    
+    private String operation = "reset_completed";
+
+    @Data
+    public static class CreditStatus {
+        private Long usedCredits;
+        private Long availableCredits;
+    }
+}

+ 59 - 0
netflix-dao/src/main/java/com/cyksj/model/response/TimeRateConfigResp.java

@@ -0,0 +1,59 @@
+package com.cyksj.model.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 时间倍率配置响应
+ */
+@Data
+public class TimeRateConfigResp {
+
+    private Long id;
+
+    /**
+     * 配置名称
+     */
+    private String name;
+
+    /**
+     * 开始小时(0-23)
+     */
+    @JsonProperty("start_hour")
+    private Integer startHour;
+
+    /**
+     * 结束小时(0-23)
+     */
+    @JsonProperty("end_hour")
+    private Integer endHour;
+
+    /**
+     * 倍率系数(0.001-10.000)
+     */
+    private Double multiplier;
+
+    /**
+     * 配置描述
+     */
+    private String description;
+
+    /**
+     * 是否启用
+     */
+    @JsonProperty("is_active")
+    private Boolean isActive;
+
+    /**
+     * 优先级(数值越大优先级越高
+     */
+    private Integer priority;
+
+    @JsonProperty("created_at")
+    private Date createdAt;
+
+    @JsonProperty("update_at")
+    private Date updatedAt;
+}

+ 16 - 0
netflix-dao/src/main/java/com/cyksj/model/response/TimeRateConfigsResp.java

@@ -0,0 +1,16 @@
+package com.cyksj.model.response;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 时间倍率配置列表响应
+ */
+@Data
+public class TimeRateConfigsResp {
+
+    private List<TimeRateConfigResp> configs;
+    
+    private Long total;
+}

+ 24 - 0
netflix-dao/src/main/java/com/cyksj/model/response/TimeRateCurrentResp.java

@@ -0,0 +1,24 @@
+package com.cyksj.model.response;
+
+import lombok.Data;
+
+/**
+ * 当前时间倍率响应
+ */
+@Data
+public class TimeRateCurrentResp {
+
+    /**
+     * 当前小时(0-23)
+     */
+    private Integer currentHour;
+
+    /**
+     * 当前倍率系数
+     */
+    private Double multiplier;
+    
+    private String configName;
+    
+    private String description;
+}

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

@@ -5,7 +5,9 @@ import com.cyksj.model.entity.GoodsDonSku;
 import com.cyksj.model.entity.GroupsRelation;
 import com.cyksj.model.request.ClaudeCodeApiKeysReq;
 import com.cyksj.model.request.ClaudeCodeDelReq;
-import com.cyksj.model.response.ClaudeCodePointsHistoryResp;
+import com.cyksj.model.request.CreditResetReq;
+import com.cyksj.model.request.TimeRateConfigReq;
+import com.cyksj.model.response.*;
 import com.cyksj.model.response.claudecode.ClaudeCodeResp;
 import com.cyksj.model.views.ClaudeCodeUserApiKeysView;
 import com.cyksj.model.views.ClaudeCodeUserInfoView;
@@ -66,4 +68,34 @@ public interface ClaudeCodeService {
 	 * 处理续费升级 TODO
 	 */
 	void handleClaudeCodeUserPackageSku(GroupsRelation relation, GoodsDonSku sku, Long orderId);
+
+	/**
+	 * 获取当前时间的积分倍率配置信息
+	 */
+	TimeRateCurrentResp getCurrentTimeRate();
+
+	/**
+	 * 获取系统中所有的时间倍率配置列表(包括已启用和已禁用的配置)
+	 */
+	TimeRateConfigsResp getAllConfigs();
+
+	/**
+	 * 创建新的时间倍率配置。系统支持跨天配置(如23点到6点)
+	 */
+	TimeRateConfigResp createConfig(TimeRateConfigReq req);
+
+	/**
+	 * 更新指定ID的时间倍率配置
+	 */
+	TimeRateConfigResp updateConfig(Long id, TimeRateConfigReq req);
+
+	/**
+	 * 删除指定ID的时间倍率配置。删除后该时间段将使用默认倍率1.0
+	 */
+	void deleteConfig(Long id);
+
+	/**
+	 * 将指定用户的积分重置为满积分状态(used_credits设为0)
+	 */
+	CreditResetResp resetUserCredits(Long userId, CreditResetReq req);
 }

+ 109 - 1
netflix-service/src/main/java/com/cyksj/service/claude/impl/ClaudeCodeServiceImpl.java

@@ -18,7 +18,9 @@ import com.cyksj.model.entity.*;
 import com.cyksj.model.manage.views.GroupsRelationView;
 import com.cyksj.model.request.ClaudeCodeApiKeysReq;
 import com.cyksj.model.request.ClaudeCodeDelReq;
-import com.cyksj.model.response.ClaudeCodePointsHistoryResp;
+import com.cyksj.model.request.CreditResetReq;
+import com.cyksj.model.request.TimeRateConfigReq;
+import com.cyksj.model.response.*;
 import com.cyksj.model.response.claudecode.ClaudeCodeResp;
 import com.cyksj.model.views.ClaudeCodeUserApiKeysView;
 import com.cyksj.model.views.ClaudeCodeUserInfoView;
@@ -410,6 +412,112 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 		generateOrUpdateClaudeCodeUserInfo(orderId, relation.getId(), relation.getUserId(), sku, 2);
 	}
 
+	@Override
+	public TimeRateCurrentResp getCurrentTimeRate() {
+		try {
+			String url = CLAUDE_CODE_API_PREFIX + "api/time-multiplier/current";
+			ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
+			return Jsons.parseObject(claudeCodeResp.getData(), TimeRateCurrentResp.class);
+		} catch (Exception e) {
+			log.error("获取当前时间倍率失败", e);
+			throw BusinessRuntimeException.getInstance("获取当前时间倍率失败");
+		}
+	}
+
+	@Override
+	public TimeRateConfigsResp getAllConfigs() {
+		try {
+			String url = CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs";
+			ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
+			return Jsons.parseObject(claudeCodeResp.getData(), TimeRateConfigsResp.class);
+		} catch (Exception e) {
+			log.error("获取时间倍率配置列表失败", e);
+			throw BusinessRuntimeException.getInstance("获取时间倍率配置列表失败");
+		}
+	}
+
+	@Override
+	public TimeRateConfigResp createConfig(TimeRateConfigReq req) {
+		try {
+			validateTimeRange(req.getStartHour(), req.getEndHour());
+
+			String url = CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs";
+			ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(url, Jsons.toJson(req));
+			return Jsons.parseObject(claudeCodeResp.getData(), TimeRateConfigResp.class);
+		} catch (Exception e) {
+			log.error("创建时间倍率配置失败", e);
+			throw BusinessRuntimeException.getInstance("创建时间倍率配置失败");
+		}
+	}
+
+	@Override
+	public TimeRateConfigResp updateConfig(Long id, TimeRateConfigReq req) {
+		try {
+			if (id <= 0) {
+				throw BusinessRuntimeException.getInstance("配置ID无效");
+			}
+			validateTimeRange(req.getStartHour(), req.getEndHour());
+
+			String url = CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs/" + id;
+			ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(url, Jsons.toJson(req));
+			return Jsons.parseObject(claudeCodeResp.getData(), TimeRateConfigResp.class);
+		} catch (Exception e) {
+			log.error("更新时间倍率配置失败", e);
+			throw BusinessRuntimeException.getInstance("更新时间倍率配置失败");
+		}
+	}
+
+	@Override
+	public void deleteConfig(Long id) {
+		try {
+			if (id <= 0) {
+				throw BusinessRuntimeException.getInstance("配置ID无效");
+			}
+
+			String url = CLAUDE_CODE_API_PREFIX + "api/time-multiplier/configs/" + id;
+			ClaudeCodeResp claudeCodeResp = executeClaudeCodeDeletedApi(url);
+			if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
+				log.error("删除时间倍率配置失败:{}", claudeCodeResp.getMessage());
+				throw BusinessRuntimeException.getInstance("删除时间倍率配置失败");
+			}
+		} catch (Exception e) {
+			log.error("删除时间倍率配置失败", e);
+			throw BusinessRuntimeException.getInstance("删除时间倍率配置失败");
+		}
+	}
+
+	@Override
+	public CreditResetResp resetUserCredits(Long userId, CreditResetReq req) {
+		try {
+			if (userId <= 0) {
+				throw BusinessRuntimeException.getInstance("用户ID无效");
+			}
+
+			String url = CLAUDE_CODE_API_PREFIX + "api/users/" + userId + "/credits/reset";
+			String body = req != null ? Jsons.toJson(req) : "{}";
+			ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(url, body);
+			return Jsons.parseObject(claudeCodeResp.getData(), CreditResetResp.class);
+		} catch (Exception e) {
+			log.error("重置用户积分失败", e);
+			throw BusinessRuntimeException.getInstance("重置用户积分失败");
+		}
+	}
+
+	/**
+	 * 验证时间范围
+	 */
+	private void validateTimeRange(Integer startHour, Integer endHour) {
+		if (startHour == null || endHour == null) {
+			throw BusinessRuntimeException.getInstance("开始和结束小时不能为空");
+		}
+		if (startHour < 0 || startHour > 23) {
+			throw BusinessRuntimeException.getInstance("开始小时必须在0-23之间");
+		}
+		if (endHour < 0 || endHour > 23) {
+			throw BusinessRuntimeException.getInstance("结束小时必须在0-23之间");
+		}
+	}
+
 	/**
 	 * 获取claude code用户id
 	 */

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

@@ -19,7 +19,9 @@ import com.cyksj.model.entity.*;
 import com.cyksj.model.manage.views.OrderDonView;
 import com.cyksj.model.request.ClaudeCodeApiKeysReq;
 import com.cyksj.model.request.ClaudeCodeDelReq;
-import com.cyksj.model.response.ClaudeCodePointsHistoryResp;
+import com.cyksj.model.request.CreditResetReq;
+import com.cyksj.model.request.TimeRateConfigReq;
+import com.cyksj.model.response.*;
 import com.cyksj.model.response.claudecode.ClaudeCodeResp;
 import com.cyksj.model.views.*;
 import com.cyksj.service.claude.AnnouncementService;
@@ -396,6 +398,7 @@ public class ClaudeCodeController {
 
 	public void setClaudeCodeSkuCouponInfo(String dsCode, Long userId, List<GoodsDonSkuView> goodsDonSkuViews) {
 		//渠道推广
+		Coupon coupon = null;
 		if (dsCode != null) {
 			UserSharedDto popularizeDto = userService.getUserShredDtoByDsCode(dsCode);
 			if (popularizeDto != null) {
@@ -407,41 +410,100 @@ public class ClaudeCodeController {
 							.eq(CouponDistributePopularize::getCouponId, Constant.DISTRIBUTE_CLAUDE_CODE_COUPON_ID)
 							.eq(CouponDistributePopularize::getDeleted, Boolean.TRUE));
 					//已配置使用渠道优惠码 未配置使用推荐优惠券
-					Coupon coupon = couponMapper.selectById(selectCount > 0 ? Constant.DISTRIBUTE_CLAUDE_CODE_COUPON_ID : Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID);
-					if (coupon == null) {
-						return;
-					}
-					if (coupon.getId().intValue() == Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID.intValue()) {
-						int isTj = couponRecommendMapper.checkPresentRecommendByCouponId(Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID);
-						if (isTj == 0) {
-							return;
-						}
-					}
-					List<Long> 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 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;
-						}
-					}
-					goodsDonSkuViews.forEach(sku -> {
-						if (couponSkuIds.contains(sku.getSkuId())) {
-							if (coupon.getType() == Coupon.Type.discount) {
-								sku.setDiscount(coupon.getDiscount());
-								sku.setDiscountPrice(sku.getMoney().multiply(coupon.getDiscount()));
-							} else {
-								sku.setReduceMoney(coupon.getReduceMoney());
-							}
-						}
-					});
+					coupon = couponMapper.selectById(selectCount > 0 ? Constant.DISTRIBUTE_CLAUDE_CODE_COUPON_ID : Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID);
+				}
+			}
+		}
+		//非推广链接
+		if (coupon == null) {
+			coupon = couponMapper.selectById(Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID);
+		}
+		if (coupon == null) {
+			return;
+		}
+		if (coupon.getId().intValue() == Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID.intValue()) {
+			int isTj = couponRecommendMapper.checkPresentRecommendByCouponId(Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID);
+			if (isTj == 0) {
+				return;
+			}
+		}
+		List<Long> 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 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())) {
+				if (coupon.getType() == Coupon.Type.discount) {
+					sku.setDiscount(coupon.getDiscount());
+					sku.setDiscountPrice(sku.getMoney().multiply(coupon.getDiscount()));
+				} else {
+					sku.setReduceMoney(coupon.getReduceMoney());
 				}
 			}
 		}
 	}
+
+	//======================================================================时间倍率===============================================================================
+	/**
+	 * 获取当前时间的积分倍率配置信息
+	 */
+	@GetMapping("/timeRate/current")
+	public Result<TimeRateCurrentResp> getCurrentTimeRate() {
+		TimeRateCurrentResp resp = claudeCodeService.getCurrentTimeRate();
+		return GatewayResponse.SUCCESS.newBuilder().toResult(resp);
+	}
+
+	/**
+	 * 获取系统中所有的时间倍率配置列表(包括已启用和已禁用的配置)
+	 */
+	@GetMapping("/timeRate/configs")
+	public Result<TimeRateConfigsResp> getAllConfigs() {
+		TimeRateConfigsResp resp = claudeCodeService.getAllConfigs();
+		return GatewayResponse.SUCCESS.newBuilder().toResult(resp);
+	}
+
+	/**
+	 * 创建新的时间倍率配置。系统支持跨天配置(如23点到6点)
+	 */
+	@PostMapping("/timeRate/configs")
+	public Result<TimeRateConfigResp> createConfig(@RequestBody @Validated TimeRateConfigReq req) {
+		TimeRateConfigResp resp = claudeCodeService.createConfig(req);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(resp);
+	}
+
+	/**
+	 * 更新指定ID的时间倍率配置
+	 */
+	@PutMapping("/timeRate/configs/{id}")
+	public Result<TimeRateConfigResp> updateConfig(@PathVariable Long id, @RequestBody @Validated TimeRateConfigReq req) {
+		TimeRateConfigResp resp = claudeCodeService.updateConfig(id, req);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(resp);
+	}
+
+	/**
+	 * 删除指定ID的时间倍率配置。删除后该时间段将使用默认倍率1.0
+	 */
+	@DeleteMapping("/timeRate/configs/{id}")
+	public Result<String> deleteConfig(@PathVariable Long id) {
+		claudeCodeService.deleteConfig(id);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("Time multiplier config deleted successfully");
+	}
+
+	/**
+	 * 将指定用户的积分重置为满积分状态(used_credits设为0)
+	 */
+	@PostMapping("/users/{userId}/credits/reset")
+	public Result<CreditResetResp> resetUserCredits(@PathVariable Long userId, @RequestBody(required = false) CreditResetReq req) {
+		CreditResetResp resp = claudeCodeService.resetUserCredits(userId, req);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(resp);
+	}
 }