Small game project distributed lock code (leave a commemorative)

package com.jd.car.member.service.index.impl;

import com.alibaba.fastjson.JSON;
import com.jd.car.member.common.contant.Constants;
import com.jd.car.member.common.exception.CarMemberErrorEnum;
import com.jd.car.member.common.exception.GameException;
import com.jd.car.member.common.util.DateUtil;
import com.jd.car.member.infrastructure.jd.cache.service.CacheLockService;
import com.jd.car.member.infrastructure.jd.cache.service.CacheService;
import com.jd.car.member.infrastructure.jd.ducc.DuccService;
import com.jd.car.member.infrastructure.utils.ValidateUtils;
import com.jd.car.member.service.coupon.monitor.CouponMonitorPoint;
import com.jd.car.member.service.es.CarMemberService;
import com.jd.car.member.service.es.bo.CarMemberBO;
import com.jd.car.member.service.index.GameIndexService;
import com.jd.car.member.service.index.bo.FirstLoginResultBO;
import com.jd.car.member.service.index.bo.GameBeginResultBO;
import com.jd.car.member.service.index.bo.GameIndexResultBO;
import com.jd.car.member.service.index.bo.GameBeginCheckResultBO;
import com.jd.car.member.service.index.enums.GameIndexCacheKeyEnum;
import com.jd.car.member.service.index.monitors.SiteInfoMonitor;
import com.jd.car.member.service.power.PowerDailyService;
import com.jd.car.member.service.power.monitor.PowerDailyMonitor;
import com.jd.car.member.service.prize.PrizeService;
import com.jd.car.member.service.prize.bo.PrizeBO;
import com.jd.car.member.service.site.GameSiteInfoService;
import com.jd.car.member.service.site.GameUserSiteInfoService;
import com.jd.car.member.service.site.bo.GameSiteInfoBO;
import com.jd.car.member.service.site.bo.GameUserSiteInfoBO;
import com.jd.ump.annotation.JProfiler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 游戏首页 服务实现类
 *
 * @author xyd
 * 
 * @date 2020/04/23
 */
@Slf4j
@Service
public class GameIndexServiceImpl implements GameIndexService {

    @Autowired
    private CacheService cacheService;

    @Autowired
    private GameSiteInfoService gameSiteInfoService;

    @Autowired
    private GameUserSiteInfoService gameUserSiteInfoService;

    @Autowired
    private PowerDailyService powerDailyService;

    @Autowired
    private CarMemberService carMemberService;

    @Autowired
    private CacheLockService cacheLockService;

    @Autowired
    private PrizeService prizeService;

    @Autowired
    private DuccService duccService;

    /**
     * 用户进入游戏首页
     *
     * @param pin
     * @param activityId
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @JProfiler(jKey = "com.jd.car.member.service.index.GameIndexService.getGameIndexInfo")
    public GameIndexResultBO getGameIndexInfo(String pin, Long activityId) {

        Assert.hasLength(pin, "pin不能为空");
        Assert.notNull(activityId, "活动id不能为空");
        GameIndexResultBO gameIndexResultBo = new GameIndexResultBO();
        String lock = null;
        try {
            lock = cacheLockService.tryLock(GameIndexCacheKeyEnum.USER_GAME_POWER_LOCK.getKey(activityId, pin), GameIndexCacheKeyEnum.USER_GAME_POWER_LOCK.getExpireTime(), TimeUnit.SECONDS);
            if (null != lock) {
                // 1.获取用户信息
                gameIndexResultBo.setHeadImg("").setMatchPoint(0);
                CarMemberBO carMember = carMemberService.queryMemberInfoByPin(pin);
                if (null != carMember && null != carMember.getTotalPoint()) {
                    gameIndexResultBo.setMatchPoint(carMember.getTotalPoint());
                }
                if (null != carMember && StringUtils.isNotEmpty(carMember.getHeadImg())) {
                    gameIndexResultBo.setHeadImg(carMember.getHeadImg());
                }

                // 2.获取用户燃力值
                gameIndexResultBo.setPower(powerDailyService.getIndexUserPower(activityId, pin, new Date()));

                // 3.获取所有站点信息列表 + 用户当前站点信息
                gameIndexResultBo.setSiteList(this.getGameSiteInfoList(pin, activityId, gameIndexResultBo));
            }
        } catch (GameException e) {
            log.error("用户进入游戏首页获取信息异常:", e);
            throw new GameException(CarMemberErrorEnum.GAME_INDEX_ERROR);
        } finally {
            if (null != lock) {
                cacheLockService.unlock(GameIndexCacheKeyEnum.USER_GAME_POWER_LOCK.getKey(activityId, pin), lock);
            }
        }
        return gameIndexResultBo;
    }

    /**
     * 获取所有站点信息列表 + 用户当前站点信息
     *
     * @param pin
     * @param activityId
     * @param result
     * @return
     */
    private List<GameSiteInfoBO> getGameSiteInfoList(String pin, Long activityId, GameIndexResultBO result) {
        result.setFirstRoundStatus(false).setIsLastSite(false);
        // 1.获取游戏所有站点信息列表
        List<GameSiteInfoBO> allSite = gameSiteInfoService.getAllSite(activityId);
        // 异常捕获
        ValidateUtils.isEmpty(allSite, CarMemberErrorEnum.SITE_LIST_NOT_EXISTS_ERROR, SiteInfoMonitor.SITE_LIST_NOT_EXISTS_ERROR, activityId);
        // 2.获取用户当前站点(初始化进入没有数据需要添加游戏站点)
        if (CollectionUtils.isNotEmpty(allSite)) {
            GameUserSiteInfoBO userSiteInfoByPin = gameUserSiteInfoService.getUserSiteInfoByPin(pin, activityId);
            if (userSiteInfoByPin == null) {
                GameUserSiteInfoBO build = GameUserSiteInfoBO.builder().activityId(activityId)
                        .siteId(allSite.stream().min(Comparator.comparing(GameSiteInfoBO::getSiteNum)).map(GameSiteInfoBO::getId).get())
                        .pin(pin).total(0).build();
                gameUserSiteInfoService.addUserSite(build);
                result.setCurrentSite(allSite.stream().min(Comparator.comparing(GameSiteInfoBO::getSiteNum)).orElseGet(GameSiteInfoBO::new));
            } else {
                //判断是否是最后站点,且是否为第一圈结束
                Long maxSiteId = allSite.stream().max(Comparator.comparing(GameSiteInfoBO::getSiteNum)).map(GameSiteInfoBO::getId).get();
                if (userSiteInfoByPin.getSiteId().equals(maxSiteId)) {
                    result.setIsLastSite(true);
                    if (userSiteInfoByPin.getTotal() == 0) {
                        result.setFirstRoundStatus(true);
                    }
                }
                result.setCurrentSite(allSite.stream().filter(e -> e.getId().equals(userSiteInfoByPin.getSiteId())).findFirst().orElseGet(GameSiteInfoBO::new));
            }
        }
        return allSite;
    }

    /**
     * 用户开始游戏验证
     *
     * @param pin
     * @param activityId
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @JProfiler(jKey = "com.jd.car.member.service.index.GameIndexService.startGameCheck")
    public GameBeginCheckResultBO startGameCheck(String pin, Long activityId) {

        Assert.hasLength(pin, "pin不能为空");
        Assert.notNull(activityId, "活动id不能为空");
        GameBeginCheckResultBO gameStartCheckResultBo = new GameBeginCheckResultBO();
        // 1.查询当天的燃气值(通过后台取值,防止凌晨时刻调用接口,用户燃气值少于100的应当是刷新到100)
        Date now = new Date();
        Integer userPower = powerDailyService.getIndexUserPower(activityId, pin, now);
        if (userPower < duccService.getDefaultOncePower()) {
            gameStartCheckResultBo.setHasPower(false);
            return gameStartCheckResultBo;
        }

       /* //用户燃力值大于阈值告警
        ValidateUtils.validate(() -> userPower > duccService.getUserPowerMaxLevel(),
                PowerDailyMonitor.USER_POWER_MAX_LEVEL, activityId, pin, DateUtil.getWholeDateForNow());*/
        gameStartCheckResultBo.setHasPower(true);
        return gameStartCheckResultBo;
    }

    /**
     * 用户开始游戏
     *
     * @param pin
     * @param activityId
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @JProfiler(jKey = "com.jd.car.member.service.index.GameIndexService.startGame")
    public GameBeginResultBO startGame(String pin, Long activityId) {
        Assert.hasLength(pin, "pin不能为空");
        Assert.notNull(activityId, "活动id不能为空");
        GameBeginResultBO result = new GameBeginResultBO();
        String lock = null;
        try {
            lock = cacheLockService.tryLock(GameIndexCacheKeyEnum.USER_GAME_BEGIN_LOCK.getKey(activityId, pin), GameIndexCacheKeyEnum.USER_GAME_BEGIN_LOCK.getExpireTime(), TimeUnit.SECONDS);
            if (null != lock) {
                // 1.查询当天的燃气值(通过后台取值,防止凌晨时刻调用接口,用户燃气值少于100的应当是刷新到100)
                Date now = new Date();
                Integer userPower = powerDailyService.getIndexUserPower(activityId, pin, now);
                //用户燃力值大于阈值告警
                ValidateUtils.validate(() -> userPower > duccService.getUserPowerMaxLevel(),
                        PowerDailyMonitor.USER_POWER_MAX_LEVEL, activityId, pin, DateUtil.getWholeDateForNow());
                // 2.判断当前用户站点位置是否为终点: (1)如果终点则圈数 +1 回到原点;  (2)否则站点位置前进 +1;
                boolean isLastSite = this.getUserNextSite(pin, activityId, result);
                if (!isLastSite) {
                    // 3.燃气值-100
                    if (userPower < duccService.getDefaultOncePower()) {
                        throw new GameException(CarMemberErrorEnum.GAME_USER_POWER_ERROR);
                    }

                    int userCurrentPower = userPower - duccService.getDefaultOncePower();
                    powerDailyService.updatePower(activityId, pin, userCurrentPower, DateFormatUtils.format(now, Constants.DATA_FORMAT_YMD));

                    // 4.抽奖
                    PrizeBO prizeBo = prizeService.dealDraw(pin, activityId);
                    if (prizeBo != null) {
                        result.setDrawStatus(prizeBo.getDrawStatus()).setType(prizeBo.getType()).setName(prizeBo.getName()).setValue(prizeBo.getValue());
                    }
                    result.setCurrentPower(userCurrentPower);
                    log.info("开始游戏-返回结果1:pin:{}, activityId:{}, result:{}", pin, activityId, JSON.toJSONString(result));
                    return result;
                }
                result.setCurrentPower(userPower).setDrawStatus(false);
                log.info("开始游戏-返回结果2:pin:{}, activityId:{}, result:{}", pin, activityId, JSON.toJSONString(result));
                return result;
            }
        } catch (GameException e) {
            log.error("用户开始游戏操作异常:", e);
            throw new GameException(CarMemberErrorEnum.START_GAME_ERROR, e.getMsg());
        } finally {
            if (null != lock) {
                cacheLockService.unlock(GameIndexCacheKeyEnum.USER_GAME_BEGIN_LOCK.getKey(activityId, pin), lock);
            }
        }
        throw new GameException(CarMemberErrorEnum.GAME_INDEX_LOCK_ERROR);
    }

    @Override
    @JProfiler(jKey = "com.jd.car.member.service.index.GameIndexService.isFirstLogin")
    public FirstLoginResultBO isFirstLogin(String pin, Long activityId) {

        Assert.hasLength(pin, "pin不能为空");
        Assert.notNull(activityId, "活动id不能为空");

        FirstLoginResultBO firstLoginResultBo = new FirstLoginResultBO();
        GameIndexCacheKeyEnum firstLoginStatusEnum = GameIndexCacheKeyEnum.USER_FIRST_LOGIN_STATUS;
        //缓存中是否存在用户登录过的状态
        if (cacheService.exists(firstLoginStatusEnum.getKey(activityId, pin))) {
            firstLoginResultBo.setIsFirstLogin(false);
            return firstLoginResultBo;
        }
        //更新用户登录过游戏的缓存
        cacheService.set(firstLoginStatusEnum.getKey(activityId, pin), "1");
        firstLoginResultBo.setIsFirstLogin(true);
        return firstLoginResultBo;
    }

    /**
     * 开始游戏-用户前进一个站点
     *
     * @param pin
     * @param activityId
     * @param result
     */
    private boolean getUserNextSite(String pin, Long activityId, GameBeginResultBO result) {

        boolean response = false;
        GameSiteInfoBO nextGameSite;
        result.setIsLastSite(false).setFirstRoundStatus(false);
        List<GameSiteInfoBO> allSite = gameSiteInfoService.getAllSite(activityId);
        if (CollectionUtils.isEmpty(allSite)) {
            throw new GameException(CarMemberErrorEnum.GAME_INDEX_ERROR);
        }

        GameUserSiteInfoBO userSiteInfo = gameUserSiteInfoService.getUserSiteInfoByPin(pin, activityId);
        log.info("开始游戏-用户前进一个站点,获取到当前站点位置:{}", JSON.toJSONString(userSiteInfo));
        if (userSiteInfo == null) {
            throw new GameException(CarMemberErrorEnum.USER_SITE_NOT_EXISTS_ERROR);
        }

        Long maxSiteId = allSite.stream().max(Comparator.comparing(GameSiteInfoBO::getSiteNum)).map(GameSiteInfoBO::getId).get();
        if (maxSiteId.equals(userSiteInfo.getSiteId())) {
            nextGameSite = allSite.stream().min(Comparator.comparing(GameSiteInfoBO::getSiteNum)).orElseGet(GameSiteInfoBO::new);
            GameUserSiteInfoBO build = GameUserSiteInfoBO.builder().id(userSiteInfo.getId()).total(userSiteInfo.getTotal() + 1)
                    .siteId(nextGameSite.getId()).build();
            gameUserSiteInfoService.updateUserSiteInfo(build);
            response = true;
        } else {
            //通过用户当前站点主键找对应序号,然后通过序号 +1 找下一个游戏站点主键id,将新的站点主键id更新到用户站点信息表中
            Integer nextSiteNum = allSite.stream().filter(e -> e.getId().equals(userSiteInfo.getSiteId())).findFirst().map(GameSiteInfoBO::getSiteNum).get() + 1;
            nextGameSite = allSite.stream().filter(s -> s.getSiteNum().equals(nextSiteNum)).findFirst().orElseGet(GameSiteInfoBO::new);
            GameUserSiteInfoBO build = GameUserSiteInfoBO.builder().id(userSiteInfo.getId()).siteId(nextGameSite.getId()).build();
            gameUserSiteInfoService.updateUserSiteInfo(build);

            //如果前进一个站点是最后一个游戏站点,需要给前端提示
            if (maxSiteId.equals(nextGameSite.getId())) {
                result.setIsLastSite(true);
                if (userSiteInfo.getTotal() == 0) {
                    result.setFirstRoundStatus(true);
                }
            }
        }
        result.setCurrentSiteNum(nextGameSite.getSiteNum()).setNextSiteX(nextGameSite.getSiteX()).setNextSiteY(nextGameSite.getSiteY());
        return response;
    }
}

 

Guess you like

Origin blog.csdn.net/qq_39809613/article/details/106999137