MessageBizService.java 8.79 KB
package cn.fw.hermes.service.biz;

import cn.fw.common.util.MessageCodeBuilder;
import cn.fw.hermes.common.constant.ActionStatusEnum;
import cn.fw.hermes.common.constant.RedisKey;
import cn.fw.hermes.common.constant.RedisKeyCache;
import cn.fw.hermes.common.utils.StringUtils;
import cn.fw.hermes.common.utils.UUIDUtils;
import cn.fw.hermes.common.utils.im.RedisKeyUtil;
import cn.fw.hermes.common.utils.im.TXCloudUtils;
import cn.fw.hermes.domain.db.Account;
import cn.fw.hermes.domain.db.SysMsg;
import cn.fw.hermes.domain.dto.AdminConfigDto;
import cn.fw.hermes.domain.dto.MsgBodyDto;
import cn.fw.hermes.domain.dto.MsgSubject;
import cn.fw.hermes.domain.enums.UserTypeEnum;
import cn.fw.hermes.sdk.api.para.*;
import cn.fw.hermes.service.data.AccountService;
import cn.fw.hermes.service.data.MessageService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.Optional;

import static cn.fw.common.businessvalidator.Validator.BV;

/**
 * @Author: Chenery
 * @Date: 2021/1/29 15:22
 */
@RequiredArgsConstructor
@Slf4j
@Service
public class MessageBizService {

    private final StringRedisTemplate redisTemplate;
    private final AccountService accountService;
    private final AccountBizService accountBizService;
    private final MessageBusinessTypeBizService messageBusinessTypeBizService;
    private final MessageService messageService;
    private final AdminConfigDto adminConfigDto;


    /**
     * 发送同步消息
     *
     * @param msgPara 消息对象
     * @return 发送结果
     */
    public Boolean synSendMsg(MsgParamCondition msgPara) {
        MsgBodyDto msgBodyDto = this.getMsgInfo(msgPara);
        MsgSubject msgSubject = new MsgSubject();
        msgSubject.setCondition(msgPara);
        msgSubject.setMsgBodyDto(msgBodyDto);
        return this.sendMsg(msgSubject);
    }

    /**
     * 发送异步消息
     * 将待发送消息放入redis有序队列
     * 定时调度任务进行消费
     *
     * @param condition 消息对象
     * @return messageId 消息唯一识别码
     */
    public String asynSendMsg(@NonNull MsgParamCondition condition) {
        String messageId = UUIDUtils.newRandomUUid();
        condition.setMessageId(messageId);
        MsgBodyDto msgBodyDto = this.getMsgInfo(condition);
        MsgSubject msgSubject = new MsgSubject();
        msgSubject.setCondition(condition);
        msgSubject.setMsgBodyDto(msgBodyDto);
        redisTemplate.opsForList()
                .leftPush(RedisKey.ASYN_MESSAGE_TARGET.getCode(), JSON.toJSONString(msgSubject));
        return messageId;
    }

    /**
     * 弃用接口的同步消息发送方法
     * 消息接收人默认为业务人员
     *
     * @param msgPara 消息对象
     * @return 发送结果
     */
    public Boolean synSendMsg(@NonNull MsgPara msgPara) {
        MsgParamCondition msgParamCondition = new MsgParamCondition();
        BeanCopier beanCopier = BeanCopier.create(MsgPara.class, MsgParamCondition.class, false);
        beanCopier.copy(msgPara, msgParamCondition, null);
        //消息接收人默认为业务人员
        msgParamCondition.setIsStaff(true);
        return this.synSendMsg(msgParamCondition);
    }


    /**
     * 发送消息主实现方法
     *
     * @param msgSubject 消息主体
     * @return 发送结果
     */
    public Boolean sendMsg(@NonNull MsgSubject msgSubject) {
        MsgParamCondition msgPara = msgSubject.getCondition();
        MsgBodyDto msgBodyDto = msgSubject.getMsgBodyDto();
        Map<String, Object> info = null;
        //是否支持离线
        if (msgPara.isUseOfflineMsg()) {
            OfflinePushInfo pushInfo = msgPara.getOfflinePushInfo();
            info = TXCloudUtils.getOfflinePushInfo(pushInfo.getTitle(), pushInfo.getDesc(),
                    JSON.toJSONString(pushInfo.getExt()));
        }
        //获取管理员签名
        String adminKey = RedisKeyUtil.getAdminkey(adminConfigDto.getAdminId());
        String adminSig = accountBizService.getSig(adminConfigDto.getAdminId(), 360 * 24 * 10, adminKey)
                .getSig();
        //调用IM发送消息接口发送消息
        JSONObject jsonObject = TXCloudUtils
                .openimSendMsg(msgBodyDto.getFromAccount(), msgBodyDto.getToAccount(),
                        msgBodyDto.getMsgBody(), adminSig,
                        adminConfigDto.getAdminId(), adminConfigDto.getAppId(), msgPara.getMsgLifeTime(),
                        msgPara.getMsgTypeEnum(), info);
        //是否发送成功
        BV.isTrue(jsonObject.get("ActionStatus").equals(ActionStatusEnum.OK.getCode()) && 0 == jsonObject.getInteger("ErrorCode"),
                MessageCodeBuilder
                        .messageCode(jsonObject.getInteger("ErrorCode"), "调用IM消息发送接口失败"),
                "调用IM消息发送接口失败");
        SysMsg sysMsg = msgBodyDto.getSysMsg();
        sysMsg.setMessageId(msgPara.getMessageId());
        sysMsg.setMsgStatusDesc(ActionStatusEnum.OK.getMsg());
        sysMsg.setMsgStatus(0);
        sysMsg.setReceiverType(msgPara.getIsStaff() ? UserTypeEnum.B : UserTypeEnum.C);
        //持久化发送消息记录
        this.saveMsg(sysMsg);
        return true;
    }

    /**
     * 持久化消息
     * 若保存失败 则将失败消息放到redis有序队列中 定时调度任务再进行保存消息的重试
     *
     * @param sysMsg 消息对象
     * @return 保存结果
     */
    public Boolean saveMsg(SysMsg sysMsg) {
        Boolean result = null;
        try {
            result = messageService.saveMsg(sysMsg);
            log.info("消息保存成功 messageId:{}", sysMsg.getMessageId());
        } catch (Exception ex) {
            //放入redis队列
            redisTemplate.opsForList()
                    .leftPush(RedisKey.SAVE_MESSAGE_FAILED.getCode(), JSON.toJSONString(sysMsg));
        }
        return result;
    }

    /**
     * 装配消息体
     * 调用弃用的消息发送接口在构建自定义消息检查对应业务消息类型之后依旧会再次从redis中所缓存的业务类型数据匹配检查
     * 调用新的消息发送接口不会
     *
     * @param msgPara 消息对象
     */
    public MsgBodyDto getMsgInfo(MsgParamCondition msgPara) {
        MsgBodyDto msgBodyDto = new MsgBodyDto(msgPara);
        BV.isTrue(msgBodyDto.getMsgBody() != null, "消息内容不合法");
        //如果消息是自定义消息,则检查对应的消息业务类型是否存在
        if (msgBodyDto.getMsgTypeEnum().getCode().equals(MsgTypeEnum.CUSTOM.getCode())) {
            BV.notNull(msgPara.getCustomContent().getExt(),"扩展字段参数不能为空");
            this.checkExt(msgPara);
        }
        //获取发送方用户识别码
        String fromAccount = (String) redisTemplate.opsForHash()
                .get(RedisKeyCache.SYS_KEY, msgPara.getBusinessType() == null ?
                        BusinessType.SYS_NOTICE.getCode().toString()
                        : msgPara.getBusinessType().getCode().toString());
        BV.isTrue(!StringUtils.isEmpty(fromAccount), "发送方用户不存在");
        //获取接收方用户识别码
        Optional<Account> optional = accountService.queryAccountByUserId(msgPara.getUserId(),
                msgPara.getIsStaff() ? UserTypeEnum.B.getValue() : UserTypeEnum.C.getValue());
        String toAccount;
        //如果消息接收人未注册,将该用户注册
        if (!optional.isPresent()) {
            AccountCondition account = new AccountCondition();
            account.setUserId(msgPara.getUserId());
            account.setStaff(msgPara.getIsStaff());
            //当获取签名的用户未注册时用户昵称组成为:userId+uuid前6位
            account.setUserName(msgPara.getUserId().toString() + UUIDUtils.newRandomUUid().substring(0, 6));
            toAccount = accountBizService.userRegister(account).getIdentifierCode();
        } else {
            toAccount = optional.get().getIdentifierCode();
        }
        msgBodyDto.setFromAccount(fromAccount);
        msgBodyDto.setToAccount(toAccount);
        return msgBodyDto;
    }

    /**
     * 判断消息业务类型是否存在
     *
     * @param msgPara 消息对象
     * @return 是否存在
     */
    public void checkExt(@NonNull MsgParamCondition msgPara) {
        Map<String, Object> extMap = msgPara.getCustomContent().getExt();
        BV.notNull(extMap.get("type"), "消息业务类型不能为空");
        String type = extMap.get("type").toString();
        BV.isTrue(messageBusinessTypeBizService.getTypeFromRedis(type), () -> "消息业务类型不正确");
    }

}