zoujiajian 11 mēneši atpakaļ
vecāks
revīzija
a8a175af4b

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

@@ -658,4 +658,9 @@ public interface Constant {
 	 * claude code日卡推广活动
 	 */
 	Long DISTRIBUTE_CLAUDE_CODE_TRIAL_SKU_ID = 732L;
+
+	/**
+	 * nano 商品id
+	 */
+	Long NANO_GOODS_ID = 111111L;
 }

+ 10 - 0
netflix-dao/src/main/java/com/cyksj/mapper/NanoFileRecordMapper.java

@@ -0,0 +1,10 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.NanoFileRecord;
+
+/**
+ * 文件记录表 Mapper
+ */
+public interface NanoFileRecordMapper extends BaseMapper<NanoFileRecord> {
+}

+ 10 - 0
netflix-dao/src/main/java/com/cyksj/mapper/NanoImageHistoryMapper.java

@@ -0,0 +1,10 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.NanoImageHistory;
+
+/**
+ * 图像生成历史记录表 Mapper
+ */
+public interface NanoImageHistoryMapper extends BaseMapper<NanoImageHistory> {
+}

+ 10 - 0
netflix-dao/src/main/java/com/cyksj/mapper/NanoRefundFailureRecordMapper.java

@@ -0,0 +1,10 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.NanoRefundFailureRecord;
+
+/**
+ * 退还失败记录表 Mapper
+ */
+public interface NanoRefundFailureRecordMapper extends BaseMapper<NanoRefundFailureRecord> {
+}

+ 10 - 0
netflix-dao/src/main/java/com/cyksj/mapper/NanoUserQuotaMapper.java

@@ -0,0 +1,10 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.NanoUserQuota;
+
+/**
+ * 用户固定配额管理表 Mapper
+ */
+public interface NanoUserQuotaMapper extends BaseMapper<NanoUserQuota> {
+}

+ 59 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/NanoFileRecord.java

@@ -0,0 +1,59 @@
+package com.cyksj.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * 文件记录表
+ */
+@Getter
+@Setter
+@TableName("nano_file_record")
+public class NanoFileRecord extends BaseEntity {
+
+    /**
+     * 用户token
+     */
+    private Long userToken;
+
+    /**
+     * 文件MD5哈希
+     */
+    private String fileMd5;
+
+    /**
+     * 文件名
+     */
+    private String fileName;
+
+    /**
+     * 访问URL
+     */
+    private String url;
+
+    /**
+     * CDN访问URL
+     */
+    private String cdnUrl;
+
+    /**
+     * OSS内部URL
+     */
+    private String ossUrl;
+
+    /**
+     * 原始文件名
+     */
+    private String originalFileName;
+
+    /**
+     * 软删除时间
+     */
+    @TableLogic
+    private Date deletedAt;
+}

+ 89 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/NanoImageHistory.java

@@ -0,0 +1,89 @@
+package com.cyksj.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * 图像生成历史记录表
+ */
+@Getter
+@Setter
+@TableName("nano_image_history")
+public class NanoImageHistory extends BaseEntity {
+
+    /**
+     * 任务ID
+     */
+    private String taskId;
+
+    /**
+     * 用户令牌
+     */
+    private String usertoken;
+
+    /**
+     * 用户ID(可选关联)
+     */
+    private Integer userId;
+
+    /**
+     * 生成提示词
+     */
+    private String prompt;
+
+    /**
+     * 图像尺寸
+     */
+    private String size;
+
+    /**
+     * 图像数量
+     */
+    private Integer num;
+
+    /**
+     * 任务状态
+     */
+    private String status;
+
+    /**
+     * JSON格式存储源图片URL(编辑模式)
+     */
+    private String imgs;
+
+    /**
+     * JSON格式存储结果图片URL
+     */
+    private String reulstImgs;
+
+    /**
+     * 错误信息
+     */
+    private String errMsg;
+
+    /**
+     * 任务类型:常规,主任务,子任务
+     */
+    private String taskType;
+
+    /**
+     * 主任务ID(子任务关联用)
+     */
+    private String masterTaskId;
+
+    /**
+     * 是否收藏
+     */
+    private Boolean isstarred;
+
+    /**
+     * 软删除时间
+     */
+    @TableLogic
+    private Date deletedAt;
+}

+ 79 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/NanoRefundFailureRecord.java

@@ -0,0 +1,79 @@
+package com.cyksj.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * 退还失败记录表
+ */
+@Getter
+@Setter
+@TableName("nano_refund_failure_record")
+public class NanoRefundFailureRecord extends BaseEntity {
+
+    /**
+     * 任务ID
+     */
+    private String taskId;
+
+    /**
+     * 用户token
+     */
+    private String userToken;
+
+    /**
+     * 需要退还的次数
+     */
+    private Integer refundCount;
+
+    /**
+     * 退还类型(full_failure/partial_failure/timeout)
+     */
+    private String refundType;
+
+    /**
+     * 失败原因
+     */
+    private String failureReason;
+
+    /**
+     * 重试次数
+     */
+    private Integer retryCount;
+
+    /**
+     * 最大重试次数
+     */
+    private Integer maxRetryCount;
+
+    /**
+     * 状态(pending/processing/success/failed)
+     */
+    private String status;
+
+    /**
+     * 最后重试时间
+     */
+    private Date lastRetryTime;
+
+    /**
+     * 下次重试时间
+     */
+    private Date nextRetryTime;
+
+    /**
+     * 正在处理的实例ID
+     */
+    private String processingInstance;
+
+    /**
+     * 软删除时间
+     */
+    @TableLogic
+    private Date deletedAt;
+}

+ 66 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/NanoUserQuota.java

@@ -0,0 +1,66 @@
+package com.cyksj.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * 用户固定配额管理表(包含完整用户信息)
+ */
+@Getter
+@Setter
+@TableName("nano_user_quota")
+public class NanoUserQuota extends BaseEntity {
+
+    /**
+     * 用户token
+     */
+    private String userToken;
+
+    /**
+     * 用户唯一标识
+     */
+    private Long uid;
+
+    /**
+     * 用户昵称
+     */
+    private String nickname;
+
+    /**
+     * 用户头像URL
+     */
+    private String avatar;
+
+    /**
+     * 套餐类型:1基础版,2标准版,3专业版,4银河大会员
+     */
+    private Integer packageType;
+
+    /**
+     * 总配额次数
+     */
+    private Integer totalQuota;
+
+    /**
+     * 已使用配额次数
+     */
+    private Integer usedQuota;
+
+    /**
+     * 剩余配额次数
+     */
+    private Integer remainingQuota;
+
+    /**
+     * 购买日期
+     */
+    private Date purchaseDate;
+
+    /**
+     * 最后使用日期
+     */
+    private Date lastUsedDate;
+}

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

@@ -6,6 +6,7 @@ import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUnit;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.lang.UUID;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.RandomUtil;
 import cn.hutool.core.util.StrUtil;
@@ -390,6 +391,8 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 
 	private final ClaudeCodeUserMapper claudeCodeUserMapper;
 
+	private final NanoUserQuotaMapper nanoUserQuotaMapper;
+
 	private final List<String> noChekGoodsTemp = List.of("Apple One", "Youtube", "Spotify");
 
 	@Transactional(rollbackFor = Throwable.class)
@@ -1878,10 +1881,31 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 					} catch (Exception e) {
 					}
 				}
+
+
+				if (order.getOrderType() == 1 && order.getGoodsId() == Constant.NANO_GOODS_ID) {
+					//创建nano user
+					createNanoUser(order.getUserId(), goodsDon);
+				}
 			}
 		}
 	}
 
+	private void createNanoUser(Long userId, GoodsDon goodsDon) {
+		try {
+			User user = userMapper.selectById(userId);
+			NanoUserQuota nanoUserQuota = new NanoUserQuota();
+			nanoUserQuota.setUid(userId);
+			nanoUserQuota.setUserToken(UUID.randomUUID().toString());
+			nanoUserQuota.setNickname(user.getNickname());
+			nanoUserQuota.setAvatar(user.getHeadimgurl());
+			nanoUserQuota.setPurchaseDate(DateTime.now());
+			nanoUserQuotaMapper.insert(nanoUserQuota);
+		} catch (DuplicateKeyException e) {
+
+		}
+	}
+
 	/**
 	 * 分销待发积分记录
 	 */

+ 25 - 0
netflix-service/src/main/java/com/cyksj/service/quota/NanoUserQuotaService.java

@@ -0,0 +1,25 @@
+package com.cyksj.service.quota;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.cyksj.model.entity.NanoUserQuota;
+
+/**
+ * 用户固定配额管理服务接口
+ */
+public interface NanoUserQuotaService extends IService<NanoUserQuota> {
+
+    /**
+     * 根据用户token获取配额信息
+     */
+    NanoUserQuota getByUserToken(String userToken);
+
+    /**
+     * 根据用户ID获取配额信息
+     */
+    NanoUserQuota getByUid(Long uid);
+
+    /**
+     * 更新配额使用情况
+     */
+    void updateUsage(Long id, Integer usedQuota);
+}

+ 67 - 0
netflix-service/src/main/java/com/cyksj/service/quota/impl/NanoUserQuotaServiceImpl.java

@@ -0,0 +1,67 @@
+package com.cyksj.service.quota.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.mapper.NanoUserQuotaMapper;
+import com.cyksj.model.entity.NanoUserQuota;
+import com.cyksj.service.quota.NanoUserQuotaService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+
+/**
+ * 用户固定配额管理服务实现类
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class NanoUserQuotaServiceImpl extends ServiceImpl<NanoUserQuotaMapper, NanoUserQuota> implements NanoUserQuotaService {
+
+    @Override
+    public NanoUserQuota getByUserToken(String userToken) {
+        if (userToken == null || userToken.trim().isEmpty()) {
+            throw new BusinessRuntimeException("用户token不能为空");
+        }
+        return this.getOne(new LambdaQueryWrapper<NanoUserQuota>()
+                .eq(NanoUserQuota::getUserToken, userToken)
+                .last("LIMIT 1"));
+    }
+
+    @Override
+    public NanoUserQuota getByUid(Long uid) {
+        if (uid == null) {
+            throw new BusinessRuntimeException("用户ID不能为空");
+        }
+        return this.getOne(new LambdaQueryWrapper<NanoUserQuota>()
+                .eq(NanoUserQuota::getUid, uid)
+                .last("LIMIT 1"));
+    }
+
+    @Override
+    public void updateUsage(Long id, Integer usedQuota) {
+        if (id == null) {
+            throw new BusinessRuntimeException("配额ID不能为空");
+        }
+        if (usedQuota == null || usedQuota < 0) {
+            throw new BusinessRuntimeException("使用配额数量不能为空且必须大于等于0");
+        }
+        
+        NanoUserQuota quota = this.getById(id);
+        if (quota == null) {
+            throw new BusinessRuntimeException("配额记录不存在");
+        }
+        
+        if (usedQuota > quota.getTotalQuota()) {
+            throw new BusinessRuntimeException("使用配额不能超过总配额");
+        }
+        
+        quota.setUsedQuota(usedQuota);
+        quota.setRemainingQuota(quota.getTotalQuota() - usedQuota);
+        quota.setLastUsedDate(new Date());
+        
+        this.updateById(quota);
+    }
+}

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

@@ -45,6 +45,8 @@ public class GroupRelationClearServiceImpl implements GroupRelationClearService
 
 	private final OAuthTokenMapper oAuthTokenMapper;
 
+	private final NanoUserQuotaMapper nanoUserQuotaMapper;
+
 	@Override
 	public void clearTicket(GroupsRelation relation, UserTicketClearedRecord.Source source) {
 		Long relationId = relation.getId();
@@ -145,6 +147,10 @@ public class GroupRelationClearServiceImpl implements GroupRelationClearService
 					//清除claude code tokens
 					oAuthTokenMapper.delete(Wrappers.lambdaQuery(OAuthToken.class).eq(OAuthToken::getUserId, userId));
 				}
+				//nano
+				if (groupsRelation.getGoodsId() != null && groupsRelation.getGoodsId() == Constant.NANO_GOODS_ID) {
+					nanoUserQuotaMapper.delete(Wrappers.lambdaQuery(NanoUserQuota.class).eq(NanoUserQuota::getUid, userId));
+				}
 			}
 		}
 	}

+ 105 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/NanoUserQuotaController.java

@@ -0,0 +1,105 @@
+package com.cyksj.web.controller.manage;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.cyksj.common.annotation.Log;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.BusinessType;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.entity.NanoUserQuota;
+import com.cyksj.service.quota.NanoUserQuotaService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 用户固定配额管理接口
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/manage/nanoUserQuota")
+public class NanoUserQuotaController {
+
+    private final HttpServletRequest request;
+    private final BeanSearcher beanSearcher;
+    private final NanoUserQuotaService nanoUserQuotaService;
+
+    /**
+     * 分页查询用户配额列表
+     */
+    @GetMapping("/list")
+    public Result<SearchResult<NanoUserQuota>> list() {
+        SearchResult<NanoUserQuota> search = beanSearcher.search(NanoUserQuota.class, 
+            MapUtils.flatBuilder(request.getParameterMap()).build());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+    /**
+     * 根据ID获取用户配额详情
+     */
+    @GetMapping("/{id}")
+    public Result<NanoUserQuota> getById(@PathVariable Long id) {
+        NanoUserQuota quota = nanoUserQuotaService.getById(id);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(quota);
+    }
+
+    /**
+     * 新增用户配额
+     */
+    @PostMapping
+    public Result<String> add(@RequestBody @Validated NanoUserQuota quota) {
+        nanoUserQuotaService.save(quota);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 修改用户配额
+     */
+    @PutMapping
+    public Result<String> update(@RequestBody @Validated NanoUserQuota quota) {
+        nanoUserQuotaService.updateById(quota);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 删除用户配额
+     */
+    @DeleteMapping("/{id}")
+    public Result<String> delete(@PathVariable Long id) {
+        nanoUserQuotaService.removeById(id);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 根据用户token获取配额信息
+     */
+    @GetMapping("/token/{userToken}")
+    public Result<NanoUserQuota> getByUserToken(@PathVariable String userToken) {
+        NanoUserQuota quota = nanoUserQuotaService.getByUserToken(userToken);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(quota);
+    }
+
+    /**
+     * 根据用户ID获取配额信息
+     */
+    @GetMapping("/uid/{uid}")
+    public Result<NanoUserQuota> getByUid(@PathVariable Long uid) {
+        NanoUserQuota quota = nanoUserQuotaService.getByUid(uid);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(quota);
+    }
+
+    /**
+     * 更新配额使用情况
+     */
+    @PutMapping("/usage/{id}")
+    public Result<String> updateUsage(@PathVariable Long id, @RequestParam Integer usedQuota) {
+        nanoUserQuotaService.updateUsage(id, usedQuota);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+}