package com.zanxiang.game.module.manage.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.zanxiang.erp.security.util.SecurityUtil;
import com.zanxiang.game.back.base.pojo.enums.OrderStatusEnum;
import com.zanxiang.game.module.manage.constant.RedisKeyConstant;
import com.zanxiang.game.module.manage.enums.*;
import com.zanxiang.game.module.manage.pojo.dto.KfAppletMsgDTO;
import com.zanxiang.game.module.manage.pojo.dto.KfWebSocketMsgDTO;
import com.zanxiang.game.module.manage.pojo.dto.PayApplicationDTO;
import com.zanxiang.game.module.manage.service.*;
import com.zanxiang.game.module.manage.service.api.KfWxApiService;
import com.zanxiang.game.module.manage.utils.FileUtil;
import com.zanxiang.game.module.manage.utils.RedisUtil;
import com.zanxiang.game.module.mybatis.entity.*;
import com.zanxiang.module.oss.service.IOssService;
import com.zanxiang.module.redis.service.IDistributedLockComponent;
import com.zanxiang.module.util.JsonUtil;
import com.zanxiang.module.util.bean.BeanUtil;
import com.zanxiang.module.util.exception.BaseException;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.util.function.Tuple2;

import java.io.IOException;
import java.math.BigDecimal;
import java.net.URI;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author : lingfeng
 * @time : 2024-02-28
 * @description : 小程序客服消息处理
 */
@Slf4j
@Service
public class KfAppletMsgServiceImpl implements IKfAppletMsgService {

    @Value("${media.realm-name}")
    private String realmName;

    @Value("${payConfig.wxPay.customH5Url}")
    private String customH5Url;

    @Autowired
    private KfWxApiService kfWxApiService;

    @Autowired
    private IOrderService orderService;

    @Autowired
    private IGamePayWayService gamePayWayService;

    @Autowired
    private IPayApplicationService payApplicationService;

    @Autowired
    private IOssService ossService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private IKfSessionUserService kfSessionUserService;

    @Autowired
    private IKfRoomService kfRoomService;

    @Autowired
    private IGameAppletService gameAppletService;

    @Autowired
    private IUserService userService;

    @Autowired
    private IGameUserRoleService gameUserRoleService;

    @Autowired
    private IKfRoomMsgService kfRoomMsgService;

    @Autowired
    private IKfSystemReplyService kfSystemReplyService;

    @Autowired
    private IKfMonitorWordService kfMonitorWordService;

    @Autowired
    private IGameAuthRoleService gameAuthRoleService;

    @Autowired
    private RedisUtil<String> redisUtil;

    @Autowired
    private KfWxApiService wxApiService;

    @Autowired
    private IDistributedLockComponent distributedLockComponent;

    @Autowired
    private IKfAppletReplyService kfAppletReplyService;

    @Autowired
    private IUserAppletService userAppletService;

    @Override
    public void appletMsg(String postData) {
        KfAppletMsgDTO kfAppletMsgDTO = JsonUtil.toObj(postData, KfAppletMsgDTO.class);
        GameApplet gameApplet = gameAppletService.getOne(new LambdaQueryWrapper<GameApplet>()
                .eq(GameApplet::getGhId, kfAppletMsgDTO.getToUserName()));
        //小游戏信息不存在, 消息不处理
        if (gameApplet == null) {
            return;
        }
        //事件消息
        if (Objects.equals(kfAppletMsgDTO.getMsgType(), KfAppletMsgDTO.MSG_TYPE_EVENT)) {
            this.eventMsgHandle(gameApplet, kfAppletMsgDTO);
            return;
        }
        //非玩家消息, 不做处理
        if (kfAppletMsgDTO.getMsgId() == null) {
            return;
        }
        //判断是否为玩家反馈信息, 如果是反馈信息需要检查玩家身份信息是否存在, 腾讯个坑比先给消息再给客服进入事件
        if (Strings.isNotBlank(kfAppletMsgDTO.getContent())
                && kfAppletMsgDTO.getContent().contains(KfAppletMsgDTO.MSG_USER_FEEDBACK)) {
            this.kfSessionUserUpdateSave(kfAppletMsgDTO, gameApplet);
        }
        //查询玩家的房间连接状态
        KfRoom kfRoom = kfRoomService.getOne(new LambdaQueryWrapper<KfRoom>()
                .eq(KfRoom::getOpenId, kfAppletMsgDTO.getFromUserName())
                .eq(KfRoom::getGameId, gameApplet.getGameId())
                .eq(KfRoom::getOnline, Boolean.TRUE));
        //构造房间消息
        KfWebSocketMsgDTO.MsgContentBean msgContent = this.getMsgContent(kfAppletMsgDTO);
        KfRoomMsg kfRoomMsg = this.transform(kfAppletMsgDTO, gameApplet, kfRoom, postData, msgContent);
        //虚拟游戏小程序, 单独逻辑
        if (Objects.equals(gameApplet.getType(), 3)) {
            //小程序自动回复
            Boolean customerSwitch = this.appletReplyHandle(gameApplet.getGameId(), kfAppletMsgDTO.getFromUserName(),
                    kfRoom, gameApplet.getAppId());
            if (Objects.equals(Boolean.TRUE, customerSwitch)) {
                //保存消息, 玩家转入接待状态
                this.saveMsgAndChangeWait(kfRoom, kfRoomMsg, gameApplet, kfAppletMsgDTO, msgContent);
            } else {
                //保存房间消息
                kfRoomMsgService.save(kfRoomMsg);
            }
            //直接结束
            return;
        }
        //判断是否请求支付链接
        String orderId = redisUtil.getCache(RedisKeyConstant.GAME_CUSTOM_PAY_SIGN + kfAppletMsgDTO.getFromUserName());
        //客服订单处理
        if (Strings.isNotBlank(orderId) && this.orderHandle(orderId, gameApplet.getGameId(), kfAppletMsgDTO, kfRoom, kfRoomMsg)) {
            return;
        }
        //不存在订单的小程序消息, 直接略过, 不转入待接入
        if (Objects.equals(kfAppletMsgDTO.getMsgType(), KfRoomMsgTypeEnum.KF_MSG_TYPE_MINI_APP.getValue())) {
            kfRoomMsg.setReadStatus(Boolean.TRUE);
            kfRoomMsgService.save(kfRoomMsg);
            return;
        }
        //客服休息时间, 发送自动回复
        this.systemReplyHandleNew(gameApplet.getGameId(), kfAppletMsgDTO.getFromUserName(), kfRoom);
        //消息报警监测
        this.monitorWordHandle(gameApplet, kfAppletMsgDTO);
        //保存消息, 玩家转入接待状态
        this.saveMsgAndChangeWait(kfRoom, kfRoomMsg, gameApplet, kfAppletMsgDTO, msgContent);
    }

    private void saveMsgAndChangeWait(KfRoom kfRoom, KfRoomMsg kfRoomMsg, GameApplet gameApplet, KfAppletMsgDTO kfAppletMsgDTO,
                                      KfWebSocketMsgDTO.MsgContentBean msgContent) {
        //保存房间消息
        kfRoomMsgService.save(kfRoomMsg);
        //玩家状态更新为待接入状态
        if (kfRoom == null) {
            kfSessionUserService.update(new LambdaUpdateWrapper<KfSessionUser>()
                    .set(KfSessionUser::getIsWait, Boolean.TRUE)
                    .set(KfSessionUser::getWaitStartTime, LocalDateTime.now())
                    .set(KfSessionUser::getUpdateTime, LocalDateTime.now())
                    .eq(KfSessionUser::getOpenId, kfAppletMsgDTO.getFromUserName())
                    .eq(KfSessionUser::getGameId, gameApplet.getGameId()));
        }
        //消息转发到redis频道
        this.pushMessage(this.transform(kfRoom, gameApplet.getGameId(), kfAppletMsgDTO.getFromUserName(), kfRoomMsg, msgContent));
    }

    private Boolean appletReplyHandle(Long gameId, String openId, KfRoom kfRoom, String appId) {
        //玩家信息
        KfSessionUser kfSessionUser = kfSessionUserService.getById(openId, gameId);
        //来源类型
        KfSessionFromEnum kfSessionFrom = KfSessionFromEnum.getKfSessionFrom(kfSessionUser.getSessionFrom());
        if (kfSessionFrom == null) {
            return Boolean.TRUE;
        }
        //查询玩家访问信息
        UserApplet userApplet = userAppletService.getOne(new LambdaQueryWrapper<UserApplet>()
                .eq(UserApplet::getAppId, appId)
                .eq(UserApplet::getOpenId, openId)
                .orderByDesc(UserApplet::getCreateTime)
                .last("limit 1"));
        //不存在访问信息, 不处理
        if (userApplet == null) {
            return Boolean.TRUE;
        }
        Map<String, String> channelMap = JsonUtil.toMap(userApplet.getChannel(), Map.class, String.class);
        String pageSign = channelMap.get(KfAppletReply.PAGE_SIGN);
        //获取自动回复配置
        KfAppletReply kfAppletReply = kfAppletReplyService.getOne(new LambdaQueryWrapper<KfAppletReply>()
                .eq(KfAppletReply::getGameId, gameId)
                .eq(KfAppletReply::getAppId, appId)
                .eq(Strings.isNotBlank(pageSign), KfAppletReply::getPageSign, pageSign)
                .orderByDesc(KfAppletReply::getCreateTime)
                .last("limit 1"));
        if (kfAppletReply == null) {
            return Boolean.TRUE;
        }
        //存在文本消息发送
        if (Strings.isNotBlank(kfAppletReply.getReplyText())) {
            this.sysMsgSend(gameId, openId, kfAppletReply.getReplyText(), kfRoom);
        }
        //存在图片发送
        if (Strings.isNotBlank(kfAppletReply.getReplyImg())) {
            this.sysImgMsgSend(gameId, openId, kfAppletReply.getReplyImg(), kfRoom);
        }
        //存在链接发送
        if (Strings.isNotBlank(kfAppletReply.getReplyLink())) {
            //链接消息参数转换
            Map<String, String> linkMap = JsonUtil.toMap(kfAppletReply.getReplyLink(), Map.class, String.class);
            String url = linkMap.get("url");
            //链接拼接短链id
            if (Strings.isNotBlank(url)) {
                linkMap.put("url", url + "?customer_channel=" + userApplet.getId());
            }
            this.sysLinkMsgSend(gameId, openId, kfRoom, linkMap);
        }
        return kfAppletReply.getCustomerSwitch();
    }

    private void sysLinkMsgSend(Long gameId, String openId, KfRoom kfRoom, Map<String, String> linkMap) {
        //发送消息
        Map<String, Object> msgParamMap = new HashMap<>(3);
        msgParamMap.put("touser", openId);
        msgParamMap.put("link", linkMap);
        msgParamMap.put("msgtype", KfRoomMsgTypeEnum.KF_MSG_TYPE_LINK.getValue());
        Tuple2<Long, String> tuple2 = kfWxApiService.sendCustomMessageApi(gameId, msgParamMap);
        //发送失败
        if (!Objects.equals(tuple2.getT1(), 0L)) {
            return;
        }
        //保存发送的支付链接消息
        kfRoomMsgService.save(this.transform(openId, gameId, kfRoom, KfRoomMsgTypeEnum.KF_MSG_TYPE_LINK, JsonUtil.toString(msgParamMap)));
    }

    private void monitorWordHandle(GameApplet gameApplet, KfAppletMsgDTO kfAppletMsgDTO) {
        //非文本消息
        if (!Objects.equals(kfAppletMsgDTO.getMsgType(), KfRoomMsgTypeEnum.KF_MSG_TYPE_TEXT.getValue())) {
            return;
        }
        //文本内容
        String textContent = kfAppletMsgDTO.getContent();
        //为本内容为空
        if (Strings.isBlank(textContent)) {
            return;
        }
        //查询所有监测词汇
        List<KfMonitorWord> kfMonitorWordList = kfMonitorWordService.list();
        //判断玩家的文本包含敏感词的数量, 不包含则不执行
        if (kfMonitorWordList.stream()
                .filter(word -> textContent.contains(word.getWord()))
                .count() <= 0) {
            return;
        }
        //给客服发送钉钉通知
        gameAuthRoleService.dingTalkCustomer(gameApplet.getGameId(), gameApplet.getAppName(), textContent);
    }

    private void systemReplyHandleNew(Long gameId, String openId, KfRoom kfRoom) {
        //获取自动回复配置
        KfSystemReply kfSystemReply = kfSystemReplyService.getById(gameId);
        if (kfSystemReply == null) {
            return;
        }
        //未开启自动回复
        if (Objects.equals(kfSystemReply.getRechargeReplySwitch(), Boolean.FALSE)) {
            return;
        }
        //发送文字消息
        if (Strings.isNotBlank(kfSystemReply.getRechargeReplyContent())) {
            this.sysMsgSend(gameId, openId, kfSystemReply.getRechargeReplyContent(), kfRoom);
        }
        //发送图片消息
        if (Strings.isNotBlank(kfSystemReply.getRechargeReplyPicture())) {
            this.sysImgMsgSend(gameId, openId, kfSystemReply.getRechargeReplyPicture(), kfRoom);
        }
    }

    private void systemReplyHandle(Long gameId, String openId, KfRoom kfRoom) {
        //获取自动回复配置
        KfSystemReply kfSystemReply = kfSystemReplyService.getById(gameId);
        if (kfSystemReply == null) {
            return;
        }
        //查询玩家信息
        KfSessionUser kfSessionUser = kfSessionUserService.getById(openId, gameId);
        //判断是否开启充值自动回复
        if (Objects.equals(kfSystemReply.getRechargeReplySwitch(), Boolean.TRUE)) {
            //查询订单
            List<Order> orderList = orderService.list(new LambdaQueryWrapper<Order>()
                    .select(Order::getAmount)
                    .eq(Order::getUserId, kfSessionUser.getUserId())
                    .eq(Order::getGameId, gameId)
                    .eq(Order::getStatus, OrderStateEnum.SUCCESS_PAY.getCode()));
            //玩家符合条件
            if (CollectionUtils.isNotEmpty(orderList) && orderList.stream().map(Order::getAmount)
                    .reduce(BigDecimal.ZERO, BigDecimal::add).intValue() >= kfSystemReply.getRechargeReplyAmount()) {
                //发送文字消息
                if (Strings.isNotBlank(kfSystemReply.getRechargeReplyContent())) {
                    this.sysMsgSend(gameId, openId, kfSystemReply.getRechargeReplyContent(), kfRoom);
                }
                //发送图片消息
                if (Strings.isNotBlank(kfSystemReply.getRechargeReplyPicture())) {
                    this.sysImgMsgSend(gameId, openId, kfSystemReply.getRechargeReplyPicture(), kfRoom);
                }
            }
        }
        //判断玩家是否已接入状态, 非带接入状态, 不发送
        if (Objects.equals(kfSessionUser.getIsWait(), Boolean.FALSE)) {
            return;
        }
        //判断当前时间是否在时间内
        LocalTime nowTime = LocalTime.now();
        LocalTime startTime = LocalTime.parse(kfSystemReply.getStartTime());
        LocalTime endTime = LocalTime.parse(kfSystemReply.getEndTime());
        if (nowTime.isAfter(startTime) && nowTime.isBefore(endTime)) {
            return;
        }
        //休息时间, 发送指定消息
        this.sysMsgSend(gameId, openId, kfSystemReply.getSysReply(), kfRoom);
    }

    private void sysMsgSend(Long gameId, String openId, String msgContent, KfRoom kfRoom) {
        //休息时间, 发送指定消息
        Map<String, Object> textMap = new HashMap<>(1);
        textMap.put("content", msgContent);
        Map<String, Object> msgParamMap = new HashMap<>(3);
        msgParamMap.put("touser", openId);
        msgParamMap.put("msgtype", KfRoomMsgTypeEnum.KF_MSG_TYPE_TEXT.getValue());
        msgParamMap.put("text", textMap);
        //调腾讯接口发送消息
        Tuple2<Long, String> tuple2 = kfWxApiService.sendCustomMessageApi(gameId, msgParamMap);
        //发送失败
        if (!Objects.equals(tuple2.getT1(), 0L)) {
            return;
        }
        //保存消息
        Map<String, Object> kfRoomMsgMap = new HashMap<>(1);
        kfRoomMsgMap.put("text", msgContent);
        kfRoomMsgService.save(this.transform(openId, gameId, kfRoom, KfRoomMsgTypeEnum.KF_MSG_TYPE_TEXT, JsonUtil.toString(kfRoomMsgMap)));
    }

    private void sysImgMsgSend(Long gameId, String openId, String imageUrl, KfRoom kfRoom) {
        //发送消息
        Map<String, Object> imageMap = new HashMap<>(1);
        //图片上传到腾讯, 转成 media_id
        Tuple2<Long, String> uploadTuple2 = wxApiService.mediaUpload(gameId, FileUtil.urlToMultipartFile(imageUrl));
        //素材上传失败
        if (uploadTuple2.getT1() != 0) {
            return;
        }
        imageMap.put("media_id", uploadTuple2.getT2());
        //构造消息发送参数
        Map<String, Object> msgParamMap = new HashMap<>(3);
        msgParamMap.put("touser", openId);
        msgParamMap.put("msgtype", KfRoomMsgTypeEnum.KF_MSG_TYPE_IMAGE.getValue());
        msgParamMap.put(KfRoomMsgTypeEnum.KF_MSG_TYPE_IMAGE.getValue(), imageMap);
        //调腾讯接口发送消息
        Tuple2<Long, String> tuple2 = wxApiService.sendCustomMessageApi(gameId, msgParamMap);
        //发送失败
        if (!Objects.equals(tuple2.getT1(), 0L)) {
            return;
        }
        //保存消息
        Map<String, Object> kfRoomMsgMap = new HashMap<>(1);
        kfRoomMsgMap.put("image", imageUrl);
        kfRoomMsgService.save(this.transform(openId, gameId, kfRoom, KfRoomMsgTypeEnum.KF_MSG_TYPE_IMAGE, JsonUtil.toString(kfRoomMsgMap)));
    }

    private boolean orderHandle(String orderId, Long gameId, KfAppletMsgDTO kfAppletMsgDTO, KfRoom kfRoom, KfRoomMsg kfRoomMsg) {
        //判断收到的消息是否为充值消息或者小程序消息
        if (!KfAppletMsgDTO.MSG_CONTENT_PAY.contains(kfAppletMsgDTO.getContent())
                && !Objects.equals(kfAppletMsgDTO.getMsgType(), KfRoomMsgTypeEnum.KF_MSG_TYPE_MINI_APP.getValue())) {
            return Boolean.FALSE;
        }
        //查询订单
        Order order = orderService.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderId, orderId));
        //判断订单是否为预下单和待支付状态
        if (!Objects.equals(order.getStatus(), OrderStatusEnum.READY_PAY.getValue())
                && !Objects.equals(order.getStatus(), OrderStatusEnum.WAIT_PAY.getValue())) {
            return Boolean.FALSE;
        }
        //发送客服支付消息
        this.sendCustomPayMessage(gameId, kfAppletMsgDTO.getFromUserName(), kfRoom, order);
        kfRoomMsg.setReadStatus(Boolean.TRUE);
        kfRoomMsgService.save(kfRoomMsg);
        return Boolean.TRUE;
    }

    private void eventMsgHandle(GameApplet gameApplet, KfAppletMsgDTO kfAppletMsgDTO) {
        //事件类型
        String event = kfAppletMsgDTO.getEvent();
        //非玩家打开会话消息, 不处理
        if (!Objects.equals(event, KfAppletMsgDTO.EVENT_USER_ENTER_TEMP_SESSION)) {
            return;
        }
        //保存玩家信息
        this.kfSessionUserUpdateSave(kfAppletMsgDTO, gameApplet);
        //判断玩家是否存在客服支付订单
        String customPaySign = RedisKeyConstant.GAME_CUSTOM_PAY_SIGN + kfAppletMsgDTO.getFromUserName();
        String orderId = redisUtil.getCache(customPaySign);
        if (Strings.isBlank(orderId)) {
            return;
        }
        //判断玩家当天是否发送过消息
        if (kfRoomMsgService.count(new LambdaQueryWrapper<KfRoomMsg>()
                .eq(KfRoomMsg::getOpenId, kfAppletMsgDTO.getFromUserName())
                .ge(KfRoomMsg::getCreateTime, LocalDateTime.now().with(LocalTime.MIDNIGHT))
        ) <= 0) {
            return;
        }
        //查询订单
        Order order = orderService.getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderId, orderId));
        //存在待支付的客服支付订单, 判断订单状态
        if (Objects.equals(order.getStatus(), OrderStatusEnum.READY_PAY.getValue())
                || Objects.equals(order.getStatus(), OrderStatusEnum.WAIT_PAY.getValue())) {
            //发送订单支付消息
            this.sendCustomPayMessage(gameApplet.getGameId(), kfAppletMsgDTO.getFromUserName(), null, order);
        }
    }

    private KfWebSocketMsgDTO transform(KfRoom kfRoom, Long gameId, String openId, KfRoomMsg kfRoomMsg,
                                        KfWebSocketMsgDTO.MsgContentBean msgContent) {
        //消息类型
        KfWebSocketMsgEnum kfWebSocketMsgEnum = kfRoom == null ? KfWebSocketMsgEnum.WEBSOCKET_MSG_WAIT_LIST
                : KfWebSocketMsgEnum.WEBSOCKET_MSG_ROOM_MSG;
        KfWebSocketMsgDTO kfWebSocketMsgDTO = KfWebSocketMsgDTO.builder()
                .webSocketMsgType(kfWebSocketMsgEnum)
                .kfUserId(kfRoom == null ? null : kfRoom.getKfUserId())
                .gameId(gameId)
                .roomId(kfRoom == null ? null : kfRoom.getId())
                .build();
        //待接入消息
        if (Objects.equals(kfWebSocketMsgEnum, KfWebSocketMsgEnum.WEBSOCKET_MSG_WAIT_LIST)) {
            List<KfWebSocketMsgDTO.WaitUserBean> waitUserList = kfSessionUserService.getWaitUserList(gameId);
            KfWebSocketMsgDTO.WaitUserBean waitUserBean = waitUserList.stream()
                    .filter(waitUser -> Objects.equals(waitUser.getOpenId(), openId))
                    .findFirst().orElse(null);
            kfWebSocketMsgDTO.setLastWaitUser(waitUserBean);
            kfWebSocketMsgDTO.setWaitUserList(waitUserList);
        }
        //房间消息
        if (Objects.equals(kfWebSocketMsgEnum, KfWebSocketMsgEnum.WEBSOCKET_MSG_ROOM_MSG)) {
            //消息对象
            KfWebSocketMsgDTO.RoomMsgBean roomMsgBean = BeanUtil.copy(kfRoomMsg, KfWebSocketMsgDTO.RoomMsgBean.class);
            roomMsgBean.setContent(msgContent);
            kfWebSocketMsgDTO.setRoomMsgList(Collections.singletonList(roomMsgBean));
            //设置房间列表和游戏列表
            if (kfRoom != null) {
                kfWebSocketMsgDTO.setGameList(kfRoomService.getKfGameByGameId(gameId, kfRoom.getKfUserId()));
                kfWebSocketMsgDTO.setRoomList(kfRoomService.getRoomByRoomId(kfRoom.getId()));
            }
        }
        return kfWebSocketMsgDTO;
    }

    private void kfSessionUserUpdateSave(KfAppletMsgDTO kfAppletMsgDTO, GameApplet gameApplet) {
        //上锁
        String lockKey = RedisKeyConstant.KF_MSG_USER_SESSION_UPDATE
                + kfAppletMsgDTO.getFromUserName() + "_" + gameApplet.getGameId();
        if (!distributedLockComponent.doLock(lockKey, 0L, 5L, TimeUnit.MINUTES)) {
            return;
        }
        //查询用户
        User user = userService.getOne(new LambdaQueryWrapper<User>()
                .eq(User::getGameId, gameApplet.getGameId())
                .eq(User::getOpenId, kfAppletMsgDTO.getFromUserName()));
        //最近角色信息
        GameUserRole gameUserRole = null;
        if (user != null) {
            gameUserRole = gameUserRoleService.getLastGameUserRoleName(user.getId(), user.getGameId());
        }
        //不存在玩家信息, 创建保存
        if (kfSessionUserService.count(new LambdaQueryWrapper<KfSessionUser>()
                .eq(KfSessionUser::getOpenId, kfAppletMsgDTO.getFromUserName())
                .eq(KfSessionUser::getGameId, gameApplet.getGameId())
        ) <= 0) {
            kfSessionUserService.save(this.transform(kfAppletMsgDTO, gameApplet, user, gameUserRole));
            return;
        }
        //不存在角色信息, 不做更新
        if (user == null && Strings.isBlank(kfAppletMsgDTO.getSessionFrom())) {
            return;
        }
        //存在, 更新玩家信息
        kfSessionUserService.update(new LambdaUpdateWrapper<KfSessionUser>()
                .set(user != null, KfSessionUser::getUserId, user == null ? null : user.getId())
                .set(gameUserRole != null, KfSessionUser::getLastRoleId, gameUserRole == null ? null : gameUserRole.getRoleId())
                .set(gameUserRole != null, KfSessionUser::getLastRoleName, gameUserRole == null ? null : gameUserRole.getRoleName())
                .set(gameUserRole != null, KfSessionUser::getServerId, gameUserRole == null ? null : gameUserRole.getServerId())
                .set(gameUserRole != null, KfSessionUser::getServerName, gameUserRole == null ? null : gameUserRole.getServerName())
                .set(Strings.isNotBlank(kfAppletMsgDTO.getSessionFrom()), KfSessionUser::getSessionFrom, kfAppletMsgDTO.getSessionFrom())
                .set(KfSessionUser::getUpdateTime, LocalDateTime.now())
                .eq(KfSessionUser::getOpenId, kfAppletMsgDTO.getFromUserName())
                .eq(KfSessionUser::getGameId, gameApplet.getGameId())
        );
        distributedLockComponent.unlock(lockKey);
    }

    private KfSessionUser transform(KfAppletMsgDTO kfAppletMsgDTO, GameApplet gameApplet, User user, GameUserRole gameUserRole) {
        return KfSessionUser.builder()
                .openId(kfAppletMsgDTO.getFromUserName())
                .gameId(gameApplet.getGameId())
                .userId(user == null ? null : user.getId())
                .isWait(Boolean.FALSE)
                .lastRoleId(gameUserRole == null ? null : gameUserRole.getRoleId())
                .lastRoleName(gameUserRole == null ? "神秘人[未创角]" : gameUserRole.getRoleName())
                .serverId(gameUserRole == null ? null : gameUserRole.getServerId())
                .serverName(gameUserRole == null ? null : gameUserRole.getServerName())
                .sessionFrom(Strings.isBlank(kfAppletMsgDTO.getSessionFrom()) ? null : kfAppletMsgDTO.getSessionFrom())
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .build();
    }

    private KfRoomMsg transform(KfAppletMsgDTO kfAppletMsgDTO, GameApplet gameApplet, KfRoom kfRoom, String postData,
                                KfWebSocketMsgDTO.MsgContentBean msgContent) {
        return KfRoomMsg.builder()
                .msgId(String.valueOf(kfAppletMsgDTO.getMsgId()))
                .msgType(kfAppletMsgDTO.getMsgType())
                .gameId(gameApplet.getGameId())
                .openId(kfAppletMsgDTO.getFromUserName())
                .userId(kfRoom == null ? null : kfRoom.getUserId())
                .readStatus(Boolean.FALSE)
                .roomId(kfRoom == null ? null : kfRoom.getId())
                .kfUserId(kfRoom == null ? null : kfRoom.getKfUserId())
                .msgOwner(KfRoomMsgOwnerEnum.KF_MSG_OWNER_USER.getValue())
                .content(JsonUtil.toString(msgContent))
                .source(postData)
                .sortTime(System.currentTimeMillis())
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .build();
    }

    private KfWebSocketMsgDTO.MsgContentBean getMsgContent(KfAppletMsgDTO kfAppletMsgDTO) {
        KfWebSocketMsgDTO.MsgContentBean msgContentBean = new KfWebSocketMsgDTO.MsgContentBean();
        if (Objects.equals(KfRoomMsgTypeEnum.KF_MSG_TYPE_TEXT.getValue(), kfAppletMsgDTO.getMsgType())) {
            msgContentBean.setText(kfAppletMsgDTO.getContent());
        }
        if (Objects.equals(KfRoomMsgTypeEnum.KF_MSG_TYPE_IMAGE.getValue(), kfAppletMsgDTO.getMsgType())) {
            msgContentBean.setImage(this.mediaConvertOss(kfAppletMsgDTO.getPicUrl()));
        }
        if (Objects.equals(KfRoomMsgTypeEnum.KF_MSG_TYPE_MINI_APP.getValue(), kfAppletMsgDTO.getMsgType())) {
            msgContentBean.setMiniprogrampage(KfWebSocketMsgDTO.MiniAppBean
                    .builder()
                    .title(kfAppletMsgDTO.getTitle())
                    .appId(kfAppletMsgDTO.getAppId())
                    .pagePath(kfAppletMsgDTO.getPagePath())
                    .thumbUrl(kfAppletMsgDTO.getThumbUrl())
                    .build());
        }
        return msgContentBean;
    }

    private String mediaConvertOss(String mediaUrl) {
        //资源转换
        MultipartFile multipartFile = FileUtil.urlToMultipartFile(mediaUrl);
        //生成唯一文件名
        String fileName = Long.toString(System.currentTimeMillis(), 36) + SecurityUtil.getUserId() + ".jpg";
        try {
            ossService.upload("image/" + fileName, multipartFile.getInputStream());
        } catch (IOException e) {
            log.error("文件上传oss异常, mediaUrl : {}, e : {}", mediaUrl, e.getMessage());
            throw new BaseException("文件上传oss异常");
        }
        //oss资源地址
        return this.realmName + "image/" + fileName;
    }

    private void sendCustomPayMessage(Long gameId, String openId, KfRoom kfRoom, Order order) {
        //查询订单支付方式
        GamePayWay gamePayWay = gamePayWayService.getById(order.getGamePayWayId());
        //查询支付应用信息
        PayApplicationDTO payApplicationDTO = payApplicationService.getPayApplicationByPayBoxId(gamePayWay.getPayBoxId());
        //客服支付链接显示图片地址
        String thumbUrl = gamePayWay.getThumbUrl();
        //支付配置参数判断
        if (Strings.isBlank(thumbUrl)) {
            log.error("客服消息卡片图片地址不存在, gamePayWayDTO : {}", JsonUtil.toString(gamePayWay));
            return;
        }
        //订单金额
        BigDecimal amount = order.getAmount();
        //构造跳转链接url
        URI url = UriComponentsBuilder.fromHttpUrl(this.customH5Url)
                .queryParam("appId", payApplicationDTO.getAppId())
                .queryParam("orderId", order.getOrderId())
                .queryParam("amount", amount)
                .queryParam("description", "购买" + amount + "元档充值")
                .build().toUri();
        //link参数构造
        Map<String, Object> linkMap = new HashMap<>(4);
        linkMap.put("title", "点我支付");
        linkMap.put("description", amount + "元\n" + "充值后返回游戏查看");
        linkMap.put("url", url);
        linkMap.put("thumb_url", thumbUrl);
        //发送消息
        Map<String, Object> msgParamMap = new HashMap<>(3);
        msgParamMap.put("touser", openId);
        msgParamMap.put("msgtype", KfRoomMsgTypeEnum.KF_MSG_TYPE_LINK.getValue());
        msgParamMap.put(KfRoomMsgTypeEnum.KF_MSG_TYPE_LINK.getValue(), linkMap);
        Tuple2<Long, String> tuple2 = kfWxApiService.sendCustomMessageApi(gameId, msgParamMap);
        //发送失败
        if (!Objects.equals(tuple2.getT1(), 0L)) {
            return;
        }
        //保存发送的支付链接消息
        kfRoomMsgService.save(this.transform(openId, gameId, kfRoom, KfRoomMsgTypeEnum.KF_MSG_TYPE_LINK, JsonUtil.toString(msgParamMap)));
        //删除待支付订单标记
        redisUtil.deleteCache(RedisKeyConstant.GAME_CUSTOM_PAY_SIGN + openId);
    }

    private KfRoomMsg transform(String openId, Long gameId, KfRoom kfRoom, KfRoomMsgTypeEnum kfRoomMsgTypeEnum, String msgContent) {
        return KfRoomMsg.builder()
                .msgId(UUID.randomUUID().toString().replace("-", ""))
                .msgType(kfRoomMsgTypeEnum.getValue())
                .gameId(gameId)
                .openId(openId)
                .userId(kfRoom == null ? null : kfRoom.getUserId())
                .readStatus(kfRoom != null)
                .roomId(kfRoom == null ? null : kfRoom.getId())
                .kfUserId(kfRoom == null ? null : kfRoom.getKfUserId())
                .msgOwner(KfRoomMsgOwnerEnum.KF_MSG_OWNER_SYSTEM.getValue())
                .content(msgContent)
                .source(msgContent)
                .sortTime(System.currentTimeMillis())
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .build();
    }

    /**
     * 消息发送到redis广播
     */
    private void pushMessage(KfWebSocketMsgDTO kfWebSocketMsgDTO) {
        redisTemplate.convertAndSend(RedisKeyConstant.KF_MSG_REDIS_LISTEN_TOPIC, JsonUtil.toString(kfWebSocketMsgDTO));
    }
}
