123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- package com.webchat.ugc.service.redpacket;
- import com.webchat.common.constants.RedPacketConstants;
- import com.webchat.common.enums.ChatMessageTypeEnum;
- import com.webchat.common.enums.RedisKeyEnum;
- import com.webchat.common.enums.messagequeue.MessageBroadChannelEnum;
- import com.webchat.common.enums.payment.PaymentTransEventEnum;
- import com.webchat.common.enums.payment.PaymentTransTypeEnum;
- import com.webchat.common.exception.BusinessException;
- import com.webchat.common.service.RedisService;
- import com.webchat.common.service.messagequeue.producer.MessageQueueProducer;
- import com.webchat.common.util.DateUtils;
- import com.webchat.common.util.JsonUtil;
- import com.webchat.domain.dto.messagecard.MessageCardSendDTO;
- import com.webchat.domain.dto.payment.PaymentOrderCreateDTO;
- import com.webchat.domain.dto.payment.PaymentTransRequestDTO;
- import com.webchat.domain.vo.request.SendRedPacketRequestVO;
- import com.webchat.domain.vo.request.mess.ChatMessageRequestVO;
- import com.webchat.domain.vo.request.mess.MessageBaseVO;
- import com.webchat.domain.vo.response.RedPacketBaseVO;
- import com.webchat.domain.vo.response.RedPacketDetailVO;
- import com.webchat.ugc.config.MessageCardAccountConfig;
- import com.webchat.ugc.config.MessageCardTemplateConfig;
- import com.webchat.ugc.repository.dao.IRedPacketDAO;
- import com.webchat.ugc.repository.dao.IRedPacketRecordDAO;
- import com.webchat.ugc.repository.entity.RedPacketEntity;
- import com.webchat.ugc.service.PaymentService;
- import org.apache.commons.lang3.StringUtils;
- import org.redisson.api.RLock;
- import org.redisson.api.RedissonClient;
- import org.springframework.beans.BeanUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.cloud.context.config.annotation.RefreshScope;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import org.springframework.util.Assert;
- import java.math.BigDecimal;
- import java.math.RoundingMode;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Map;
- @RefreshScope
- @Service
- public class RedPacketService {
- @Autowired
- private IRedPacketDAO redPacketDAO;
- @Autowired
- private IRedPacketRecordDAO redPacketRecordDAO;
- @Autowired
- private RedisService redisService;
- @Autowired
- private PaymentService paymentService;
- @Autowired
- private MessageQueueProducer<Object, Long> messageQueueProducer;
- @Autowired
- private MessageCardAccountConfig messageCardAccountConfig;
- @Autowired
- private MessageCardTemplateConfig messageCardTemplateConfig;
- @Autowired
- private RedissonClient redissonClient;
- @Value("${red-packet.open-with}")
- private String openWith;
- /**
- * 发送包
- *
- * @param sendRedPacketRequest
- * @return
- */
- @Transactional
- public Long send(SendRedPacketRequestVO sendRedPacketRequest) {
- sendRedPacketRequest.setExpireDate(this.getRedPacketExpireDate());
- String sendUserId = sendRedPacketRequest.getSender();
- BigDecimal redPacketAmount = sendRedPacketRequest.getTotalMoney();
- /**
- * 1. 校验发包人账户余额
- */
- paymentService.validateAccountBalance(sendUserId, redPacketAmount);
- /**
- * 2. 申请分布式交易事务id, 申请创建一条交易订单
- */
- String transId = paymentService.transId();
- // 构造支付平台创建订单所需业务参数
- PaymentOrderCreateDTO paymentOrderCreateDTO = this.buildPaymentOrderCreateDTO(sendRedPacketRequest);
- String orderId = paymentService.createOrder(transId, paymentOrderCreateDTO);
- /**
- * 3. 扣除发包人账户余额(实际交易)
- */
- // 构造实际交易请求参数
- PaymentTransRequestDTO paymentTransRequest = this.buildPaymentTransRequestDTO(orderId, sendRedPacketRequest);
- boolean transResult = paymentService.doTrans(paymentTransRequest);
- if (!transResult) {
- // 回滚交易订单
- paymentService.doRollBack(transId);
- throw new BusinessException("发包发送失败,稍后重试!");
- }
- try {
- /**
- * 4. 持久化红包发送记录
- */
- RedPacketEntity redPacketEntity = this.buildRedPacketEntity(orderId, sendRedPacketRequest);
- redPacketEntity = redPacketDAO.save(redPacketEntity);
- /**
- * 5. 刷新红包详情redis缓存
- * 后续红包的信息要在IM客户端展示,为了保证后续请求性能,我们现将红包详情信息做一层redis缓存
- */
- this.refreshRedPacketDetailCache(redPacketEntity);
- this.initOpenRedPacketContextInfo(redPacketEntity);
- /**
- * 6. MQ广播通知websocket推送红包消息给接收人
- */
- MessageBaseVO<RedPacketBaseVO> chatMessage = this.buildMessageVOForSendRedPacket(redPacketEntity);
- messageQueueProducer.broadSend(MessageBroadChannelEnum.QUEUE_RED_PACKET_NOTIFY, chatMessage);
- /**
- * 7. 走“WebChat支付这个服务号推送用户消息认证”
- */
- this.doPushRedPacketSendPaymentOrderMessage(sendRedPacketRequest);
- return redPacketEntity.getId();
- } catch (Exception e) {
- // 交易成功,订单创建成功,但是业务侧处理失败,我们需要回滚用户交易订单
- // 回滚交易订单
- paymentService.doRollBack(transId);
- // 回滚redis数据
- // TODO
- }
- return null;
- }
- private void initOpenRedPacketContextInfo(RedPacketEntity redPacketEntity) {
- Long redPacketId = redPacketEntity.getId();
- int gradCount = redPacketEntity.getCount();
- // 设置红包金额(元)
- BigDecimal totalMoneyYun = redPacketEntity.getTotalMoney();
- // 设置红包金额(分)
- int totalMoneyFen = totalMoneyYun.multiply(new BigDecimal(100)).intValue();
- if (RedPacketConstants.OpenRedPacketWithEnum.LOCK.name().equals(openWith) ||
- RedPacketConstants.OpenRedPacketWithEnum.LUA.name().equals(openWith)) {
- // 初始化红包金额
- OpenRedPacketWithRedissonLockService openRedPacketService =
- (OpenRedPacketWithRedissonLockService) OpenRedPacketWithFactory.getService(
- RedPacketConstants.OpenRedPacketWithEnum.LOCK.name());
- openRedPacketService.initBalanceCache(redPacketId, totalMoneyFen);
- openRedPacketService.initGradCountCache(redPacketId, gradCount);
- } else if (RedPacketConstants.OpenRedPacketWithEnum.QUEUE.name().equals(openWith)) {
- // 预先生成好红包份额对应的金额
- OpenRedPacketWithQueueService openRedPacketService =
- (OpenRedPacketWithQueueService) OpenRedPacketWithFactory.getService(
- RedPacketConstants.OpenRedPacketWithEnum.QUEUE.name());
- openRedPacketService.preGeneratedRedPackets(redPacketId, totalMoneyFen, gradCount);
- }
- }
- /**
- * 更新红包的状态
- *
- * @param redPacketId
- * @param redPacketStatus
- * @return
- */
- @Transactional
- public boolean updateRedPacketStatus(Long redPacketId, RedPacketConstants.RedPacketStatus redPacketStatus) {
- RedPacketEntity entity = redPacketDAO.findById(redPacketId).orElse(null);
- Assert.notNull(entity, "红包状态更新失败:红包不存在!");
- entity.setStatus(redPacketStatus.getStatus());
- try {
- redPacketDAO.save(entity);
- this.refreshRedPacketDetailCache(entity);
- } catch (Exception e) {
- throw new BusinessException("红包状态更新失败:redis缓存数据刷新异常!");
- }
- return true;
- }
- /**
- * 红包拆分
- *
- * @param redPacketId
- * @param userId
- * @return
- */
- public String open(Long redPacketId, String userId) {
- int amount = OpenRedPacketWithFactory.getService(openWith)
- .open(redPacketId, userId);
- return new BigDecimal(amount).movePointLeft(2).setScale(2, RoundingMode.HALF_UP).toString();
- }
- /**
- * 红包发送成功,走webchtaPay服务号推送交易凭证消息卡片
- *------------------
- * @param requestVO
- */
- private void doPushRedPacketSendPaymentOrderMessage(SendRedPacketRequestVO requestVO) {
- MessageCardSendDTO messageCardSendDTO = new MessageCardSendDTO();
- messageCardSendDTO.setSender(messageCardAccountConfig.getPayment());
- messageCardSendDTO.setTemplateId(messageCardTemplateConfig.getRedPacketSend());
- messageCardSendDTO.setReceiver(requestVO.getSender());
- messageCardSendDTO.setTitle("扣费凭证");
- messageCardSendDTO.setLogo("https://coderutil.oss-cn-beijing.aliyuncs.com/bbs-image/file_c3a791ab87c5419b980bc27d9791bab6.png");
- messageCardSendDTO.setRedirectName("查看交易明细");
- messageCardSendDTO.setRedirectUrl("https://www.coderutil.com");
- Map<String, Object> vars = new HashMap<>();
- vars.put("amountTitle", "扣费金额");
- vars.put("amount", requestVO.getTotalMoney().toString());
- vars.put("tip", "红包发送,账户变更");
- vars.put("k1", "扣费服务");
- vars.put("k2", "支付方式");
- vars.put("v1", "红包服务");
- vars.put("v2", "WebChatPay线上支付");
- messageCardSendDTO.setVars(vars);
- /**
- * MQ广播通知connect服务,完成服务号消息卡内容websocket主动推送
- */
- messageQueueProducer.broadSend(MessageBroadChannelEnum.QUEUE_MESSAGE_CARD_PUSH, messageCardSendDTO);
- }
- private MessageBaseVO<RedPacketBaseVO> buildMessageVOForSendRedPacket(RedPacketEntity redPacketEntity) {
- MessageBaseVO<RedPacketBaseVO> chatMessage = new ChatMessageRequestVO();
- RedPacketBaseVO redPacketBaseVO = new RedPacketBaseVO();
- chatMessage.setSenderId(redPacketEntity.getSender());
- chatMessage.setReceiverId(redPacketEntity.getReceiver());
- chatMessage.setType(ChatMessageTypeEnum.RED_PACKET.getType());
- chatMessage.setMessageExt(redPacketBaseVO);
- redPacketBaseVO.setId(redPacketEntity.getId());
- redPacketBaseVO.setCover(redPacketEntity.getCover());
- redPacketBaseVO.setBlessing(redPacketEntity.getBlessing());
- return chatMessage;
- }
- private RedPacketDetailVO refreshRedPacketDetailCache(Long redPacketId) {
- RedPacketEntity entity = redPacketDAO.findById(redPacketId).orElse(null);
- return this.refreshRedPacketDetailCache(entity);
- }
- /**
- * 刷新红包缓存
- *
- * @param redPacketEntity
- * @return
- */
- private RedPacketDetailVO refreshRedPacketDetailCache(RedPacketEntity redPacketEntity) {
- if (redPacketEntity == null) {
- return null;
- }
- RedPacketDetailVO redPacketDetailVO = new RedPacketDetailVO();
- BeanUtils.copyProperties(redPacketEntity, redPacketDetailVO);
- redPacketDetailVO.setSendTime(redPacketEntity.getCreateDate().getTime());
- redPacketDetailVO.setExpireTime(redPacketEntity.getExpireDate().getTime());
- // 缓存红包详情
- String cacheKey = this.redPacketCacheKey(redPacketEntity.getId());
- redisService.set(cacheKey, JsonUtil.toJsonString(redPacketDetailVO),
- RedisKeyEnum.RED_PACKET_DETAIL_CACHE.getExpireTime());
- return redPacketDetailVO;
- }
- public RedPacketDetailVO getRedPacket(Long redPacketId) {
- String cacheKey = this.redPacketCacheKey(redPacketId);
- String cache = redisService.get(cacheKey);
- if (StringUtils.isNotBlank(cache)) {
- return JsonUtil.fromJson(cache, RedPacketDetailVO.class);
- }
- /**
- * 虽然红包缓存设置的2 * 24小时,且红包24小时内有效,但是不能完全依赖redis
- * 如果缓存失效,需要主动刷新
- */
- String refreshRedPacketCacheKey = RedisKeyEnum.LOCK_REFRESH_RED_PACKET_CACHE.getKey(String.valueOf(redPacketId));
- RLock lock = redissonClient.getLock(refreshRedPacketCacheKey);
- try {
- lock.lock();
- cache = redisService.get(cacheKey);
- if (StringUtils.isNotBlank(cache)) {
- return JsonUtil.fromJson(cache, RedPacketDetailVO.class);
- }
- return this.refreshRedPacketDetailCache(redPacketId);
- } catch (Exception e) {
- // TODO
- } finally {
- if (lock.isLocked() && lock.isHeldByCurrentThread()) {
- lock.unlock();
- }
- }
- return null;
- }
- private String redPacketCacheKey(Long id) {
- return RedisKeyEnum.RED_PACKET_DETAIL_CACHE.getKey(String.valueOf(id));
- }
- private RedPacketEntity buildRedPacketEntity(String orderId, SendRedPacketRequestVO sendRedPacketRequestVO) {
- RedPacketEntity entity = new RedPacketEntity();
- BeanUtils.copyProperties(sendRedPacketRequestVO, entity);
- entity.setOrderId(orderId);
- entity.setStatus(RedPacketConstants.RedPacketStatus.RUNNING.getStatus());
- return entity;
- }
- /**
- * 构造创建交易订单业务参数
- *
- * @return
- */
- private PaymentOrderCreateDTO buildPaymentOrderCreateDTO(SendRedPacketRequestVO sendRedPacketRequest) {
- PaymentOrderCreateDTO dto = new PaymentOrderCreateDTO();
- dto.setAmount(sendRedPacketRequest.getTotalMoney());
- dto.setSourceAccount(sendRedPacketRequest.getSender());
- dto.setTargetAccount(sendRedPacketRequest.getReceiver());
- dto.setEventType(PaymentTransEventEnum.RED_PACKET.getTransEvent());
- dto.setExpireDate(sendRedPacketRequest.getExpireDate());
- dto.setBillType(PaymentTransTypeEnum.EXPENSES.getTransType());
- dto.setDescription("红包发送订单");
- return dto;
- }
- private Date getRedPacketExpireDate() {
- return DateUtils.addDays(new Date(), 1);
- }
- private PaymentTransRequestDTO buildPaymentTransRequestDTO(String orderId, SendRedPacketRequestVO sendRedPacketRequest) {
- PaymentTransRequestDTO dto = new PaymentTransRequestDTO();
- dto.setOrderId(orderId);
- dto.setAmount(sendRedPacketRequest.getTotalMoney());
- dto.setTransType(PaymentTransTypeEnum.EXPENSES.getTransType());
- dto.setTransDetail("红包发送");
- dto.setTransEvent(PaymentTransEventEnum.RED_PACKET.getTransEvent());
- dto.setSourceUserId(sendRedPacketRequest.getSender());
- dto.setTargetUserId(sendRedPacketRequest.getReceiver());
- return dto;
- }
- }
|