zoujiajian 11 місяців тому
батько
коміт
655f7a7c73
26 змінених файлів з 719 додано та 95 видалено
  1. 20 0
      netflix-common/src/main/java/com/cyksj/common/constant/Constant.java
  2. 0 13
      netflix-dao/src/main/java/com/cyksj/mapper/claudecode/ClaudeCodeUserPopularizeTrialTicketRecordMapper.java
  3. 13 0
      netflix-dao/src/main/java/com/cyksj/mapper/claudecode/PopularizeTrialTicketRecordMapper.java
  4. 10 0
      netflix-dao/src/main/java/com/cyksj/mapper/nano/NanoUserTrialReceiveRecordMapper.java
  5. 5 0
      netflix-dao/src/main/java/com/cyksj/model/dto/AuthRedirect.java
  6. 5 0
      netflix-dao/src/main/java/com/cyksj/model/dto/GzhBodyDto.java
  7. 20 0
      netflix-dao/src/main/java/com/cyksj/model/entity/NanoUserTrialReceiveRecord.java
  8. 4 2
      netflix-dao/src/main/java/com/cyksj/model/entity/PopularizeTrialTicketRecord.java
  9. 5 0
      netflix-dao/src/main/java/com/cyksj/model/request/LoginReq.java
  10. 3 3
      netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuView.java
  11. 23 0
      netflix-dao/src/main/java/com/cyksj/model/views/NanoPackageView.java
  12. 68 0
      netflix-dao/src/main/java/com/cyksj/model/views/NanoUserQuotaInfoView.java
  13. 1 0
      netflix-service/src/main/java/com/cyksj/redis/RedisService.java
  14. 103 25
      netflix-service/src/main/java/com/cyksj/service/gzh/factory/EventMsgService.java
  15. 12 0
      netflix-service/src/main/java/com/cyksj/service/nano/NanoService.java
  16. 83 0
      netflix-service/src/main/java/com/cyksj/service/nano/impl/NanoServiceImpl.java
  17. 17 0
      netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java
  18. 1 1
      netflix-service/src/main/java/com/cyksj/service/user/UserService.java
  19. 137 41
      netflix-service/src/main/java/com/cyksj/service/user/impl/UserServiceImpl.java
  20. 1 1
      netflix-service/src/main/java/com/cyksj/service/vip/impl/VipFrontServiceImpl.java
  21. 2 2
      netflix-web/src/main/java/com/cyksj/web/controller/claude/ClaudeCodeController.java
  22. 5 1
      netflix-web/src/main/java/com/cyksj/web/controller/cps/CpsPopularizeController.java
  23. 1 1
      netflix-web/src/main/java/com/cyksj/web/controller/mirror/MirrorController.java
  24. 173 0
      netflix-web/src/main/java/com/cyksj/web/controller/nano/NanoController.java
  25. 5 3
      netflix-web/src/main/java/com/cyksj/web/controller/user/AuthorizationController.java
  26. 2 2
      netflix-web/src/main/java/com/cyksj/web/controller/user/UserController.java

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

@@ -683,4 +683,24 @@ public interface Constant {
 	 * nano 商品id
 	 */
 	Long NANO_GOODS_ID = 104L;
+
+	/**
+	 * nano 渠道优惠券id
+	 */
+	Long DISTRIBUTE_NANO_COUPON_ID = 0L;
+
+	/**
+	 * nano 渠道体验卡规格id
+	 */
+	Long DISTRIBUTE_NANO_TRIAL_SKU_ID = 742L;
+
+	/**
+	 * nano推广标识
+	 */
+	String NANO_DISTRIBUTE_MARK = "nano";
+
+	/**
+	 * 推广claude code分销链接
+	 */
+	String DISTRIBUTE_NANO_URL = "https://nano.yhlxj.com/%s";
 }

+ 0 - 13
netflix-dao/src/main/java/com/cyksj/mapper/claudecode/ClaudeCodeUserPopularizeTrialTicketRecordMapper.java

@@ -1,13 +0,0 @@
-package com.cyksj.mapper.claudecode;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.cyksj.model.entity.ClaudeCodeUserPopularizeTrialTicketRecord;
-
-/**
- * 项目名: yhlxj11111111
- * 文件名: ClaudeCodeUserPopularizeTrialTicketRecordMapper
- * 创建者: JavaZou
- * 创建时间:2025/8/21 14:04
- */
-public interface ClaudeCodeUserPopularizeTrialTicketRecordMapper extends BaseMapper<ClaudeCodeUserPopularizeTrialTicketRecord> {
-}

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/mapper/claudecode/PopularizeTrialTicketRecordMapper.java

@@ -0,0 +1,13 @@
+package com.cyksj.mapper.claudecode;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.PopularizeTrialTicketRecord;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: PopularizeTrialTicketRecordMapper
+ * 创建者: JavaZou
+ * 创建时间:2025/8/21 14:04
+ */
+public interface PopularizeTrialTicketRecordMapper extends BaseMapper<PopularizeTrialTicketRecord> {
+}

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

@@ -0,0 +1,10 @@
+package com.cyksj.mapper.nano;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.NanoUserTrialReceiveRecord;
+
+/**
+ * Nano用户试用体验卡领取记录 Mapper
+ */
+public interface NanoUserTrialReceiveRecordMapper extends BaseMapper<NanoUserTrialReceiveRecord> {
+}

+ 5 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/AuthRedirect.java

@@ -94,4 +94,9 @@ public class AuthRedirect {
      * 是否是cc普通推广
      */
     private Boolean cgTj;
+
+    /**
+     * 是否是nano注册登录
+     */
+    private Boolean isNano = false;
 }

+ 5 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/GzhBodyDto.java

@@ -107,4 +107,9 @@ public class GzhBodyDto {
 	 * 是否是cc普通推广
 	 */
 	private Boolean cgTj;
+
+	/**
+	 * 是否是nano注册登录
+	 */
+	private Boolean isNano = false;
 }

+ 20 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/NanoUserTrialReceiveRecord.java

@@ -0,0 +1,20 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Nano用户试用体验卡领取记录
+ * 项目名: yhlxj11111111
+ * 文件名: NanoUserTrialReceiveRecord
+ * 创建者: Claude Code
+ * 创建时间:2025/9/4
+ */
+@Getter
+@Setter
+public class NanoUserTrialReceiveRecord extends BaseEntity {
+	
+	private Long userId;
+	
+	private Long relationId;
+}

+ 4 - 2
netflix-dao/src/main/java/com/cyksj/model/entity/ClaudeCodeUserPopularizeTrialTicketRecord.java → netflix-dao/src/main/java/com/cyksj/model/entity/PopularizeTrialTicketRecord.java

@@ -5,12 +5,14 @@ import lombok.Setter;
 
 /**
  * 项目名: yhlxj11111111
- * 文件名: ClaudeCodeUserPopularizeTrialTicketRecord
+ * 文件名: PopularizeTrialTicketRecord
  * 创建者: JavaZou
  * 创建时间:2025/8/21 14:03
  */
 @Getter
 @Setter
-public class ClaudeCodeUserPopularizeTrialTicketRecord extends BaseEntity{
+public class PopularizeTrialTicketRecord extends BaseEntity{
+	private Long goodsId;
+
 	private Long userId;
 }

+ 5 - 0
netflix-dao/src/main/java/com/cyksj/model/request/LoginReq.java

@@ -79,4 +79,9 @@ public class LoginReq {
 	 * 是否是cc普通推广
 	 */
 	private Boolean cgTj;
+
+	/**
+	 * 是否是nano注册登录
+	 */
+	private Boolean isNano = false;
 }

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

@@ -81,10 +81,10 @@ public class GoodsDonSkuView {
 	private Integer codeCreditRecovery;
 
 	/**
-	 * cc最高约可使用次数
+	 * nano次数
 	 */
-	@DbField("gdk.highest_usage")
-	private Integer highestUsage;
+	@DbField("gdk.nano_quota")
+	private Integer nanoQuota;
 
 	/**
 	 * 优惠券id

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

@@ -0,0 +1,23 @@
+package com.cyksj.model.views;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+/**
+ * Nano套餐视图
+ * 项目名: yhlxj11111111
+ * 文件名: NanoPackageView
+ * 创建者: Claude Code
+ * 创建时间:2025/9/4
+ */
+@Getter
+@Setter
+public class NanoPackageView {
+
+	/**
+	 * nano规格
+	 */
+	private List<GoodsDonSkuView> skus;
+}

+ 68 - 0
netflix-dao/src/main/java/com/cyksj/model/views/NanoUserQuotaInfoView.java

@@ -0,0 +1,68 @@
+package com.cyksj.model.views;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: NanoUserQuotaInfoView
+ * 创建者: JavaZou
+ * 创建时间:2025/9/4 15:19
+ */
+@Getter
+@Setter
+public class NanoUserQuotaInfoView {
+
+	/**
+	 * 用户token
+	 */
+	private String userToken;
+
+	/**
+	 * 用户唯一标识
+	 */
+	private Long uid;
+
+	private Long relationId;
+
+	private Long skuId;
+
+	/**
+	 * 用户昵称
+	 */
+	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 lastUsedDate;
+
+	private Boolean isVip;
+}

+ 1 - 0
netflix-service/src/main/java/com/cyksj/redis/RedisService.java

@@ -860,6 +860,7 @@ public class RedisService {
         GPT_RECHARGE_KEY("gpt_recharge_key:", "gpt代充", 60),
         GPT_RECHARGE_RECOVER_KEY("gpt_recharge_recover_key:", "gpt代充恢复key", 60 * 5l),
         CLAUDE_CODE_TY_DAY_CARD_KEY("claude_code_ty_day_card_key:", "claude_code体验天卡key", 30L),
+        NANO_TY_DAY_CARD_KEY("nano_ty_day_card_key:", "nano体验天卡key", 30L),
         ;
 
         private String name;

+ 103 - 25
netflix-service/src/main/java/com/cyksj/service/gzh/factory/EventMsgService.java

@@ -1,5 +1,7 @@
 package com.cyksj.service.gzh.factory;
 
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.lang.UUID;
 import cn.hutool.core.util.RandomUtil;
 import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -10,6 +12,8 @@ import com.cyksj.common.util.Jsons;
 import com.cyksj.common.util.StringUtil;
 import com.cyksj.config.WeChatConfig;
 import com.cyksj.dto.UserSharedDto;
+import com.cyksj.mapper.GoodsDonSkuMapper;
+import com.cyksj.mapper.NanoUserQuotaMapper;
 import com.cyksj.mapper.UserMapper;
 import com.cyksj.mapper.WxAppMapper;
 import com.cyksj.mapper.claudecode.ClaudeCodeUserTrialReceiveRecordMapper;
@@ -18,6 +22,7 @@ import com.cyksj.mapper.manage.coupon.CouponDistributePopularizeMapper;
 import com.cyksj.mapper.manage.coupon.CouponRecommendMapper;
 import com.cyksj.mapper.manage.cps.UserDistributePopularizeMapper;
 import com.cyksj.mapper.manage.distribute.UserDistributeMapper;
+import com.cyksj.mapper.nano.NanoUserTrialReceiveRecordMapper;
 import com.cyksj.mapper.station.CollegeStudentStationUserRelateMapper;
 import com.cyksj.model.dto.*;
 import com.cyksj.model.entity.*;
@@ -91,6 +96,12 @@ public class EventMsgService implements GzhMsgService {
 
 	private final ClaudeCodeUserTrialReceiveRecordMapper claudeCodeUserTrialReceiveRecordMapper;
 
+	private final NanoUserTrialReceiveRecordMapper nanoUserTrialReceiveRecordMapper;
+
+	private final NanoUserQuotaMapper nanoUserQuotaMapper;
+
+	private final GoodsDonSkuMapper goodsDonSkuMapper;
+
 	private static final GlobalThreadPoolTaskExecutor TASK_POOL = GlobalThreadPoolTaskExecutor.getInstance();
 
 	@Override
@@ -271,7 +282,7 @@ public class EventMsgService implements GzhMsgService {
 				}
 			}
 			//查找渠道是否有设置了对应的claude code优惠码
-			setClaudeCodePopularize(sharedId, dsPopularizeId, user);
+			handlerSpecialPopularize(sharedId, dsPopularizeId, user, isNewUser, gzhDto.getIsNano());
 			//每日活跃记录
 			userLoginRecordService.saveUserLoginDayRecord(user.getId());
 			//是否是大学生快递站进来的用户
@@ -379,7 +390,18 @@ public class EventMsgService implements GzhMsgService {
 	/**
 	 * 查找渠道是否有设置了对应的claude code优惠码
 	 */
-	public void setClaudeCodePopularize(Long sharedId, Long dsPopularizeId, User user) {
+	public void handlerSpecialPopularize(Long sharedId, Long dsPopularizeId, User user, Boolean isNewUser, Boolean isNano) {
+		if (!isNewUser) {
+			return;
+		}
+		String email = user.getEmail();
+		if (StrUtil.isNotBlank(email)) {
+			Long count = email.chars().filter(c -> c == '.').count();
+			//邮箱超过3个'.'字符用户过滤
+			if (count > 3) {
+				return;
+			}
+		}
 		//查找渠道是否有设置了对应的claude code优惠码
 		if (sharedId != null) {
 			UserDistribute userDistribute = userDistributeMapper.selectOne(Wrappers.lambdaQuery(UserDistribute.class).eq(UserDistribute::getUserId, sharedId).eq(UserDistribute::getDeleted, Boolean.TRUE).last("limit 1"));
@@ -392,33 +414,59 @@ public class EventMsgService implements GzhMsgService {
 					return;
 				}
 				//是否是cc推广
-				if (StrUtil.isEmpty(userDistributePopularize.getOtherUrl()) || !"cc".equals(userDistributePopularize.getOtherUrl())) {
-					return;
-				}
-				Integer selectCount = couponDistributePopularizeMapper.selectCount(Wrappers.lambdaQuery(CouponDistributePopularize.class).eq(CouponDistributePopularize::getDistributeId, userDistribute.getId()).eq(CouponDistributePopularize::getDeleted, Boolean.TRUE).eq(CouponDistributePopularize::getCouponId, Constant.DISTRIBUTE_CLAUDE_CODE_COUPON_ID));
-				//是否设置claude cod优惠券
-				if (selectCount > 0) {
-					//设置了 直接发送优惠券
-					couponCommonService.sendCouponToUser(user.getId(), Constant.DISTRIBUTE_CLAUDE_CODE_COUPON_ID, 0l, 0l, dsPopularizeId, CouponUser.Channel.manual);
-					if (redisService.setNx(RedisService.key.CLAUDE_CODE_TY_DAY_CARD_KEY.getName() + user.getId(), user.getId(), RedisService.key.CLAUDE_CODE_TY_DAY_CARD_KEY.getTimeout())) {
-						//存在Claude code车票不发送体验卡
-						if (!claudeCodeService.checkClaudeCodeUser(user.getId())) {
-							Integer receiveRecord = claudeCodeUserTrialReceiveRecordMapper.selectCount(Wrappers.lambdaQuery(ClaudeCodeUserTrialReceiveRecord.class).eq(ClaudeCodeUserTrialReceiveRecord::getUserId, user.getId()));
-							if (receiveRecord > 0) {
-								return;
+				if (Constant.CC_DISTRIBUTE_MARK.equals(userDistributePopularize.getOtherUrl())) {
+					Integer selectCount = couponDistributePopularizeMapper.selectCount(Wrappers.lambdaQuery(CouponDistributePopularize.class).eq(CouponDistributePopularize::getDistributeId, userDistribute.getId()).eq(CouponDistributePopularize::getDeleted, Boolean.TRUE).eq(CouponDistributePopularize::getCouponId, Constant.DISTRIBUTE_CLAUDE_CODE_COUPON_ID));
+					//是否设置claude cod优惠券
+					if (selectCount > 0) {
+						//设置了 直接发送优惠券
+						Boolean b = couponCommonService.sendCouponToUser(user.getId(), Constant.DISTRIBUTE_CLAUDE_CODE_COUPON_ID, 0l, 0l, dsPopularizeId, CouponUser.Channel.manual);
+						if (!b) {
+							return;
+						}
+						if (redisService.setNx(RedisService.key.CLAUDE_CODE_TY_DAY_CARD_KEY.getName() + user.getId(), user.getId(), RedisService.key.CLAUDE_CODE_TY_DAY_CARD_KEY.getTimeout())) {
+							//存在Claude code车票不发送体验卡
+							if (!claudeCodeService.checkClaudeCodeUser(user.getId())) {
+								Integer receiveRecord = claudeCodeUserTrialReceiveRecordMapper.selectCount(Wrappers.lambdaQuery(ClaudeCodeUserTrialReceiveRecord.class).eq(ClaudeCodeUserTrialReceiveRecord::getUserId, user.getId()));
+								if (receiveRecord > 0) {
+									return;
+								}
+								//发送claude code 体验卡
+								Long relationId = groupRelationFrontService.getTrialTicketByUserIdAndGoodsId(user.getId(), Constant.DISTRIBUTE_CLAUDE_CODE_TRIAL_SKU_ID, 1);
+								recordReceiveClaudeCodeRecord(user.getId(), relationId);
 							}
-							//发送claude code 体验卡
-							Long relationId = groupRelationFrontService.getTrialTicketByUserIdAndGoodsId(user.getId(), Constant.DISTRIBUTE_CLAUDE_CODE_TRIAL_SKU_ID, 1);
-							recordReceiveClaudeCodeRecord(user.getId(), relationId);
+						}
+					} else {
+						//该优惠券是否处于推荐中
+						int isTj = couponRecommendMapper.checkPresentRecommendByCouponId(Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID);
+						if (isTj != 0) {
+							//发送无优惠码的 优惠券
+							couponCommonService.sendCouponToUser(user.getId(), Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID, 0l, 0l, dsPopularizeId, CouponUser.Channel.manual);
 						}
 					}
-				} else {
-					//该优惠券是否处于推荐中
-					int isTj = couponRecommendMapper.checkPresentRecommendByCouponId(Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID);
-					if (isTj != 0) {
-						//发送无优惠码的 优惠券
-						couponCommonService.sendCouponToUser(user.getId(), Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID, 0l, 0l, dsPopularizeId, CouponUser.Channel.manual);
+				}
+
+				//是否是nano 推广
+				if (Constant.NANO_DISTRIBUTE_MARK.equals(userDistributePopularize.getOtherUrl())) {
+					Integer selectCount = couponDistributePopularizeMapper.selectCount(Wrappers.lambdaQuery(CouponDistributePopularize.class).eq(CouponDistributePopularize::getDistributeId, userDistribute.getId()).eq(CouponDistributePopularize::getDeleted, Boolean.TRUE).eq(CouponDistributePopularize::getCouponId, Constant.DISTRIBUTE_NANO_COUPON_ID));
+					if (selectCount > 0) {
+						//设置了 直接发送优惠券
+						couponCommonService.sendCouponToUser(user.getId(), Constant.DISTRIBUTE_NANO_COUPON_ID, 0l, 0l, dsPopularizeId, CouponUser.Channel.manual);
+					}
+				}
+			}
+
+			//新用户都送体验卡
+			if (isNano != null && isNano) {
+				if (redisService.setNx(RedisService.key.NANO_TY_DAY_CARD_KEY.getName() + user.getId(), user.getId(), RedisService.key.NANO_TY_DAY_CARD_KEY.getTimeout())) {
+					Integer receiveRecord = nanoUserTrialReceiveRecordMapper.selectCount(Wrappers.lambdaQuery(NanoUserTrialReceiveRecord.class).eq(NanoUserTrialReceiveRecord::getUserId, user.getId()));
+					if (receiveRecord > 0) {
+						return;
 					}
+					//发送nano 体验卡
+					Long relationId = groupRelationFrontService.getTrialTicketByUserIdAndGoodsId(user.getId(), Constant.DISTRIBUTE_NANO_TRIAL_SKU_ID, 2);
+					recordReceiveNanoRecord(user.getId(), relationId);
+					//创建nano user
+					createNanoUser(user.getId(), relationId, goodsDonSkuMapper.selectById(Constant.DISTRIBUTE_NANO_TRIAL_SKU_ID));
 				}
 			}
 		}
@@ -432,4 +480,34 @@ public class EventMsgService implements GzhMsgService {
 			claudeCodeUserTrialReceiveRecordMapper.insert(record);
 		}
 	}
+
+	private void recordReceiveNanoRecord(Long userId, Long relationId) {
+		if (relationId != null) {
+			NanoUserTrialReceiveRecord record = new NanoUserTrialReceiveRecord();
+			record.setUserId(userId);
+			record.setRelationId(relationId);
+			nanoUserTrialReceiveRecordMapper.insert(record);
+		}
+	}
+
+	private void createNanoUser(Long userId, Long relationId, GoodsDonSku sku) {
+		try {
+			if (relationId == null) {
+				return;
+			}
+			User user = userMapper.selectById(userId);
+			NanoUserQuota nanoUserQuota = new NanoUserQuota();
+			nanoUserQuota.setUid(userId);
+			nanoUserQuota.setRelationId(relationId);
+			nanoUserQuota.setUserToken(UUID.randomUUID().toString());
+			nanoUserQuota.setNickname(user.getNickname());
+			nanoUserQuota.setAvatar(user.getHeadimgurl());
+			nanoUserQuota.setPurchaseDate(DateTime.now());
+			nanoUserQuota.setTotalQuota(sku.getNanoQuota());
+			nanoUserQuota.setRemainingQuota(nanoUserQuota.getTotalQuota());
+			nanoUserQuotaMapper.insert(nanoUserQuota);
+		} catch (DuplicateKeyException e) {
+
+		}
+	}
 }

+ 12 - 0
netflix-service/src/main/java/com/cyksj/service/nano/NanoService.java

@@ -0,0 +1,12 @@
+package com.cyksj.service.nano;
+
+import com.cyksj.model.views.NanoUserQuotaInfoView;
+
+/**
+ * Nano服务接口
+ */
+public interface NanoService {
+	NanoUserQuotaInfoView getUserSubsInfo(long userId);
+
+	Long getUserTokenByNanoToken(String usertoken);
+}

+ 83 - 0
netflix-service/src/main/java/com/cyksj/service/nano/impl/NanoServiceImpl.java

@@ -0,0 +1,83 @@
+package com.cyksj.service.nano.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.date.DateTime;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.constant.Constant;
+import com.cyksj.mapper.GroupsMapper;
+import com.cyksj.mapper.GroupsRelationMapper;
+import com.cyksj.mapper.NanoUserQuotaMapper;
+import com.cyksj.mapper.vip.VipUserMapper;
+import com.cyksj.model.entity.GroupsRelation;
+import com.cyksj.model.entity.GroupsTrips;
+import com.cyksj.model.entity.NanoUserQuota;
+import com.cyksj.model.entity.VipUser;
+import com.cyksj.model.manage.views.GroupsRelationView;
+import com.cyksj.model.views.NanoUserQuotaInfoView;
+import com.cyksj.service.nano.NanoService;
+import com.cyksj.service.user.UserBindRelationService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.param.Operator;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * Nano服务实现
+ */
+@Service
+@RequiredArgsConstructor
+public class NanoServiceImpl implements NanoService {
+	private final UserBindRelationService userBindRelationService;
+
+	private final NanoUserQuotaMapper nanoUserQuotaMapper;
+
+	private final GroupsRelationMapper groupsRelationMapper;
+
+	private final GroupsMapper groupsMapper;
+
+	private final VipUserMapper vipUserMapper;
+
+	private final BeanSearcher beanSearcher;
+	@Override
+	public NanoUserQuotaInfoView getUserSubsInfo(long userId) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		VipUser vipUser = vipUserMapper.selectOne(Wrappers.lambdaQuery(VipUser.class).in(VipUser::getUserId, userIdList).gt(VipUser::getExpiryTime, DateTime.now()));
+		Boolean isVip = vipUser != null;
+		NanoUserQuota nanoUserQuota = null;
+		if (isVip) {
+			GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
+					.field(GroupsRelationView::getIsVip, Boolean.TRUE)
+					.field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
+					.field(GroupsRelationView::getGoodsId, Constant.NANO_GOODS_ID)
+					.build());
+			if (groupsRelationView != null) {
+				nanoUserQuota = nanoUserQuotaMapper.selectOne(Wrappers.lambdaQuery(NanoUserQuota.class).eq(NanoUserQuota::getRelationId, groupsRelationView.getId()).last("limit 1"));
+			}
+		}
+		if (nanoUserQuota == null) {
+			nanoUserQuota = nanoUserQuotaMapper.selectOne(Wrappers.lambdaQuery(NanoUserQuota.class).in(NanoUserQuota::getUid, userIdList).orderByDesc(NanoUserQuota::getTotalQuota).orderByDesc(NanoUserQuota::getRemainingQuota).last("limit 1"));
+		}
+		if (nanoUserQuota != null) {
+			NanoUserQuotaInfoView nanoUserQuotaInfoView = new NanoUserQuotaInfoView();
+			nanoUserQuotaInfoView.setIsVip(vipUser != null);
+			GroupsRelation relation = groupsRelationMapper.selectById(nanoUserQuota.getRelationId());
+			GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId());
+			BeanUtil.copyProperties(nanoUserQuota, nanoUserQuotaInfoView);
+			nanoUserQuotaInfoView.setSkuId(groupsTrips.getSkuId());
+			return nanoUserQuotaInfoView;
+		}
+		return null;
+	}
+
+	@Override
+	public Long getUserTokenByNanoToken(String usertoken) {
+		NanoUserQuota nanoUserQuota = nanoUserQuotaMapper.selectOne(Wrappers.lambdaQuery(NanoUserQuota.class).eq(NanoUserQuota::getUserToken, usertoken).last("limit 1"));
+		if (nanoUserQuota != null) {
+			return nanoUserQuota.getUid();
+		}
+		return null;
+	}
+}

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

@@ -1892,6 +1892,23 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 	}
 
 	private void createNanoUser(Long userId, Long relationId, GoodsDonSku sku) {
+		//nano
+		//是否存在nano 推广体验车票
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder().field(GroupsRelationView::getSkuId, Constant.DISTRIBUTE_NANO_TRIAL_SKU_ID).field(GroupsRelationView::getUserId, userIdList).build());
+		if (groupsRelationView != null) {
+			//清除车票
+			Long trialRelationId = groupsRelationView.getId();
+			GroupsRelation trialRelation = groupsRelationMapper.selectById(trialRelationId);
+			groupRelationClearService.clearTicket(trialRelation, UserTicketClearedRecord.Source.vip_deduct);
+
+			NanoUserQuota nanoUserQuota = nanoUserQuotaMapper.selectOne(Wrappers.lambdaQuery(NanoUserQuota.class).eq(NanoUserQuota::getRelationId, trialRelationId).last("limit 1"));
+			nanoUserQuota.setTotalQuota(nanoUserQuota.getTotalQuota() + sku.getNanoQuota());
+			nanoUserQuota.setRemainingQuota(nanoUserQuota.getRemainingQuota() + sku.getNanoQuota());
+			nanoUserQuota.setRelationId(relationId);
+			nanoUserQuotaMapper.updateById(nanoUserQuota);
+			return;
+		}
 		try {
 			User user = userMapper.selectById(userId);
 			NanoUserQuota nanoUserQuota = new NanoUserQuota();

+ 1 - 1
netflix-service/src/main/java/com/cyksj/service/user/UserService.java

@@ -22,7 +22,7 @@ public interface UserService extends IService<User> {
 
 	User saveAuth(GzhOAuth2UserInfo userinfo, AuthRedirect re, User.RegisterEnv registerEnv) throws Exception;
 
-	UserWhoami whoami(long userId, Boolean ccCode);
+	UserWhoami whoami(long userId, Long popularizeGoodsId);
 
     User getSyncUser(String openId, String unionId, WxSyncLog wxSyncLog);
 

+ 137 - 41
netflix-service/src/main/java/com/cyksj/service/user/impl/UserServiceImpl.java

@@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.lang.UUID;
 import cn.hutool.core.util.RandomUtil;
 import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -19,7 +20,7 @@ import com.cyksj.config.wxlogin.WxOpenPlatYHLXLoginConfig;
 import com.cyksj.dto.UserSharedDto;
 import com.cyksj.mapper.*;
 import com.cyksj.mapper.channel.ChannelPopularizeMapper;
-import com.cyksj.mapper.claudecode.ClaudeCodeUserPopularizeTrialTicketRecordMapper;
+import com.cyksj.mapper.claudecode.PopularizeTrialTicketRecordMapper;
 import com.cyksj.mapper.claudecode.ClaudeCodeUserTrialReceiveRecordMapper;
 import com.cyksj.mapper.corp.*;
 import com.cyksj.mapper.manage.coupon.CouponDistributePopularizeMapper;
@@ -29,6 +30,7 @@ import com.cyksj.mapper.manage.distribute.UserDistributeMapper;
 import com.cyksj.mapper.manage.distribute.UserDistributeSharedMapper;
 import com.cyksj.mapper.market.task.UserBindDetailMapper;
 import com.cyksj.mapper.mirrorshop.UserMirrorShopMapper;
+import com.cyksj.mapper.nano.NanoUserTrialReceiveRecordMapper;
 import com.cyksj.mapper.station.CollegeStudentStationUserRelateMapper;
 import com.cyksj.mapper.vip.VipUserMapper;
 import com.cyksj.model.dto.*;
@@ -150,12 +152,16 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 
 	private final ClaudeCodeService claudeCodeService;
 
-	private final ClaudeCodeUserPopularizeTrialTicketRecordMapper claudeCodeUserPopularizeTrialTicketRecordMapper;
+	private final PopularizeTrialTicketRecordMapper popularizeTrialTicketRecordMapper;
 
 	private final CouponRecommendMapper couponRecommendMapper;
 
 	private final ClaudeCodeUserTrialReceiveRecordMapper claudeCodeUserTrialReceiveRecordMapper;
 
+	private final NanoUserTrialReceiveRecordMapper nanoUserTrialReceiveRecordMapper;
+
+	private final NanoUserQuotaMapper nanoUserQuotaMapper;
+
 	private static final GlobalThreadPoolTaskExecutor TASK_POOL = GlobalThreadPoolTaskExecutor.getInstance();
 
 	@Override
@@ -167,6 +173,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 		String callback = re.getCk();
 		String callbackType = re.getCt();
 		String customId = re.getCd();
+		Boolean isNano = re.getIsNano();
 
 		final String unionid = info.getUnionid();
 		if (StrUtil.isEmpty(unionid)) {
@@ -248,8 +255,8 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 		if (sharedId != null && dsType != null && dsType == 2) {
 			equipDistributeService.saveDistributionInvitation(sharedId, isNewUser, user.getId());
 		}
-		//查找渠道是否有设置了对应的claude code优惠码
-		setClaudeCodePopularize(sharedId, dsPopularizeId, user);
+		//处理特殊推广
+		handlerSpecialPopularize(sharedId, dsPopularizeId, user, isNewUser, isNano);
 		//每日活跃记录
 		userLoginRecordService.saveUserLoginDayRecord(user.getId());
 		//是否是大学生快递站进来的用户
@@ -262,8 +269,12 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 		return user;
 	}
 
+	/**
+	 *
+	 * @param popularizeGoodsId 独立站推广商品id
+	 */
 	@Override
-	public UserWhoami whoami(long userId, Boolean ccCode) {
+	public UserWhoami whoami(long userId, Long popularizeGoodsId) {
 		User user = this.getById(userId);
 		Assert.notNull(user, "业务处理失败,请联系客服");
 		UserWhoami userWhoami = new UserWhoami();
@@ -316,21 +327,14 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 				.in(OrderDon::getUserId, userIdList)
 				.notIn(OrderDon::getStatus, Constant.noOrderStatus));
 		userWhoami.setIsPay(selectCount > 0);
-		if (ccCode != null) {
-			Number number = beanSearcher.searchCount(GroupsRelationView.class, MapUtils.builder().field(GroupsRelationView::getUserId, userIdList).op(Operator.InList).field(GroupsRelationView::getSkuId, Constant.DISTRIBUTE_CLAUDE_CODE_TRIAL_SKU_ID).field(GroupsRelationView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID).build());
-			userWhoami.setCcCode(number.intValue() > 0);
-			Integer selectCount1 = claudeCodeUserPopularizeTrialTicketRecordMapper.selectCount(Wrappers.lambdaQuery(ClaudeCodeUserPopularizeTrialTicketRecord.class).in(ClaudeCodeUserPopularizeTrialTicketRecord::getUserId, userIdList));
-			if (selectCount1 == 0) {
-				ClaudeCodeUserPopularizeTrialTicketRecord record = new ClaudeCodeUserPopularizeTrialTicketRecord();
-				record.setUserId(userId);
-				claudeCodeUserPopularizeTrialTicketRecordMapper.insert(record);
-			}
-			userWhoami.setTc(selectCount1 == 0);
+		//推广信息
+		if (popularizeGoodsId != null) {
+			setPopularizeUserInfo(popularizeGoodsId, userIdList, userId, userWhoami);
 		}
 		return userWhoami;
 	}
 
-    @Override
+	@Override
     public User getSyncUser(String openId, String unionId, WxSyncLog wxSyncLog) {
         User user;
         //迁移公众号之后同步授权后信息
@@ -603,7 +607,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 			equipDistributeService.saveDistributionInvitation(sharedId, isNewUser, user.getId());
 		}
 		//查找渠道是否有设置了对应的claude code优惠码
-		setClaudeCodePopularize(sharedId, dsPopularizeId, user);
+		handlerSpecialPopularize(sharedId, dsPopularizeId, user, isNewUser, loginReq.getIsNano());
 
 		//每日活跃记录
 		userLoginRecordService.saveUserLoginDayRecord(user.getId());
@@ -947,7 +951,18 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 	/**
 	 * 查找渠道是否有设置了对应的claude code优惠码
 	 */
-	public void setClaudeCodePopularize(Long sharedId, Long dsPopularizeId, User user) {
+	public void handlerSpecialPopularize(Long sharedId, Long dsPopularizeId, User user, Boolean isNewUser, Boolean isNano) {
+		if (!isNewUser) {
+			return;
+		}
+		String email = user.getEmail();
+		if (StrUtil.isNotBlank(email)) {
+			Long count = email.chars().filter(c -> c == '.').count();
+			//邮箱超过3个'.'字符用户过滤
+			if (count > 3) {
+				return;
+			}
+		}
 		//查找渠道是否有设置了对应的claude code优惠码
 		if (sharedId != null) {
 			UserDistribute userDistribute = userDistributeMapper.selectOne(Wrappers.lambdaQuery(UserDistribute.class).eq(UserDistribute::getUserId, sharedId).eq(UserDistribute::getDeleted, Boolean.TRUE).last("limit 1"));
@@ -960,36 +975,61 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 					return;
 				}
 				//是否是cc推广
-				if (StrUtil.isEmpty(userDistributePopularize.getOtherUrl()) || !"cc".equals(userDistributePopularize.getOtherUrl())) {
-					return;
-				}
-				Integer selectCount = couponDistributePopularizeMapper.selectCount(Wrappers.lambdaQuery(CouponDistributePopularize.class).eq(CouponDistributePopularize::getDistributeId, userDistribute.getId()).eq(CouponDistributePopularize::getDeleted, Boolean.TRUE).eq(CouponDistributePopularize::getCouponId, Constant.DISTRIBUTE_CLAUDE_CODE_COUPON_ID));
-				//是否设置claude cod优惠券
-				if (selectCount > 0) {
-					//设置了 直接发送优惠券
-					couponCommonService.sendCouponToUser(user.getId(), Constant.DISTRIBUTE_CLAUDE_CODE_COUPON_ID, 0l, 0l, dsPopularizeId, CouponUser.Channel.manual);
-					if (redisService.setNx(RedisService.key.CLAUDE_CODE_TY_DAY_CARD_KEY.getName() + user.getId(), user.getId(), RedisService.key.CLAUDE_CODE_TY_DAY_CARD_KEY.getTimeout())) {
-						//存在Claude code车票不发送体验卡
-						if (!claudeCodeService.checkClaudeCodeUser(user.getId())) {
-							Integer receiveRecord = claudeCodeUserTrialReceiveRecordMapper.selectCount(Wrappers.lambdaQuery(ClaudeCodeUserTrialReceiveRecord.class).eq(ClaudeCodeUserTrialReceiveRecord::getUserId, user.getId()));
-							if (receiveRecord > 0) {
-								return;
+				if (Constant.CC_DISTRIBUTE_MARK.equals(userDistributePopularize.getOtherUrl())) {
+					Integer selectCount = couponDistributePopularizeMapper.selectCount(Wrappers.lambdaQuery(CouponDistributePopularize.class).eq(CouponDistributePopularize::getDistributeId, userDistribute.getId()).eq(CouponDistributePopularize::getDeleted, Boolean.TRUE).eq(CouponDistributePopularize::getCouponId, Constant.DISTRIBUTE_CLAUDE_CODE_COUPON_ID));
+					//是否设置claude cod优惠券
+					if (selectCount > 0) {
+						//设置了 直接发送优惠券
+						Boolean b = couponCommonService.sendCouponToUser(user.getId(), Constant.DISTRIBUTE_CLAUDE_CODE_COUPON_ID, 0l, 0l, dsPopularizeId, CouponUser.Channel.manual);
+						if (!b) {
+							return;
+						}
+						if (redisService.setNx(RedisService.key.CLAUDE_CODE_TY_DAY_CARD_KEY.getName() + user.getId(), user.getId(), RedisService.key.CLAUDE_CODE_TY_DAY_CARD_KEY.getTimeout())) {
+							//存在Claude code车票不发送体验卡
+							if (!claudeCodeService.checkClaudeCodeUser(user.getId())) {
+								Integer receiveRecord = claudeCodeUserTrialReceiveRecordMapper.selectCount(Wrappers.lambdaQuery(ClaudeCodeUserTrialReceiveRecord.class).eq(ClaudeCodeUserTrialReceiveRecord::getUserId, user.getId()));
+								if (receiveRecord > 0) {
+									return;
+								}
+								//发送claude code 体验卡
+								Long relationId = groupRelationFrontService.getTrialTicketByUserIdAndGoodsId(user.getId(), Constant.DISTRIBUTE_CLAUDE_CODE_TRIAL_SKU_ID, 1);
+								recordReceiveClaudeCodeRecord(user.getId(), relationId);
 							}
-							//发送claude code 体验卡
-							Long relationId = groupRelationFrontService.getTrialTicketByUserIdAndGoodsId(user.getId(), Constant.DISTRIBUTE_CLAUDE_CODE_TRIAL_SKU_ID, 1);
-							recordReceiveClaudeCodeRecord(user.getId(), relationId);
+						}
+					} else {
+						//该优惠券是否处于推荐中
+						int isTj = couponRecommendMapper.checkPresentRecommendByCouponId(Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID);
+						if (isTj != 0) {
+							//发送无优惠码的 优惠券
+							couponCommonService.sendCouponToUser(user.getId(), Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID, 0l, 0l, dsPopularizeId, CouponUser.Channel.manual);
 						}
 					}
-				} else {
-					//该优惠券是否处于推荐中
-					int isTj = couponRecommendMapper.checkPresentRecommendByCouponId(Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID);
-					if (isTj != 0) {
-						//发送无优惠码的 优惠券
-						couponCommonService.sendCouponToUser(user.getId(), Constant.DISTRIBUTE_CLAUDE_CODE_NO_CODE_COUPON_ID, 0l, 0l, dsPopularizeId, CouponUser.Channel.manual);
+				}
+
+				//是否是nano 推广
+				if (Constant.NANO_DISTRIBUTE_MARK.equals(userDistributePopularize.getOtherUrl())) {
+					Integer selectCount = couponDistributePopularizeMapper.selectCount(Wrappers.lambdaQuery(CouponDistributePopularize.class).eq(CouponDistributePopularize::getDistributeId, userDistribute.getId()).eq(CouponDistributePopularize::getDeleted, Boolean.TRUE).eq(CouponDistributePopularize::getCouponId, Constant.DISTRIBUTE_NANO_COUPON_ID));
+					if (selectCount > 0) {
+						//设置了 直接发送优惠券
+						couponCommonService.sendCouponToUser(user.getId(), Constant.DISTRIBUTE_NANO_COUPON_ID, 0l, 0l, dsPopularizeId, CouponUser.Channel.manual);
 					}
 				}
 			}
 		}
+		//新用户都发
+		if (isNano != null && isNano) {
+			if (redisService.setNx(RedisService.key.NANO_TY_DAY_CARD_KEY.getName() + user.getId(), user.getId(), RedisService.key.NANO_TY_DAY_CARD_KEY.getTimeout())) {
+				Integer receiveRecord = nanoUserTrialReceiveRecordMapper.selectCount(Wrappers.lambdaQuery(NanoUserTrialReceiveRecord.class).eq(NanoUserTrialReceiveRecord::getUserId, user.getId()));
+				if (receiveRecord > 0) {
+					return;
+				}
+				//发送nano 体验卡
+				Long relationId = groupRelationFrontService.getTrialTicketByUserIdAndGoodsId(user.getId(), Constant.DISTRIBUTE_NANO_TRIAL_SKU_ID, 2);
+				recordReceiveNanoRecord(user.getId(), relationId);
+				//创建nano user
+				createNanoUser(user.getId(), relationId, goodsDonSkuMapper.selectById(Constant.DISTRIBUTE_NANO_TRIAL_SKU_ID));
+			}
+		}
 	}
 
 	public void recordReceiveClaudeCodeRecord(Long userId,Long relationId) {
@@ -1000,4 +1040,60 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 			claudeCodeUserTrialReceiveRecordMapper.insert(record);
 		}
 	}
+
+	public void recordReceiveNanoRecord(Long userId, Long relationId) {
+		if (relationId != null) {
+			NanoUserTrialReceiveRecord record = new NanoUserTrialReceiveRecord();
+			record.setUserId(userId);
+			record.setRelationId(relationId);
+			nanoUserTrialReceiveRecordMapper.insert(record);
+		}
+	}
+
+	/**
+	 * 独立站渠道推广弹窗
+	 */
+	private void setPopularizeUserInfo(Long popularizeGoodsId, List<Long> userIdList, Long userId, UserWhoami userWhoami) {
+		Long trialSkuId = null;
+		if (popularizeGoodsId == Constant.CLAUDE_CODE_GOODS_ID) {
+			trialSkuId = Constant.DISTRIBUTE_CLAUDE_CODE_TRIAL_SKU_ID;
+		}
+		if (popularizeGoodsId == Constant.NANO_GOODS_ID) {
+			trialSkuId = Constant.DISTRIBUTE_NANO_TRIAL_SKU_ID;
+		}
+		if (trialSkuId == null) {
+			return;
+		}
+		Number number = beanSearcher.searchCount(GroupsRelationView.class, MapUtils.builder().field(GroupsRelationView::getUserId, userIdList).op(Operator.InList).field(GroupsRelationView::getSkuId, trialSkuId).field(GroupsRelationView::getGoodsId, popularizeGoodsId).build());
+		userWhoami.setCcCode(number.intValue() > 0);
+		Integer selectCount1 = popularizeTrialTicketRecordMapper.selectCount(Wrappers.lambdaQuery(PopularizeTrialTicketRecord.class).eq(PopularizeTrialTicketRecord::getGoodsId, popularizeGoodsId).in(PopularizeTrialTicketRecord::getUserId, userIdList));
+		if (selectCount1 == 0) {
+			PopularizeTrialTicketRecord record = new PopularizeTrialTicketRecord();
+			record.setGoodsId(popularizeGoodsId);
+			record.setUserId(userId);
+			popularizeTrialTicketRecordMapper.insert(record);
+		}
+		userWhoami.setTc(selectCount1 == 0);
+	}
+
+	private void createNanoUser(Long userId, Long relationId, GoodsDonSku sku) {
+		try {
+			if (relationId == null) {
+				return;
+			}
+			User user = userMapper.selectById(userId);
+			NanoUserQuota nanoUserQuota = new NanoUserQuota();
+			nanoUserQuota.setUid(userId);
+			nanoUserQuota.setRelationId(relationId);
+			nanoUserQuota.setUserToken(UUID.randomUUID().toString());
+			nanoUserQuota.setNickname(user.getNickname());
+			nanoUserQuota.setAvatar(user.getHeadimgurl());
+			nanoUserQuota.setPurchaseDate(DateTime.now());
+			nanoUserQuota.setTotalQuota(sku.getNanoQuota());
+			nanoUserQuota.setRemainingQuota(nanoUserQuota.getTotalQuota());
+			nanoUserQuotaMapper.insert(nanoUserQuota);
+		} catch (DuplicateKeyException e) {
+
+		}
+	}
 }

+ 1 - 1
netflix-service/src/main/java/com/cyksj/service/vip/impl/VipFrontServiceImpl.java

@@ -482,7 +482,7 @@ public class VipFrontServiceImpl implements VipFrontService {
 		if (remainNum == null) {
 			deductMoney = deductMoney.add(orderTotalMoney);
 		} else {
-			BigDecimal remainMoney = BigDecimal.valueOf(remainNum).divide(BigDecimal.valueOf(totalNum), 2, RoundingMode.DOWN).multiply(orderTotalMoney);
+			BigDecimal remainMoney = totalNum == 0 ? BigDecimal.ZERO : BigDecimal.valueOf(remainNum).divide(BigDecimal.valueOf(totalNum), 2, RoundingMode.DOWN).multiply(orderTotalMoney);
 			deductMoney = deductMoney.add(remainMoney);
 		}
 		return deductMoney;

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

@@ -427,8 +427,8 @@ public class ClaudeCodeController {
 			}
 		}
 		Boolean isTjB = false;
-		//非推广链接
-		if (dsCode == null && coupon == null) {
+		//不存在优惠 查找推荐优惠券
+		if (coupon == null) {
 			coupon = couponRecommendMapper.getRecommendCouponByGoodsId(Constant.CLAUDE_CODE_GOODS_ID);
 			isTjB = true;
 		}

+ 5 - 1
netflix-web/src/main/java/com/cyksj/web/controller/cps/CpsPopularizeController.java

@@ -118,10 +118,14 @@ public class CpsPopularizeController {
 		SearchResult<UserDistributePopularizeView> search = beanSearcher.search(UserDistributePopularizeView.class, builder.build());
 		search.getDataList().forEach(data -> {
 			if (StrUtil.isNotBlank(data.getOtherUrl())) {
-				if ("cc".equals(data.getOtherUrl())) {
+				if (Constant.CC_DISTRIBUTE_MARK.equals(data.getOtherUrl())) {
 					data.setPopularizeLink(String.format(Constant.DISTRIBUTE_CLAUDE_CODE_CC_URL, data.getCode()));
 					//对应优惠码
 					data.setCouponCode(couponDistributePopularizeMapper.getCouponCodeByUid(cpsUserId, Constant.DISTRIBUTE_CLAUDE_CODE_COUPON_ID));
+				} else if (Constant.NANO_DISTRIBUTE_MARK.equals(data.getOtherUrl())) {
+					data.setPopularizeLink(String.format(Constant.DISTRIBUTE_NANO_URL, data.getCode()));
+					//对应优惠码
+					data.setCouponCode(couponDistributePopularizeMapper.getCouponCodeByUid(cpsUserId, Constant.DISTRIBUTE_NANO_COUPON_ID));
 				}
 			}
 			if (StrUtil.isEmpty(data.getPopularizeLink())) {

+ 1 - 1
netflix-web/src/main/java/com/cyksj/web/controller/mirror/MirrorController.java

@@ -629,7 +629,7 @@ public class MirrorController {
     }
 
     /**
-     * nano车票跳转登录
+     * nano车票信息
      */
     @GetMapping("/nanoUser/info")
     public Result<NanoUserQuota> nanoUserInfo(Long relationId) {

+ 173 - 0
netflix-web/src/main/java/com/cyksj/web/controller/nano/NanoController.java

@@ -0,0 +1,173 @@
+package com.cyksj.web.controller.nano;
+
+import cn.hutool.core.date.DateTime;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.constant.Constant;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.dto.Result;
+import com.cyksj.dto.UserSharedDto;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.manage.coupon.*;
+import com.cyksj.mapper.manage.distribute.UserDistributeMapper;
+import com.cyksj.model.entity.*;
+import com.cyksj.model.views.GoodsDonSkuView;
+import com.cyksj.model.views.NanoPackageView;
+import com.cyksj.model.views.NanoUserQuotaInfoView;
+import com.cyksj.service.nano.NanoService;
+import com.cyksj.service.user.UserService;
+import com.cyksj.web.util.StpUserUtil;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.param.Operator;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * server/nano推广
+ * Nano推广控制器
+ * 项目名: yhlxj11111111
+ * 文件名: NanoController
+ * 创建者: Claude Code
+ * 创建时间:2025/9/4
+ */
+@Slf4j
+@RestController
+@RequestMapping("/applet/nano")
+@RequiredArgsConstructor
+public class NanoController {
+
+	private final BeanSearcher beanSearcher;
+
+	private final HttpServletRequest request;
+
+	private final NanoService nanoService;
+
+	private final UserService userService;
+
+	private final UserDistributeMapper userDistributeMapper;
+
+	private final CouponDistributePopularizeMapper couponDistributePopularizeMapper;
+
+	private final CouponMapper couponMapper;
+
+	private final CouponSkuMapper couponSkuMapper;
+
+	private final CouponRecommendMapper couponRecommendMapper;
+
+	private final CouponUserMapper couponUserMapper;
+
+	/**
+	 * 通过车票token获取用户信息
+	 */
+	@GetMapping("/get/user/token")
+	public Result<String> getUserTokenByNanoToken(String usertoken) {
+		Long userId = nanoService.getUserTokenByNanoToken(usertoken);
+		if (userId == null) {
+			throw BusinessRuntimeException.getInstance("车票不存在");
+		}
+		String userToken = StpUserUtil.getTokenValueByLoginId(userId);
+		if (userToken == null) {
+			StpUserUtil.login(userId);
+			userToken = StpUserUtil.getTokenValue();
+		}
+		return GatewayResponse.SUCCESS.newBuilder().toResult(userToken);
+	}
+
+
+	/**
+	 * 用户订阅车票信息
+	 */
+	@GetMapping("/get/user/subs/info")
+	public Result<NanoUserQuotaInfoView> getUserSubsInfo() {
+		long userId = StpUserUtil.getLoginIdAsLong();
+		NanoUserQuotaInfoView nanoUserQuotaInfoView = nanoService.getUserSubsInfo(userId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(nanoUserQuotaInfoView);
+	}
+
+	/**
+	 * nano可选套餐
+	 */
+	@GetMapping("/get/packages")
+	public Result<NanoPackageView> getNanoPackages(String dsCode) {
+		Long userId = StpUserUtil.getUserIdAfterLogin();
+		NanoPackageView nanoPackageView = new NanoPackageView();
+		List<GoodsDonSkuView> goodsDonSkuViews = beanSearcher.searchAll(GoodsDonSkuView.class, MapUtils.builder().field(GoodsDonSkuView::getGoodsId, Constant.NANO_GOODS_ID).field(GoodsDonSkuView::getUserOwns).op(Operator.IsNull).build());
+		//渠道推广
+		setNanoSkuCouponInfo(dsCode, userId, goodsDonSkuViews);
+		nanoPackageView.setSkus(goodsDonSkuViews);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(nanoPackageView);
+	}
+
+	public void setNanoSkuCouponInfo(String dsCode, Long userId, List<GoodsDonSkuView> goodsDonSkuViews) {
+		//渠道推广
+		Coupon coupon = null;
+		if (dsCode != null) {
+			UserSharedDto popularizeDto = userService.getUserShredDtoByDsCode(dsCode);
+			if (popularizeDto != null) {
+				UserDistribute userDistribute = userDistributeMapper.selectOne(Wrappers.lambdaQuery(UserDistribute.class).eq(UserDistribute::getUserId, popularizeDto.getSharedId()).eq(UserDistribute::getDeleted, Boolean.TRUE).last("limit 1"));
+				//渠道推广
+				if (userDistribute != null) {
+					// 这里需要根据实际的nano推广优惠券配置来设置
+					// 假设有DISTRIBUTE_NANO_COUPON_ID和DISTRIBUTE_NANO_NO_CODE_COUPON_ID常量
+					Integer selectCount = couponDistributePopularizeMapper.selectCount(Wrappers.lambdaQuery(CouponDistributePopularize.class)
+							.eq(CouponDistributePopularize::getDistributeId, userDistribute.getId())
+							.eq(CouponDistributePopularize::getCouponId, Constant.DISTRIBUTE_NANO_COUPON_ID)
+							.eq(CouponDistributePopularize::getDeleted, Boolean.TRUE));
+					//已配置使用渠道优惠码
+					if (selectCount > 0) {
+						coupon = couponMapper.selectById(Constant.DISTRIBUTE_NANO_COUPON_ID);
+					}
+				}
+			}
+		}
+		Boolean isTjB = false;
+		//不存在优惠 查找推荐优惠券
+		if (coupon == null) {
+			coupon = couponRecommendMapper.getRecommendCouponByGoodsId(Constant.NANO_GOODS_ID);
+			isTjB = true;
+		}
+		if (coupon == null) {
+			return;
+		}
+		if (isTjB) {
+			int isTj = couponRecommendMapper.checkPresentRecommendByCouponId(coupon.getId());
+			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 selectCount = couponUserMapper.selectCount(Wrappers.lambdaQuery(CouponUser.class).eq(CouponUser::getCouponId, coupon.getId()).eq(CouponUser::getUserId, userId));
+			if (selectCount > 0) {
+				Integer isUsed = couponUserMapper.selectCount(Wrappers.lambdaQuery(CouponUser.class)
+						.eq(CouponUser::getCouponId, coupon.getId())
+						.eq(CouponUser::getStatus, CouponUser.Status.unused)
+						.eq(CouponUser::getUserId, userId)
+						.lt(CouponUser::getValidStartTime, DateTime.now())
+						.gt(CouponUser::getValidEndTime, DateTime.now()));
+				if (isUsed == 0) {
+					return;
+				}
+			}
+		}
+		for (GoodsDonSkuView sku : goodsDonSkuViews) {
+			if (couponSkuIds.contains(sku.getSkuId())) {
+				sku.setCouponId(coupon.getId());
+				if (coupon.getType() == Coupon.Type.discount) {
+					sku.setDiscount(coupon.getDiscount());
+					sku.setDiscountPrice(sku.getMoney().multiply(coupon.getDiscount()));
+				} else {
+					sku.setReduceMoney(coupon.getReduceMoney());
+				}
+			}
+		}
+	}
+}

+ 5 - 3
netflix-web/src/main/java/com/cyksj/web/controller/user/AuthorizationController.java

@@ -94,7 +94,7 @@ public class AuthorizationController {
     private final TemplateCommonService templateCommonService;
 
     @GetMapping(value = "/weChat")
-    public void base(String url, String dsCode, Long sharedId, Integer dsType, Long popularizeId, String authType, String callbackType, String callback, String customId, Integer cst, Integer cmt, Boolean cgTj, HttpServletResponse response) throws Exception {
+    public void base(String url, String dsCode, Long sharedId, Integer dsType, Long popularizeId, String authType, String callbackType, String callback, String customId, Integer cst, Integer cmt, Boolean cgTj, Boolean isNano, HttpServletResponse response) throws Exception {
         log.info("微信授权转跳. url: {},authType:{}", url, authType);
         if (StrUtil.isBlank(url)) {
             url = "https://nf.video/";
@@ -115,6 +115,7 @@ public class AuthorizationController {
                 .setCt(callbackType)
                 .setCst(cst)
                 .setCgTj(cgTj)
+                .setIsNano(isNano)
                 .setCmt(cmt);
         String state = Codec.DoBase64.custom().setData(Jsons.toJson(redirect)).encodeAsText();
 
@@ -346,14 +347,15 @@ public class AuthorizationController {
      * 网页扫描微信公众号二维码 临时凭证ticket  关注后登录
      */
     @GetMapping("/wx/gzh/login/qrCode")
-    public Result<WxGzhQrCodeTicketRep> getWxGzhQrCode(Long sharedId, Integer dsType, Long popularizeId, String dsCode, String callbackType, String callback, String customId, Integer cmt, Boolean cgTj) throws Exception {
+    public Result<WxGzhQrCodeTicketRep> getWxGzhQrCode(Long sharedId, Integer dsType, Long popularizeId, String dsCode, String callbackType, String callback, String customId, Integer cmt, Boolean cgTj, Boolean isNano) throws Exception {
         //获取gzh带参数的二维码
         GzhBodyDto gzhBodyDto = new GzhBodyDto().setSharedId(sharedId).setDsType(dsType).setPid(popularizeId).setDsCode(dsCode)
                 .setCk(callback)
                 .setCt(callbackType)
                 .setCd(customId)
                 .setCmt(cmt)
-                .setCgTj(cgTj);
+                .setCgTj(cgTj)
+                .setIsNano(isNano);
         WxGzhQrCodeTicketRep result = weChatService.getWxGzhQrCode(gzhBodyDto);
         return GatewayResponse.SUCCESS.newBuilder().toResult(result);
     }

+ 2 - 2
netflix-web/src/main/java/com/cyksj/web/controller/user/UserController.java

@@ -106,10 +106,10 @@ public class UserController {
     private final UserGalaxyCoinRecordMapper userGalaxyCoinRecordMapper;
 
     @GetMapping(value = "/wx/whoami")
-    public Result<UserWhoami> whoami(Boolean ccCode) {
+    public Result<UserWhoami> whoami(Long popularizeGoodsId) {
         long userId = StpUserUtil.getLoginIdAsLong();
 
-        UserWhoami userWhoami = userService.whoami(userId, ccCode);
+        UserWhoami userWhoami = userService.whoami(userId, popularizeGoodsId);
 
         return GatewayResponse.SUCCESS.newBuilder().toResult(userWhoami);
     }