Przeglądaj źródła

商品订阅提醒

zoujiajian 7 miesięcy temu
rodzic
commit
89886c0c60
25 zmienionych plików z 511 dodań i 6 usunięć
  1. 15 0
      netflix-common/src/main/java/com/cyksj/common/constant/Constant.java
  2. 3 1
      netflix-common/src/main/java/com/cyksj/common/constant/TemplateEnum.java
  3. 2 0
      netflix-common/src/main/java/com/cyksj/dto/RedisKey.java
  4. 13 0
      netflix-dao/src/main/java/com/cyksj/mapper/UserSubscribeAlertGoodsRecordMapper.java
  5. 5 0
      netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDon.java
  6. 5 0
      netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDonSku.java
  7. 25 0
      netflix-dao/src/main/java/com/cyksj/model/entity/UserSubscribeAlertGoodsRecord.java
  8. 5 0
      netflix-dao/src/main/java/com/cyksj/model/manage/request/ReqGoodsSkuInsert.java
  9. 5 0
      netflix-dao/src/main/java/com/cyksj/model/manage/request/ReqGoodsSpuInsert.java
  10. 20 0
      netflix-dao/src/main/java/com/cyksj/model/request/GoodsSubscribeAlertReq.java
  11. 14 1
      netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuFrontView.java
  12. 13 0
      netflix-dao/src/main/java/com/cyksj/model/views/GoodsFrontListCategoryView.java
  13. 6 0
      netflix-dao/src/main/java/com/cyksj/model/views/GoodsFrontListView.java
  14. 13 0
      netflix-dao/src/main/java/com/cyksj/model/views/HomeManagementFrontView.java
  15. 13 0
      netflix-service/src/main/java/com/cyksj/service/goods/GoodsSubscribeAlertService.java
  16. 15 0
      netflix-service/src/main/java/com/cyksj/service/goods/UserSubscribeAlertGoodsRecordService.java
  17. 153 0
      netflix-service/src/main/java/com/cyksj/service/goods/impl/GoodsSubscribeAlertServiceImpl.java
  18. 86 0
      netflix-service/src/main/java/com/cyksj/service/goods/impl/UserSubscribeAlertGoodsRecordServiceImpl.java
  19. 2 0
      netflix-service/src/main/java/com/cyksj/service/mail/MailService.java
  20. 19 0
      netflix-service/src/main/java/com/cyksj/service/mail/impl/MailServiceImpl.java
  21. 2 0
      netflix-service/src/main/java/com/cyksj/service/template/TemplateCommonService.java
  22. 21 0
      netflix-service/src/main/java/com/cyksj/service/template/impl/TemplateCommonServiceImpl.java
  23. 36 4
      netflix-web/src/main/java/com/cyksj/web/controller/goods/GoodsDonController.java
  24. 11 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/goods/CmsGoodsDonController.java
  25. 9 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/goods/CmsGoodsDonSkuController.java

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

@@ -775,4 +775,19 @@ public interface Constant {
 	 * 油管售后时间限制key
 	 */
 	String YOUTUBE_AFTER_SALES_TIME_KEY = "youtube_after_sales_time_key";
+
+	/**
+	 * 商品订阅微信模板
+	 */
+	String GOODS_SUBSCRIBE_ALERT_WX_MSG = "尊敬的用户,您关注的%s已上线,快去看看吧";
+
+	/**
+	 * 商品订阅短信模板
+	 */
+	String GOODS_SUBSCRIBE_ALERT_MSG = "您好,您关注的%s已上线,登录网站https://nf.video/buy/%s 看看吧【杭州崇宇信息】";
+
+	/**
+	 * 商品订阅邮件模板
+	 */
+	String GOODS_SUBSCRIBE_ALERT_EMAIL_MSG = "您好,您关注的%s已上线,登录网站https://nf.video/buy/%s 看看吧";
 }

+ 3 - 1
netflix-common/src/main/java/com/cyksj/common/constant/TemplateEnum.java

@@ -28,7 +28,9 @@ public enum TemplateEnum {
 
 	EXPIRY_TEMPLATE("用户平台账号过期通知模板"),
 
-	WX_GZH_QR_CODE_LOGIN("微信公众号扫码登录模板")
+	WX_GZH_QR_CODE_LOGIN("微信公众号扫码登录模板"),
+
+	GOODS_SUBSCRIBE_ALERT("商品订阅提醒模板")
 
 	;
 	private String desc;

+ 2 - 0
netflix-common/src/main/java/com/cyksj/dto/RedisKey.java

@@ -72,4 +72,6 @@ public class RedisKey {
      * 镜像gpt 当日使用量
      */
     public static String CHATGPT_USER_RESTRICTION ="chatgpt:user:restriction:";
+
+    public static String GOODS_SUBSCRIBE_ALERT_KEY = "goods_subscribe_alert_key:";
 }

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

@@ -0,0 +1,13 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.UserSubscribeAlertGoodsRecord;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: UserSubscribeAlertGoodsRecordMapper
+ * 创建者: JavaZou
+ * 创建时间:2026/1/6 15:47
+ */
+public interface UserSubscribeAlertGoodsRecordMapper extends BaseMapper<UserSubscribeAlertGoodsRecord> {
+}

+ 5 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDon.java

@@ -259,6 +259,11 @@ public class GoodsDon extends BaseEntity {
      */
     private Boolean isFilter;
 
+    /**
+     * 是否开启订阅提醒
+     */
+    private Boolean isSubscribeAlert;
+
     @AllArgsConstructor
     @Getter
     public enum SpecialType {

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

@@ -377,4 +377,9 @@ public class GoodsDonSku extends BaseEntity {
      * 是否禁止奈飞检测
      */
     private Boolean isBannedCheck;
+
+    /**
+     * 是否开启订阅提醒
+     */
+    private Boolean isSubscribeAlert;
 }

+ 25 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/UserSubscribeAlertGoodsRecord.java

@@ -0,0 +1,25 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: UserSubscribeAlertGoodsRecord
+ * 创建者: JavaZou
+ * 创建时间:2026/1/6 15:46
+ */
+@Getter
+@Setter
+public class UserSubscribeAlertGoodsRecord extends BaseEntity{
+	private Long goodsId;
+
+	private Long skuId;
+
+	private Long userId;
+
+	/**
+	 * 通知状态
+	 */
+	private Boolean status;
+}

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

@@ -268,6 +268,11 @@ public class ReqGoodsSkuInsert {
          * 是否禁止奈飞检测
          */
         private Boolean isBannedCheck;
+
+        /**
+         * 是否开启订阅提醒
+         */
+        private Boolean isSubscribeAlert;
     }
 
 }

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

@@ -174,4 +174,9 @@ public class ReqGoodsSpuInsert {
      * 特殊ip展示是否隐藏
      */
     private Boolean isFilter;
+
+    /**
+     * 是否开启订阅提醒
+     */
+    private Boolean isSubscribeAlert;
 }

+ 20 - 0
netflix-dao/src/main/java/com/cyksj/model/request/GoodsSubscribeAlertReq.java

@@ -0,0 +1,20 @@
+package com.cyksj.model.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: GoodsSubscribeAlertReq
+ * 创建者: JavaZou
+ * 创建时间:2026/1/6 16:46
+ */
+@Getter
+@Setter
+public class GoodsSubscribeAlertReq {
+	private Long goodsId;
+
+	private Long skuId;
+
+	private Long userId;
+}

+ 14 - 1
netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuFrontView.java

@@ -19,7 +19,8 @@ import java.util.List;
  */
 @Getter
 @Setter
-@SearchBean(tables = "goods_don_sku sku")
+@SearchBean(tables = "goods_don_sku sku",
+		where = ":condition:")
 @JsonInclude(JsonInclude.Include.NON_NULL)
 public class GoodsDonSkuFrontView {
 
@@ -317,4 +318,16 @@ public class GoodsDonSkuFrontView {
 	 * 是否禁止奈飞检测
 	 */
 	private Boolean isBannedCheck;
+
+	/**
+	 * 是否开启订阅提醒
+	 */
+	private Boolean isSubscribeAlert;
+
+	/**
+	 * 用户订阅状态
+	 * true 已开启
+	 */
+	@DbIgnore
+	private Boolean userSubscribeStatus = false;
 }

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/model/views/GoodsFrontListCategoryView.java

@@ -181,6 +181,19 @@ public class GoodsFrontListCategoryView {
 	@DbField("g.is_filter")
 	private Boolean filter;
 
+	/**
+	 * 是否开启订阅提醒
+	 */
+	@DbField("g.is_subscribe_alert")
+	private Boolean isSubscribeAlert;
+
+	/**
+	 * 用户订阅状态
+	 * true 已开启
+	 */
+	@DbIgnore
+	private Boolean userSubscribeStatus = false;
+
 	/**
 	 * 购买通知信息
 	 */

+ 6 - 0
netflix-dao/src/main/java/com/cyksj/model/views/GoodsFrontListView.java

@@ -158,6 +158,12 @@ public class GoodsFrontListView {
 	@DbField("g.is_third_goods")
 	private Boolean isThirdGoods;
 
+	/**
+	 * 是否开启订阅提醒
+	 */
+	@DbField("g.is_subscribe_alert")
+	private Boolean isSubscribeAlert;
+
 	/**
 	 * 购买通知信息
 	 */

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/model/views/HomeManagementFrontView.java

@@ -52,6 +52,19 @@ public class HomeManagementFrontView {
 	@DbField("g.is_filter")
 	private Boolean filter;
 
+	/**
+	 * 是否开启订阅提醒
+	 */
+	@DbField("g.is_subscribe_alert")
+	private Boolean isSubscribeAlert;
+
+	/**
+	 * 用户订阅状态
+	 * true 已开启
+	 */
+	@DbIgnore
+	private Boolean userSubscribeStatus = false;
+
 	@DbField("hc.url")
 	private String url;
 

+ 13 - 0
netflix-service/src/main/java/com/cyksj/service/goods/GoodsSubscribeAlertService.java

@@ -0,0 +1,13 @@
+package com.cyksj.service.goods;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: GoodsSubscribeAlertService
+ * 创建者: JavaZou
+ * 创建时间:2026/1/6 15:34
+ */
+public interface GoodsSubscribeAlertService {
+	void alertBeforeSubscribeByGoodsId(Long goodsId);
+
+	void alertBeforeSubscribeBySkuId(Long skuId);
+}

+ 15 - 0
netflix-service/src/main/java/com/cyksj/service/goods/UserSubscribeAlertGoodsRecordService.java

@@ -0,0 +1,15 @@
+package com.cyksj.service.goods;
+
+import com.cyksj.model.request.GoodsSubscribeAlertReq;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: UserSubscribeAlertGoodsRecordService
+ * 创建者: JavaZou
+ * 创建时间:2026/1/6 16:48
+ */
+public interface UserSubscribeAlertGoodsRecordService {
+	void userSubscribeAlert(GoodsSubscribeAlertReq goodsSubscribeAlertReq);
+
+	Boolean getUserSubscribeGoodsStatus(Long userId, Long goodsId, Long skuId);
+}

+ 153 - 0
netflix-service/src/main/java/com/cyksj/service/goods/impl/GoodsSubscribeAlertServiceImpl.java

@@ -0,0 +1,153 @@
+package com.cyksj.service.goods.impl;
+
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.constant.Constant;
+import com.cyksj.common.util.StringUtil;
+import com.cyksj.dto.RedisKey;
+import com.cyksj.mapper.GoodsDonMapper;
+import com.cyksj.mapper.GoodsDonSkuMapper;
+import com.cyksj.mapper.UserMapper;
+import com.cyksj.mapper.UserSubscribeAlertGoodsRecordMapper;
+import com.cyksj.model.entity.GoodsDon;
+import com.cyksj.model.entity.GoodsDonSku;
+import com.cyksj.model.entity.User;
+import com.cyksj.model.entity.UserSubscribeAlertGoodsRecord;
+import com.cyksj.redis.RedisService;
+import com.cyksj.service.goods.GoodsSubscribeAlertService;
+import com.cyksj.service.mail.MailService;
+import com.cyksj.service.sms.SmsService;
+import com.cyksj.service.template.TemplateCommonService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: GoodsSubscribeAlertServiceImpl
+ * 创建者: JavaZou
+ * 创建时间:2026/1/6 15:35
+ */
+@RequiredArgsConstructor
+@Service
+@Slf4j
+public class GoodsSubscribeAlertServiceImpl implements GoodsSubscribeAlertService {
+	private final UserSubscribeAlertGoodsRecordMapper userSubscribeAlertGoodsRecordMapper;
+
+	private final UserMapper userMapper;
+
+	private final GoodsDonMapper goodsDonMapper;
+
+	private final TemplateCommonService templateCommonService;
+
+	private final SmsService smsService;
+
+	private final MailService mailService;
+
+	private final GoodsDonSkuMapper skuMapper;
+
+	private final RedisService redisService;
+
+	@Override
+	public void alertBeforeSubscribeByGoodsId(Long goodsId) {
+		DateTime now = DateTime.now();
+		List<UserSubscribeAlertGoodsRecord> userSubscribeAlertGoodsRecords = userSubscribeAlertGoodsRecordMapper.selectList(Wrappers.lambdaQuery(UserSubscribeAlertGoodsRecord.class)
+				.eq(UserSubscribeAlertGoodsRecord::getGoodsId, goodsId)
+				.eq(UserSubscribeAlertGoodsRecord::getStatus, Boolean.FALSE)
+				.le(UserSubscribeAlertGoodsRecord::getCreatedTime, now));
+		if (userSubscribeAlertGoodsRecords.isEmpty()) {
+			return;
+		}
+		String key = RedisKey.GOODS_SUBSCRIBE_ALERT_KEY + goodsId;
+		if (!redisService.setNx(key, goodsId, 60L * 30)) {
+			return;
+		}
+		try {
+			GoodsDon goodsDon = goodsDonMapper.selectById(goodsId);
+			for (UserSubscribeAlertGoodsRecord userSubscribeAlertGoodsRecord : userSubscribeAlertGoodsRecords) {
+				userSubscribeAlertGoodsRecord.setStatus(Boolean.TRUE);
+				userSubscribeAlertGoodsRecordMapper.updateById(userSubscribeAlertGoodsRecord);
+
+				Long userId = userSubscribeAlertGoodsRecord.getUserId();
+				User user = userMapper.selectById(userId);
+				//微信用户
+				if (StrUtil.isNotBlank(user.getUnionid())) {
+					//发送微信通知模板
+					templateCommonService.sendUserSubscribeAlertMsg(user, String.format(Constant.GOODS_SUBSCRIBE_ALERT_WX_MSG, goodsDon.getTitle()));
+					continue;
+				}
+				//手机用户
+				if (StrUtil.isNotBlank(user.getLoginPhone())) {
+					String msg = String.format(Constant.GOODS_SUBSCRIBE_ALERT_MSG, goodsDon.getTitle(), goodsDon.getId());
+					try {
+						smsService.sendSmsToRelationUser(user.getId(), msg);
+					} catch (Exception e) {
+						log.error("发送商品订阅通知短信失败:{}", StringUtil.getErrorText(e));
+					}
+					continue;
+				}
+				//邮箱用户
+				String msg = String.format(Constant.GOODS_SUBSCRIBE_ALERT_EMAIL_MSG, goodsDon.getTitle(), goodsDon.getId());
+				mailService.sendGoodsSubscribeAlertEmail(msg, user.getEmail());
+			}
+		} finally {
+			if (redisService.hasKey(key)) {
+				redisService.del(key);
+			}
+		}
+	}
+
+	@Override
+	public void alertBeforeSubscribeBySkuId(Long skuId) {
+		DateTime now = DateTime.now();
+		List<UserSubscribeAlertGoodsRecord> userSubscribeAlertGoodsRecords = userSubscribeAlertGoodsRecordMapper.selectList(Wrappers.lambdaQuery(UserSubscribeAlertGoodsRecord.class)
+				.eq(UserSubscribeAlertGoodsRecord::getSkuId, skuId)
+				.eq(UserSubscribeAlertGoodsRecord::getStatus, Boolean.FALSE)
+				.le(UserSubscribeAlertGoodsRecord::getCreatedTime, now));
+		if (userSubscribeAlertGoodsRecords.isEmpty()) {
+			return;
+		}
+		GoodsDonSku sku = skuMapper.selectById(skuId);
+		GoodsDon goodsDon = goodsDonMapper.selectById(sku.getGoodsId());
+		String key = RedisKey.GOODS_SUBSCRIBE_ALERT_KEY + goodsDon.getId() + ":" + skuId;
+		if (!redisService.setNx(key, skuId, 60L * 30)) {
+			return;
+		}
+		try {
+			String title = goodsDon.getTitle() + sku.getSpecVal();
+			for (UserSubscribeAlertGoodsRecord userSubscribeAlertGoodsRecord : userSubscribeAlertGoodsRecords) {
+				userSubscribeAlertGoodsRecord.setStatus(Boolean.TRUE);
+				userSubscribeAlertGoodsRecordMapper.updateById(userSubscribeAlertGoodsRecord);
+
+				Long userId = userSubscribeAlertGoodsRecord.getUserId();
+				User user = userMapper.selectById(userId);
+				//微信用户
+				if (StrUtil.isNotBlank(user.getUnionid())) {
+					//发送微信通知模板
+					templateCommonService.sendUserSubscribeAlertMsg(user, String.format(Constant.GOODS_SUBSCRIBE_ALERT_WX_MSG, title));
+					continue;
+				}
+				//手机用户
+				if (StrUtil.isNotBlank(user.getLoginPhone())) {
+					String msg = String.format(Constant.GOODS_SUBSCRIBE_ALERT_MSG, title, goodsDon.getId());
+					try {
+						smsService.sendSmsToRelationUser(user.getId(), msg);
+					} catch (Exception e) {
+						log.error("发送商品规格订阅通知短信失败:{}", StringUtil.getErrorText(e));
+					}
+					continue;
+				}
+				//邮箱用户
+				String msg = String.format(Constant.GOODS_SUBSCRIBE_ALERT_EMAIL_MSG, title, goodsDon.getId());
+				mailService.sendGoodsSubscribeAlertEmail(msg, user.getEmail());
+			}
+		} finally {
+			if (redisService.hasKey(key)) {
+				redisService.del(key);
+			}
+		}
+	}
+}

+ 86 - 0
netflix-service/src/main/java/com/cyksj/service/goods/impl/UserSubscribeAlertGoodsRecordServiceImpl.java

@@ -0,0 +1,86 @@
+package com.cyksj.service.goods.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.mapper.GoodsDonMapper;
+import com.cyksj.mapper.GoodsDonSkuMapper;
+import com.cyksj.mapper.UserSubscribeAlertGoodsRecordMapper;
+import com.cyksj.model.entity.GoodsDon;
+import com.cyksj.model.entity.GoodsDonSku;
+import com.cyksj.model.entity.UserSubscribeAlertGoodsRecord;
+import com.cyksj.model.request.GoodsSubscribeAlertReq;
+import com.cyksj.service.goods.UserSubscribeAlertGoodsRecordService;
+import com.cyksj.service.user.UserBindRelationService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: UserSubscribeAlertGoodsRecordServiceImpl
+ * 创建者: JavaZou
+ * 创建时间:2026/1/6 16:48
+ */
+@Service
+@RequiredArgsConstructor
+public class UserSubscribeAlertGoodsRecordServiceImpl implements UserSubscribeAlertGoodsRecordService {
+
+	private final UserSubscribeAlertGoodsRecordMapper userSubscribeAlertGoodsRecordMapper;
+
+	private final UserBindRelationService userBindRelationService;
+
+	private final GoodsDonSkuMapper skuMapper;
+
+	private final GoodsDonMapper goodsDonMapper;
+
+	@Override
+	public void userSubscribeAlert(GoodsSubscribeAlertReq goodsSubscribeAlertReq) {
+		Long goodsId = goodsSubscribeAlertReq.getGoodsId();
+		Long skuId = goodsSubscribeAlertReq.getSkuId();
+		if (goodsId == null && skuId == null) {
+			throw BusinessRuntimeException.getInstance("操作异常");
+		}
+		if (goodsId != null) {
+			GoodsDon goodsDon = goodsDonMapper.selectById(goodsId);
+			if (goodsDon == null) {
+				throw BusinessRuntimeException.getInstance("该商品不存在");
+			}
+			if (!goodsDon.getIsSubscribeAlert()) {
+				throw BusinessRuntimeException.getInstance("该商品未开启到期提醒配置");
+			}
+			skuId = null;
+		}
+		if (skuId != null) {
+			GoodsDonSku sku = skuMapper.selectById(skuId);
+			if (sku == null) {
+				throw BusinessRuntimeException.getInstance("规格不存在");
+			}
+			if (!sku.getIsSubscribeAlert()) {
+				throw BusinessRuntimeException.getInstance("该规格未开启到期提醒配置");
+			}
+			goodsId = sku.getGoodsId();
+		}
+		Long userId = goodsSubscribeAlertReq.getUserId();
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		Integer selectCount = userSubscribeAlertGoodsRecordMapper.selectCount(Wrappers.lambdaQuery(UserSubscribeAlertGoodsRecord.class).in(UserSubscribeAlertGoodsRecord::getUserId, userIdList).eq(UserSubscribeAlertGoodsRecord::getStatus, Boolean.FALSE).eq(goodsId != null, UserSubscribeAlertGoodsRecord::getGoodsId, goodsId).eq(skuId != null, UserSubscribeAlertGoodsRecord::getSkuId, skuId));
+		if (selectCount > 0) {
+			throw BusinessRuntimeException.getInstance("您已开启到期提醒");
+		}
+		UserSubscribeAlertGoodsRecord record = new UserSubscribeAlertGoodsRecord();
+		record.setUserId(userId);
+		record.setGoodsId(goodsId);
+		record.setSkuId(skuId);
+		userSubscribeAlertGoodsRecordMapper.insert(record);
+	}
+
+	@Override
+	public Boolean getUserSubscribeGoodsStatus(Long userId, Long goodsId, Long skuId) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		Integer selectCount = userSubscribeAlertGoodsRecordMapper.selectCount(Wrappers.lambdaQuery(UserSubscribeAlertGoodsRecord.class)
+				.in(UserSubscribeAlertGoodsRecord::getUserId, userIdList).eq(UserSubscribeAlertGoodsRecord::getStatus, Boolean.FALSE)
+				.eq(goodsId != null, UserSubscribeAlertGoodsRecord::getGoodsId, goodsId)
+				.eq(skuId != null, UserSubscribeAlertGoodsRecord::getSkuId, skuId));
+		return selectCount > 0;
+	}
+}

+ 2 - 0
netflix-service/src/main/java/com/cyksj/service/mail/MailService.java

@@ -25,4 +25,6 @@ public interface MailService {
 	void sendCoursewareByGoodsId(Long goodsId, Long relationId, Long userId, String goodsTitle, String email) throws Exception;
 
 	void sendSpotifyUpgradeEmail(SpotifyUserUpgradeSubmitRecord.Status status, String email);
+
+	Boolean sendGoodsSubscribeAlertEmail(String email, String content);
 }

+ 19 - 0
netflix-service/src/main/java/com/cyksj/service/mail/impl/MailServiceImpl.java

@@ -205,6 +205,25 @@ public class MailServiceImpl implements MailService {
 		}
 	}
 
+	@Override
+	public Boolean sendGoodsSubscribeAlertEmail(String email, String content) {
+		try {
+			MimeMessage msg = mailSender.createMimeMessage();
+			MimeMessageHelper helper = new MimeMessageHelper(msg, true, "utf-8");
+			//标题
+			helper.setSubject("【崇宇科技】到货提醒");
+			//内容
+			helper.setText(content);
+			helper.setTo(email);
+			helper.setFrom(from);
+			mailSender.send(msg);
+		} catch (Exception e) {
+			log.error("发送邮件失败,error:{}", StringUtil.getErrorText(e));
+			return false;
+		}
+		return true;
+	}
+
 	private void sendMsgIfFailRetry(JavaMailSenderImpl sender, MimeMessage msg) {
 		try {
 			sender.send(msg);

+ 2 - 0
netflix-service/src/main/java/com/cyksj/service/template/TemplateCommonService.java

@@ -47,6 +47,8 @@ public interface TemplateCommonService {
 	 */
 	void sendUpdateAccountMsgNotify(GroupsRelationView relation) throws Exception;
 
+	void sendUserSubscribeAlertMsg(User user, String msg);
+
 	/**
 	 * Spotify账号到期通知模板
 	 */

+ 21 - 0
netflix-service/src/main/java/com/cyksj/service/template/impl/TemplateCommonServiceImpl.java

@@ -247,6 +247,27 @@ public class TemplateCommonServiceImpl implements TemplateCommonService {
 		}
 	}
 
+	@Override
+	public void sendUserSubscribeAlertMsg(User user, String msg) {
+		if (!EnvCommonService.active.equals(envCommonService.getEnv())) {
+			String openId = user.getOpenId();
+			if (StrUtil.isNotBlank(openId)) {
+				WxAppTemplateView wxAppTemplateView = this.getWxAppByUserId(openId, TemplateEnum.GOODS_SUBSCRIBE_ALERT.getDesc());
+				if (wxAppTemplateView != null) {
+					String url = String.format("%sauth/weChat?url=%s", envCommonService.getHost(), envCommonService.getDomain());
+					WxMpTemplateMessage templateMessage = new WxMpTemplateMessage()
+							.setToUser(openId)
+							.setTemplateId(wxAppTemplateView.getTemplateId())
+							.setUrl(url);
+					WxMpTemplateMessage.TemplateData data = templateMessage.getData();
+					data.setTime3(new WxMpTemplateData(DateUtil.format(DateTime.now(), "yyyy-MM-dd HH:mm:ss")));
+					data.setThing17(new WxMpTemplateData(user.getNickname()));
+					weChatService.sendTemplateMessage(weChatService.getAccessToken(wxAppTemplateView.getAppId()), Jsons.toJson(templateMessage));
+				}
+			}
+		}
+	}
+
 	public void sendUpdateAccountMsg(GroupsRelationView relation) throws Exception {
 		String url = String.format("%sauth/weChat?url=%syinhe/web/ticket", envCommonService.getHost(), envCommonService.getDomain());
 		User user = userMapper.selectById(relation.getUserId());

+ 36 - 4
netflix-web/src/main/java/com/cyksj/web/controller/goods/GoodsDonController.java

@@ -24,9 +24,11 @@ import com.cyksj.mapper.shop.YhShopGoodsSkuMapper;
 import com.cyksj.mapper.sys.SysConfigMapper;
 import com.cyksj.model.entity.*;
 import com.cyksj.model.request.ClickRecordReq;
+import com.cyksj.model.request.GoodsSubscribeAlertReq;
 import com.cyksj.model.views.*;
 import com.cyksj.redis.RedisService;
 import com.cyksj.service.goods.GoodsDonFrontService;
+import com.cyksj.service.goods.UserSubscribeAlertGoodsRecordService;
 import com.cyksj.service.home.ClickRecordFrontService;
 import com.cyksj.service.shop.YhShopFrontService;
 import com.cyksj.service.translations.BackInfoTranslationsFrontService;
@@ -96,6 +98,8 @@ public class GoodsDonController {
 
 	private final ApiVerifyService apiVerifyService;
 
+	private final UserSubscribeAlertGoodsRecordService userSubscribeAlertGoodsRecordService;
+
 	/**
      * 获取平台商品分类
      */
@@ -436,7 +440,9 @@ public class GoodsDonController {
 							}
 
 							views.setSpecs(beanSearcher.searchFirst(GoodsDonSpecFrontView.class, MapUtils.builder().field(GoodsDonSpecFrontView::getGoodsId, views.getId()).build()));
-							MapBuilder builder = MapUtils.builder().field(GoodsDonSkuFrontView::getGoodsId, views.getId()).field(GoodsDonSkuFrontView::getStatus, true);
+							//把开启订阅提醒的规格展示出来
+							String condition = "or (sku.status = 0 and sku.is_subscribe_alert)";
+							MapBuilder builder = MapUtils.builder().field(GoodsDonSkuFrontView::getGoodsId, views.getId()).put("condition", condition).field(GoodsDonSkuFrontView::getStatus, true);
 
 							if (views.getType() == 1) {
 								builder.orderBy(GoodsDonSkuFrontView::getSorted).asc();
@@ -551,6 +557,10 @@ public class GoodsDonController {
 			//设置规格优惠后的价格
 			sku.setChannelDiscount(discount);
 			setUserCouponSkuMoney(fUid, sku);
+			//查看用户是否已订阅到货通知
+			if (fUid != null && sku.getIsSubscribeAlert()) {
+				sku.setUserSubscribeStatus(userSubscribeAlertGoodsRecordService.getUserSubscribeGoodsStatus(fUid, null, sku.getId()));
+			}
 			return true;
 		}).collect(Collectors.toList()));
 		return GatewayResponse.SUCCESS.newBuilder().toResult(views);
@@ -736,7 +746,7 @@ public class GoodsDonController {
 					try {
 						o = redisService.get(key);
 						if (o == null) {
-							String condition = "g.deleted is true and g.status is true";
+							String condition = "g.deleted is true and g.status is true or (g.status = 0 and g.is_subscribe_alert)";
 							Long finalYhsId = yhsId;
 							if (yhsId != null) {
 								condition += String.format(" and g.id in (select distinct goods_id from yh_shop_goods_sku where yhs_id = %s and status is true and deleted is true) and g.is_distribute is true", yhsId);
@@ -1116,6 +1126,10 @@ public class GoodsDonController {
 					//推荐优惠券商品最低折扣
 					setHomeConfigGoodsCouponMoney(data);
 				}
+				//已登录 查看是否开启通知订阅
+				if (fUid != null && data.getIsSubscribeAlert()) {
+					data.setUserSubscribeStatus(userSubscribeAlertGoodsRecordService.getUserSubscribeGoodsStatus(fUid, data.getGoodsId(), null));
+				}
 				return true;
 			}).collect(Collectors.toList());
 			setHomeManageSpecific(list, fUid, dsUserId.get(), discount);
@@ -1254,13 +1268,17 @@ public class GoodsDonController {
 						if (o == null) {
 							DateTime now = DateTime.now();
 							MapBuilder mapBuilder = MapUtils.flatBuilder(request.getParameterMap());
+							//把开启订阅提醒的商品展示出来
+							String condition = "or (g.status = 0 and g.is_subscribe_alert)";
 							if (cgy != null) {
-								String condition = String.format(" and gcr.category = %s", cgy);
-								mapBuilder.put("condition", condition);
+								condition += String.format(" and gcr.category = %s", cgy);
 							}
 							if (isFilterGoodsIds) {
 								mapBuilder.field(GoodsFrontListCategoryView::getFilter, Boolean.FALSE);
 							}
+							if (StrUtil.isNotEmpty(condition)) {
+								mapBuilder.put("condition", condition);
+							}
 							List<GoodsFrontListCategoryView> goodsFrontListViews = beanSearcher.searchAll(GoodsFrontListCategoryView.class, mapBuilder.build());
 							List<Long> gptBuyGids = getGptMirrorGiveawayBuyGids();
 							goodsFrontListViews.forEach(data -> {
@@ -1328,6 +1346,9 @@ public class GoodsDonController {
 				if (fUid == null) {
 					setGoodsCouponMoney(data);
 				}
+				if (fUid != null && data.getIsSubscribeAlert()) {
+					data.setUserSubscribeStatus(userSubscribeAlertGoodsRecordService.getUserSubscribeGoodsStatus(fUid, data.getId(), null));
+				}
 				return true;
 			}).collect(Collectors.toList());
 			return GatewayResponse.SUCCESS.newBuilder().toResult(goodsFrontListViews);
@@ -1651,6 +1672,17 @@ public class GoodsDonController {
 		return GatewayResponse.SUCCESS.newBuilder().toResult();
 	}
 
+	/**
+	 * 用户开启到货提醒
+	 */
+	@PostMapping("/user/subscribe/alert")
+	public Result<String> userSubscribeAlert(@RequestBody GoodsSubscribeAlertReq goodsSubscribeAlertReq) throws Exception {
+		long userId = StpUserUtil.getLoginIdAsLong();
+		goodsSubscribeAlertReq.setUserId(userId);
+		userSubscribeAlertGoodsRecordService.userSubscribeAlert(goodsSubscribeAlertReq);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
 	/**
 	 * 获取平台销量
 	 * @param goodsId

+ 11 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/goods/CmsGoodsDonController.java

@@ -18,6 +18,7 @@ import com.cyksj.model.manage.views.GoodsDonViewer;
 import com.cyksj.model.views.CategoryGoodsCascadeView;
 import com.cyksj.model.views.GoodsDetailView;
 import com.cyksj.model.views.GoodsDonCategoryDetailView;
+import com.cyksj.service.goods.GoodsSubscribeAlertService;
 import com.cyksj.service.mange.CmsGoodsDonService;
 import com.cyksj.service.mange.GoodsDonCategoryService;
 import com.cyksj.service.mange.RefreshCacheService;
@@ -64,6 +65,8 @@ public class CmsGoodsDonController {
 
     private final BackInfoTranslationsService backInfoTranslationsService;
 
+    private final GoodsSubscribeAlertService goodsSubscribeAlertService;
+
     @GetMapping("/get")
     public Result<SearchResult<GoodsDonCategoryDetailView>> get(){
         SearchResult<GoodsDonCategoryDetailView> search = beanSearcher.search(GoodsDonCategoryDetailView.class, MapUtils.flatBuilder(request.getParameterMap())
@@ -141,6 +144,9 @@ public class CmsGoodsDonController {
     @SaCheckPermission("platform:update")
     @Log(module = "平台管理/编辑平台", businessType = BusinessType.PUT, isSaveRequestData = true)
     public Result<String> spuUpdate(@Validated @RequestBody ReqGoodsSpuInsert spu) throws Exception{
+        Long id = spu.getId();
+        GoodsDon dbGoodsDon = goodsDonService.getById(id);
+
         GoodsDon goods = new GoodsDon();
         BeanUtil.copyProperties(spu,goods);
         if (spu.getSpecialType() == null) {
@@ -176,6 +182,11 @@ public class CmsGoodsDonController {
             //平台 分类关系
             goodsDonCategoryService.handleGoodsRelate(goods.getId(), spu.getCategoryList());
             goodsDonService.updateById(goods);
+            //关闭订阅提醒 且已上架
+            if (spu.getStatus() && (dbGoodsDon.getIsSubscribeAlert() && !spu.getIsSubscribeAlert())) {
+                //通知之前 订阅该平台的用户
+                goodsSubscribeAlertService.alertBeforeSubscribeByGoodsId(spu.getId());
+            }
         }
         refreshCacheService.refreshYhHomePageCache();
         return GatewayResponse.SUCCESS.newBuilder().toResult();

+ 9 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/goods/CmsGoodsDonSkuController.java

@@ -14,6 +14,7 @@ import com.cyksj.model.entity.*;
 import com.cyksj.model.manage.request.ReqGoodsSkuInsert;
 import com.cyksj.model.request.CombinationSkuReq;
 import com.cyksj.model.views.DistributeSearchView;
+import com.cyksj.service.goods.GoodsSubscribeAlertService;
 import com.cyksj.service.mange.CmsGoodsDonService;
 import com.cyksj.service.mange.CmsGoodsDonSkuService;
 import com.cyksj.service.mange.CmsGoodsDonSpecService;
@@ -63,6 +64,8 @@ public class CmsGoodsDonSkuController {
 
     private final BackInfoTranslationsFrontService backInfoTranslationsFrontService;
 
+    private final GoodsSubscribeAlertService goodsSubscribeAlertService;
+
 
     /**
      * 新增 商品spec & sku
@@ -113,6 +116,7 @@ public class CmsGoodsDonSkuController {
     public Result<String> skuUpdate(@RequestBody GoodsDonSku sku) throws Exception {
         Long skuId = sku.getId();
         GoodsDonSku dbSku = goodsDonSkuService.getById(skuId);
+        Boolean dbIsSubscribeAlert = dbSku.getIsSubscribeAlert();
         Assert.notNull(dbSku, "规格不存在");
         Long goodsId = sku.getGoodsId();
         GoodsDon goods = goodsDonService.getById(goodsId);
@@ -184,6 +188,11 @@ public class CmsGoodsDonSkuController {
             goodsDonSkuService.setBelowPriceShopSkuDown(skuId, newPrice);
             //套餐包关联规格
             goodsDonSkuService.handleGptPackageSku(dbSku, sku);
+            //关闭订阅提醒 且已上架
+            if (sku.getStatus() && (dbIsSubscribeAlert && !sku.getIsSubscribeAlert())) {
+                //通知之前 订阅该平台的用户
+                goodsSubscribeAlertService.alertBeforeSubscribeBySkuId(sku.getId());
+            }
         }
         refreshCacheService.refreshYhHomePageCache();
         return GatewayResponse.SUCCESS.newBuilder().toResult();