CouponFontServiceImpl.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. package com.cyksj.service.coupon.impl;
  2. import cn.hutool.core.date.DateTime;
  3. import cn.hutool.core.date.DateUtil;
  4. import cn.hutool.core.lang.Assert;
  5. import cn.hutool.core.util.StrUtil;
  6. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  7. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  8. import com.cyksj.common.constant.Constant;
  9. import com.cyksj.common.exception.BusinessRuntimeException;
  10. import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
  11. import com.cyksj.common.util.Jsons;
  12. import com.cyksj.enums.GatewayApiCode;
  13. import com.cyksj.mapper.OrderDonMapper;
  14. import com.cyksj.mapper.UserMapper;
  15. import com.cyksj.mapper.channel.ChannelPopularizeMapper;
  16. import com.cyksj.mapper.manage.coupon.*;
  17. import com.cyksj.model.entity.*;
  18. import com.cyksj.model.request.ChannelPopularizeCouponReq;
  19. import com.cyksj.model.request.CouponCollectReq;
  20. import com.cyksj.model.views.*;
  21. import com.cyksj.service.coupon.CouponFontService;
  22. import com.ejlchina.searcher.BeanSearcher;
  23. import com.ejlchina.searcher.param.Operator;
  24. import com.ejlchina.searcher.util.MapUtils;
  25. import lombok.RequiredArgsConstructor;
  26. import lombok.extern.slf4j.Slf4j;
  27. import org.springframework.dao.DuplicateKeyException;
  28. import org.springframework.stereotype.Service;
  29. import org.springframework.transaction.annotation.Transactional;
  30. import java.math.BigDecimal;
  31. import java.util.*;
  32. /*
  33. *项目名: netflix
  34. *文件名: CouponFontServiceImpl
  35. *创建者: JavaZou
  36. *创建时间:2022/11/18 13:34
  37. */
  38. @Service
  39. @RequiredArgsConstructor
  40. @Slf4j
  41. public class CouponFontServiceImpl implements CouponFontService {
  42. private final CouponMapper couponMapper;
  43. private final CouponActiveMapper couponActiveMapper;
  44. private final CouponUserMapper couponUserMapper;
  45. private final BeanSearcher beanSearcher;
  46. private final CouponSkuMapper couponSkuMapper;
  47. private static final GlobalThreadPoolTaskExecutor TASK_EXECUTOR = GlobalThreadPoolTaskExecutor.getInstance();
  48. private final CouponRecommendMapper recommendMapper;
  49. private final ChannelPopularizeMapper channelPopularizeMapper;
  50. private final UserMapper userMapper;
  51. private final OrderDonMapper orderDonMapper;
  52. @Override
  53. @Transactional(rollbackFor = Throwable.class)
  54. public void collectCoupon(CouponCollectReq couponCollectReq, Long userId) {
  55. collectCommonCheck(couponCollectReq.getCouponId(), userId);
  56. CouponActive couponActive = couponActiveMapper.selectById(couponCollectReq.getCouponActiveId());
  57. if (couponActive == null) {
  58. throw BusinessRuntimeException.getInstance("该发放优惠券不存在");
  59. }
  60. if (couponActive.getRemainNum() <= 0) {
  61. throw BusinessRuntimeException.getInstance("该优惠券已领取完");
  62. }
  63. receiveCouponUser(couponCollectReq.getCouponId(), userId, CouponUser.Channel.center);
  64. if (couponActive != null) {
  65. //发放优惠券记录剩余数量
  66. Integer remainNum = couponActive.getRemainNum();
  67. Integer update = couponActiveMapper.subRemainNum(couponActive.getId(), remainNum);
  68. if (update != 1) {
  69. throw BusinessRuntimeException.getInstance("系统繁忙,请稍后再试..");
  70. }
  71. }
  72. }
  73. @Override
  74. @Transactional(rollbackFor = Throwable.class)
  75. public void collectRecommendCoupon(CouponCollectReq couponCollectReq, Long userId) {
  76. collectCommonCheck(couponCollectReq.getCouponId(), userId);
  77. CouponRecommend couponRecommend = recommendMapper.selectOne(Wrappers.lambdaQuery(CouponRecommend.class).eq(CouponRecommend::getCouponId, couponCollectReq.getCouponId()).last("limit 1"));
  78. if (couponRecommend == null || !couponRecommend.getDeleted() || !couponRecommend.getStatus()) {
  79. return;
  80. }
  81. Date upTime = couponRecommend.getUpTime();
  82. Date downTime = couponRecommend.getDownTime();
  83. if (upTime != null && upTime.after(DateTime.now())) {
  84. return;
  85. }
  86. if (downTime != null && downTime.before(DateTime.now())) {
  87. return;
  88. }
  89. receiveCouponUser(couponCollectReq.getCouponId(), userId, CouponUser.Channel.recommend);
  90. }
  91. @Override
  92. @Transactional(rollbackFor = Throwable.class)
  93. public void collectWholeRecommendCoupon(Long userId) {
  94. CouponNewUserView couponNewUserView = beanSearcher.searchFirst(CouponNewUserView.class, MapUtils.builder().field(CouponNewUserView::getUserId, userId).op(Operator.Equal).build());
  95. Boolean isNewUser = true;
  96. if (couponNewUserView != null) {
  97. isNewUser = false;
  98. }
  99. //查询所有可领取的推荐优惠券
  100. List<CouponAvailableRecommendView> couponAvailableRecommendViews = recommendMapper.getRecommendCouponList(userId, isNewUser);
  101. couponAvailableRecommendViews.stream().forEach(data -> {
  102. //领取优惠券
  103. CouponUser couponUser = new CouponUser();
  104. couponUser.setUserId(userId);
  105. couponUser.setCouponId(data.getCouponId());
  106. //推荐渠道
  107. couponUser.setChannel(CouponUser.Channel.recommend);
  108. couponUser.setStatus(CouponUser.Status.unused);
  109. couponUser.setRecommendId(data.getRecommendId());
  110. //优惠券有效时间
  111. this.updateCouponUserValidTime(data.getCouponId(), couponUser);
  112. try {
  113. couponUserMapper.insert(couponUser);
  114. } catch (DuplicateKeyException e) {
  115. //插入失败继续插入 不影响
  116. log.info("用户:{}一键领取推荐优惠券{}插入数据错误", userId, data.getCouponId());
  117. }
  118. });
  119. }
  120. @Override
  121. public Page<CouponUserView> getAvailableCouponList(Long skuId, Long couponId, Long userId, Boolean isRenew, Long start, Long limit) {
  122. DateTime now = DateTime.now();
  123. Long goodsId = null;
  124. //实物还是虚物
  125. GoodsDonSkuView goodsDonSkuView = beanSearcher.searchFirst(GoodsDonSkuView.class, MapUtils.builder().field(GoodsDonSkuView::getSkuId, skuId).op(Operator.Equal).build());
  126. if (goodsDonSkuView == null) {
  127. return new Page<>();
  128. }
  129. BigDecimal skuMoney = goodsDonSkuView.getMoney();
  130. if (goodsDonSkuView.getType() == 2) {
  131. goodsId = goodsDonSkuView.getGoodsId();
  132. }
  133. Page<CouponUserView> availableCouponList = couponUserMapper.getAvailableCouponList(skuId, skuMoney, couponId, goodsId, userId, isRenew, new Page<>(start, limit));
  134. Optional.ofNullable(availableCouponList).ifPresent(page->{
  135. page.getRecords().stream().forEach(data->{
  136. String str = couponSkuMapper.selectGoodsSkuStr(data.getCouponId());
  137. data.setGoodsSkuStr(str.replaceAll(",", "/").replaceAll(";", ""));
  138. List<CouponSkuView> couponSkus = couponSkuMapper.getGoodsSkuByCouponId(data.getCouponId());
  139. try {
  140. data.setGoodsSkuIds(Jsons.toJson(couponSkus));
  141. } catch (Exception e) {
  142. }
  143. if (!data.getIsAvailable()) {
  144. data.setReason(String.format("仅限%s", data.getGoodsSkuStr()));
  145. if (data.getUseScope() == Coupon.UseScope.renew) {
  146. data.setReason(String.format("%s续费时可使用", data.getReason()));
  147. } else if (data.getUseScope() == Coupon.UseScope.orders) {
  148. data.setReason(String.format("%s下单时可使用", data.getReason()));
  149. }
  150. }
  151. if (goodsDonSkuView.getType() == 2 && data.getType() == Coupon.Type.reduce) {
  152. if (data.getIsAvailable() && skuMoney.compareTo(data.getReduceCondition()) < 0) {
  153. data.setIsAvailable(false);
  154. data.setReason(String.format("满%s可用", data.getReduceCondition().divide(BigDecimal.valueOf(100))));
  155. }
  156. }
  157. if (isRenew != null && isRenew && data.getUseScope() == Coupon.UseScope.orders) {
  158. data.setReason("仅下单可使用");
  159. }
  160. //为了重新校验
  161. data.setCouponUserStatus(CouponUser.Status.unused);
  162. this.setCouponUserStatus(data, now);
  163. });
  164. });
  165. if (goodsDonSkuView.getType() == 2) {
  166. availableCouponList.getRecords().sort(Comparator.comparing(CouponUserView::getIsAvailable).reversed());
  167. }
  168. return availableCouponList;
  169. }
  170. /**
  171. * 设置用户优惠券状态
  172. */
  173. @Override
  174. public void setCouponUserStatus(CouponUserView data, DateTime now) {
  175. Boolean isFlag = false;
  176. if (data.getCouponUserStatus() == CouponUser.Status.unused) {
  177. if (data.getExpiryStatus() != null && !data.getExpiryStatus()) {
  178. data.setCouponUserStatus(CouponUser.Status.invalid);
  179. data.setIsAvailable(false);
  180. data.setReason("该优惠券已失效");
  181. isFlag = true;
  182. } else if (now.isAfter(data.getValidEndTime())) {
  183. data.setCouponUserStatus(CouponUser.Status.expired);
  184. data.setIsAvailable(false);
  185. data.setReason("该优惠券已过期");
  186. isFlag = true;
  187. } else if (now.isBefore(data.getValidStartTime())) {
  188. data.setCouponUserStatus(CouponUser.Status.notEffective);
  189. data.setIsAvailable(false);
  190. data.setReason("该优惠券未生效");
  191. isFlag = true;
  192. }
  193. }
  194. if (isFlag) {
  195. TASK_EXECUTOR.execute(() -> {
  196. if (data.getCouponUserStatus() != CouponUser.Status.notEffective) {
  197. CouponUser couponUser = new CouponUser();
  198. couponUser.setId(data.getId());
  199. couponUser.setStatus(data.getCouponUserStatus());
  200. couponUserMapper.updateById(couponUser);
  201. }
  202. });
  203. }
  204. }
  205. @Override
  206. public Integer collectRecommendCouponNum(Long userId, Boolean isNewUser) {
  207. List<CouponAvailableRecommendView> recommendCouponList = Optional.ofNullable(recommendMapper.getRecommendCouponList(userId, isNewUser)).orElse(new ArrayList<>());
  208. return recommendCouponList.size();
  209. }
  210. public void collectCommonCheck(Long couponId, Long userId) {
  211. Coupon coupon = couponMapper.selectById(couponId);
  212. if (coupon == null || !coupon.getDeleted()) {
  213. throw BusinessRuntimeException.getInstance("该优惠券不存在");
  214. }
  215. //是否可领取
  216. if (coupon.getScope() == Coupon.Scope.newUser) {
  217. //注册后从没产生过虚拟订单的用户,产生过退款后也不是新用户
  218. CouponNewUserView couponNewUserView = beanSearcher.searchFirst(CouponNewUserView.class, MapUtils.builder().field(CouponNewUserView::getUserId, userId).op(Operator.Equal).build());
  219. if (couponNewUserView != null) {
  220. throw BusinessRuntimeException.getInstance("该优惠券只能新用户才能领取");
  221. }
  222. }
  223. CouponUser couponUser = couponUserMapper.selectOne(Wrappers.lambdaQuery(CouponUser.class).eq(CouponUser::getUserId, userId).eq(CouponUser::getCouponId, couponId));
  224. if (couponUser != null) {
  225. if (couponUser.getChannel() == CouponUser.Channel.exCode) {
  226. throw BusinessRuntimeException.getInstance("您已兑换过该优惠券..");
  227. }
  228. throw BusinessRuntimeException.getInstance("您已领取该优惠券..");
  229. }
  230. }
  231. @Override
  232. public void updateCouponUserValidTime(Long couponId, CouponUser couponUser) {
  233. Coupon entity = couponMapper.getCouponById(couponId);
  234. Optional.ofNullable(entity).ifPresent(coupon -> {
  235. if (coupon.getIsValidTime()) {
  236. couponUser.setValidStartTime(coupon.getValidStartTime());
  237. couponUser.setValidEndTime(coupon.getValidEndTime());
  238. return;
  239. }
  240. couponUser.setValidStartTime(DateTime.now());
  241. if (coupon.getValidHours() > 0) {
  242. couponUser.setValidEndTime(DateUtil.offsetHour(couponUser.getValidStartTime(), coupon.getValidHours()));
  243. return;
  244. }
  245. couponUser.setValidEndTime(DateUtil.offsetDay(couponUser.getValidStartTime(), coupon.getValidDays()));
  246. });
  247. }
  248. @Override
  249. public void updateOrderDonCouponStatus(OrderDon orderDon) {
  250. log.info("订单:{}退款,优惠券返还start...", orderDon.getId());
  251. CouponUser couponUser = couponUserMapper.selectById(orderDon.getCouponUserId());
  252. if (CouponUser.Channel.exCode == couponUser.getChannel()) {
  253. //兑换码兑换的优惠券退款时,直接删除
  254. couponUserMapper.deleteById(couponUser.getId());
  255. } else {
  256. CouponUserView couponUserView = beanSearcher.searchFirst(CouponUserView.class, MapUtils.builder()
  257. .field(CouponUserView::getCouponId, couponUser.getCouponId()).op(Operator.Equal)
  258. .field(CouponUserView::getUserId, orderDon.getUserId()).op(Operator.Equal)
  259. .build());
  260. couponUserView.setCouponUserStatus(CouponUser.Status.unused);
  261. this.setCouponUserStatus(couponUserView, DateTime.now());
  262. couponUser.setStatus(couponUserView.getCouponUserStatus());
  263. couponUserMapper.updateById(couponUser);
  264. }
  265. log.info("订单:{}退款,优惠券返还end...", orderDon.getId());
  266. }
  267. @Override
  268. public void receiveChannelPopularizeCoupon(ChannelPopularizeCouponReq req) {
  269. Long userId = req.getUserId();
  270. Long popularizeId = req.getPopularizeId();
  271. Long couponId = req.getCouponId();
  272. ChannelPopularize channelPopularize = checkPopularizeChannelCoupon(userId, popularizeId);
  273. String couponIds = channelPopularize.getCouponIds();
  274. List<Long> couponIdList = null;
  275. try {
  276. couponIdList = Jsons.parseList(couponIds, Long.class);
  277. if (!couponIdList.contains(couponId)) {
  278. throw BusinessRuntimeException.getInstance("链接未关联该优惠券");
  279. }
  280. } catch (Exception e) {
  281. throw BusinessRuntimeException.getInstance("链接未关联该优惠券");
  282. }
  283. //优惠券领取状态校验
  284. collectCommonCheck(couponId, userId);
  285. //领取优惠券
  286. receiveCouponUser(couponId, userId, CouponUser.Channel.channel_popularize);
  287. }
  288. @Override
  289. public void collectAllChannelCoupons(long userId, Long popularizeId) {
  290. ChannelPopularize channelPopularize = checkPopularizeChannelCoupon(userId, popularizeId);
  291. String couponIds = channelPopularize.getCouponIds();
  292. List<Long> couponIdList = null;
  293. try {
  294. couponIdList = Jsons.parseList(couponIds, Long.class);
  295. } catch (Exception e) {
  296. throw BusinessRuntimeException.getInstance("链接未关联优惠券");
  297. }
  298. //一键领取优惠券
  299. couponIdList.forEach(couponId -> {
  300. try {
  301. receiveCouponUser(couponId, userId, CouponUser.Channel.channel_popularize);
  302. } catch (Exception e) {
  303. }
  304. });
  305. }
  306. public ChannelPopularize checkPopularizeChannelCoupon(Long userId, Long popularizeId) {
  307. //是否是该渠道来源新用户
  308. User user = userMapper.selectById(userId);
  309. if (user == null) {
  310. throw BusinessRuntimeException.getGatewayApiCode(GatewayApiCode.CMS_USER_NO_FIND);
  311. }
  312. if (!user.getPopularizeId().equals(popularizeId)) {
  313. throw BusinessRuntimeException.getInstance("渠道推广新用户才能领取该优惠券");
  314. }
  315. //渠道链接是否存在
  316. ChannelPopularize channelPopularize = channelPopularizeMapper.selectById(popularizeId);
  317. Assert.notNull(channelPopularize, "链接不存在");
  318. //是否关联该优惠券
  319. String couponIds = channelPopularize.getCouponIds();
  320. if (StrUtil.isEmpty(couponIds)) {
  321. throw BusinessRuntimeException.getInstance("链接未关联优惠券");
  322. }
  323. return channelPopularize;
  324. }
  325. /**
  326. * 领取优惠券
  327. */
  328. public void receiveCouponUser(Long couponId, Long userId, CouponUser.Channel channel) {
  329. CouponUser couponUser = new CouponUser();
  330. couponUser.setUserId(userId);
  331. couponUser.setCouponId(couponId);
  332. couponUser.setChannel(channel);
  333. couponUser.setStatus(CouponUser.Status.unused);
  334. //优惠券有效时间
  335. this.updateCouponUserValidTime(couponId, couponUser);
  336. try {
  337. couponUserMapper.insert(couponUser);
  338. } catch (DuplicateKeyException e) {
  339. log.info("用户:{}重复领取优惠券{}插入数据错误", userId, couponId);
  340. }
  341. }
  342. @Override
  343. public CouponUserView getNewUserWelfareCoupons(long userId,Long couponId) {
  344. return couponUserMapper.getNewUserWelfareCouponsByUserId(userId, couponId);
  345. }
  346. @Override
  347. public CouponUserView getCouponDetailById(long userId, Long couponId) {
  348. CouponUserView couponUserView = couponUserMapper.getCouponDetailById(userId, couponId);
  349. if (couponUserView != null) {
  350. String str = couponSkuMapper.selectGoodsSkuStr(couponUserView.getCouponId());
  351. couponUserView.setGoodsSkuStr(str.replaceAll(",", "/").replaceAll(";", ""));
  352. }
  353. return couponUserView;
  354. }
  355. @Override
  356. public CouponUserView receiveCoupon(long userId, Long couponId, CouponUser.Channel channel) {
  357. try {
  358. receiveCouponUser(couponId, userId, channel);
  359. } catch (Exception e) {
  360. return null;
  361. }
  362. return getCouponDetailById(userId, couponId);
  363. }
  364. @Override
  365. public List<CouponView> getGoodsUserActivityCoupons(long userId) throws Exception {
  366. UserActivityDistributeDetailView goodsActivity = getGoodsActivity();
  367. if (goodsActivity == null) {
  368. return null;
  369. }
  370. String couponIds = goodsActivity.getCouponIds();
  371. List<Long> couponIdList = Jsons.parseList(couponIds, Long.class);
  372. //是否领取过
  373. Integer selectCount = couponUserMapper.selectCount(Wrappers.lambdaQuery(CouponUser.class).in(CouponUser::getCouponId, couponIdList).eq(CouponUser::getUserId, userId));
  374. if (selectCount == couponIdList.size()) {
  375. return null;
  376. }
  377. //未领取
  378. //是否购买过
  379. List<Long> skuIdList = couponMapper.getCouponSkuIds(couponIdList);
  380. Integer orderCount = orderDonMapper.selectCount(Wrappers.lambdaQuery(OrderDon.class).eq(OrderDon::getUserId, userId).notIn(OrderDon::getStatus, Constant.noOrderAllStatus).in(OrderDon::getSkuId, skuIdList));
  381. if (orderCount == 0) {
  382. return null;
  383. }
  384. List<CouponView> couponViews = beanSearcher.searchAll(CouponView.class, MapUtils.builder().field(CouponView::getCouponId, couponIdList).op(Operator.InList).orderBy(CouponView::getCouponId).build());
  385. return couponViews;
  386. }
  387. @Override
  388. public void collectAllGoodsActivityCoupons(long userId) throws Exception {
  389. UserActivityDistributeDetailView goodsActivity = getGoodsActivity();
  390. if (goodsActivity != null) {
  391. String couponIds = goodsActivity.getCouponIds();
  392. List<Long> couponIdList = Jsons.parseList(couponIds, Long.class);
  393. //是否领取过
  394. Integer selectCount = couponUserMapper.selectCount(Wrappers.lambdaQuery(CouponUser.class).in(CouponUser::getCouponId, couponIdList).eq(CouponUser::getUserId, userId));
  395. if (selectCount != couponIdList.size()) {
  396. //未领取
  397. //是否购买过
  398. List<Long> skuIdList = couponMapper.getCouponSkuIds(couponIdList);
  399. Integer orderCount = orderDonMapper.selectCount(Wrappers.lambdaQuery(OrderDon.class).eq(OrderDon::getUserId, userId).notIn(OrderDon::getStatus, Constant.noOrderAllStatus).in(OrderDon::getSkuId, skuIdList));
  400. if (orderCount != 0) {
  401. //一键领取优惠券
  402. couponIdList.forEach(couponId -> {
  403. try {
  404. receiveCouponUser(couponId, userId, CouponUser.Channel.ptfq);
  405. } catch (Exception e) {
  406. }
  407. });
  408. }
  409. }
  410. }
  411. }
  412. public UserActivityDistributeDetailView getGoodsActivity() {
  413. String dateStr = DateTime.now().toString();
  414. String time = String.format(" and (ua.start_time is null or (ua.start_time < '%s' and ua.end_time > '%s'))", dateStr, dateStr);
  415. UserActivityDistributeDetailView goodsActivity = beanSearcher.searchFirst(UserActivityDistributeDetailView.class, MapUtils.builder()
  416. .field(UserActivityDistributeDetailView::getType, 3)
  417. .put("time", time)
  418. .build());
  419. return goodsActivity;
  420. }
  421. }