63 changed files with 4830 additions and 8 deletions
@ -0,0 +1,96 @@ |
|||
package cc.hiver.mall.planet.constant; |
|||
|
|||
/** |
|||
* 白嫖星球常量 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
public class PlanetConstant { |
|||
|
|||
/** 奖池状态 0进行中 1已开奖 */ |
|||
public static final int POOL_STATUS_RUNNING = 0; |
|||
public static final int POOL_STATUS_DRAWN = 1; |
|||
|
|||
/** 券流水来源类型 */ |
|||
public static final String TICKET_TYPE_ORDER = "order"; |
|||
public static final String TICKET_TYPE_GROUP = "group"; |
|||
public static final String TICKET_TYPE_INVITE = "invite"; |
|||
public static final String TICKET_TYPE_SIGN = "sign"; |
|||
public static final String TICKET_TYPE_BOX = "box"; |
|||
public static final String TICKET_TYPE_HUNT = "hunt"; |
|||
public static final String TICKET_TYPE_BUFF = "buff"; |
|||
public static final String TICKET_TYPE_DRAW = "draw"; |
|||
|
|||
/** 任务编码 */ |
|||
public static final String TASK_WAIMAI = "waimai"; |
|||
public static final String TASK_GROUP = "group"; |
|||
public static final String TASK_INVITE = "invite"; |
|||
public static final String TASK_SIGN = "sign"; |
|||
|
|||
/** BUFF类型 */ |
|||
public static final String BUFF_DOUBLE = "double"; |
|||
public static final String BUFF_SHIELD = "shield"; |
|||
public static final String BUFF_LUCKY = "lucky"; |
|||
public static final String BUFF_HUNT = "hunt"; |
|||
public static final String BUFF_STEALTH = "stealth"; |
|||
|
|||
/** BUFF状态 */ |
|||
public static final int BUFF_STATUS_ACTIVE = 0; |
|||
public static final int BUFF_STATUS_EXPIRED = 1; |
|||
|
|||
/** 追捕结果 */ |
|||
public static final String HUNT_SUCCESS = "success"; |
|||
public static final String HUNT_SHIELD = "shield"; |
|||
public static final String HUNT_FAIL = "fail"; |
|||
|
|||
/** 每日追捕次数上限 */ |
|||
public static final int HUNT_DAILY_LIMIT = 3; |
|||
/** 每日被追捕成功上限(达到自动开防护罩) */ |
|||
public static final int HUNTED_SUCCESS_LIMIT = 3; |
|||
/** 自动防护罩持续小时 */ |
|||
public static final int AUTO_SHIELD_HOURS = 24; |
|||
|
|||
/** 危险等级 */ |
|||
public static String dangerLevel(int ticket) { |
|||
if (ticket > 30) { |
|||
return "SSS"; |
|||
} else if (ticket >= 21) { |
|||
return "S"; |
|||
} else if (ticket >= 11) { |
|||
return "A"; |
|||
} else if (ticket >= 4) { |
|||
return "B"; |
|||
} |
|||
return "C"; |
|||
} |
|||
|
|||
public static String dangerLevelName(int ticket) { |
|||
if (ticket > 30) { |
|||
return "宇宙首富"; |
|||
} else if (ticket >= 21) { |
|||
return "S级目标"; |
|||
} else if (ticket >= 11) { |
|||
return "A级目标"; |
|||
} else if (ticket >= 4) { |
|||
return "B级目标"; |
|||
} |
|||
return "C级目标"; |
|||
} |
|||
|
|||
/** 榜单目标悬赏奖励:按排名前三额外悬赏券 */ |
|||
public static int bountyByRank(int rankNo) { |
|||
if (rankNo == 1) { |
|||
return 2; |
|||
} else if (rankNo == 2 || rankNo == 3) { |
|||
return 1; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
/** 快讯类型 */ |
|||
public static final String NEWS_RANK = "rank"; |
|||
public static final String NEWS_HUNT = "hunt"; |
|||
public static final String NEWS_BOX = "box"; |
|||
public static final String NEWS_DRAW = "draw"; |
|||
public static final String NEWS_SYS = "sys"; |
|||
} |
|||
@ -0,0 +1,235 @@ |
|||
package cc.hiver.mall.planet.controller; |
|||
|
|||
import cc.hiver.core.common.exception.HiverException; |
|||
import cc.hiver.core.common.utils.ResultUtil; |
|||
import cc.hiver.core.common.vo.PageVo; |
|||
import cc.hiver.core.common.vo.Result; |
|||
import cc.hiver.mall.planet.entity.*; |
|||
import cc.hiver.mall.planet.service.PlanetAdminService; |
|||
import io.swagger.annotations.Api; |
|||
import io.swagger.annotations.ApiOperation; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
import org.springframework.web.bind.annotation.*; |
|||
|
|||
/** |
|||
* 白嫖星球-后台管理接口 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Slf4j |
|||
@RestController |
|||
@Api(tags = "白嫖星球后台接口") |
|||
@RequestMapping("/hiver/app/planet/admin") |
|||
@Transactional |
|||
public class PlanetAdminController { |
|||
|
|||
@Autowired |
|||
private PlanetAdminService adminService; |
|||
|
|||
/** 商圈强隔离:白嫖星球后台所有功能均限定在本商圈内 */ |
|||
private static final String REGION_REQUIRED = "请先选择商圈,白嫖星球仅限本区域内进行"; |
|||
|
|||
private PageVo buildPage(Integer pageNumber, Integer pageSize) { |
|||
final PageVo pageVo = new PageVo(); |
|||
pageVo.setPageNumber(pageNumber == null ? 1 : pageNumber); |
|||
pageVo.setPageSize(pageSize == null ? 10 : pageSize); |
|||
return pageVo; |
|||
} |
|||
|
|||
// ---------------- 奖池 ----------------
|
|||
@ApiOperation(value = "奖池分页") |
|||
@RequestMapping(value = "/pool/page", method = RequestMethod.GET) |
|||
public Result poolPage(@RequestParam(required = false) Integer pageNumber, |
|||
@RequestParam(required = false) Integer pageSize, |
|||
@RequestParam(required = false) String regionId, |
|||
@RequestParam(required = false) Integer status) { |
|||
if (StringUtils.isEmpty(regionId)) { |
|||
return ResultUtil.error("请先选择商圈,抽奖仅限本区域内进行"); |
|||
} |
|||
return new ResultUtil<>().setData(adminService.pagePools(buildPage(pageNumber, pageSize), regionId, status)); |
|||
} |
|||
|
|||
@ApiOperation(value = "保存奖池") |
|||
@RequestMapping(value = "/pool/save", method = RequestMethod.POST) |
|||
public Result poolSave(@RequestBody PlanetPool pool) { |
|||
if (pool == null || StringUtils.isEmpty(pool.getRegionId())) { |
|||
return ResultUtil.error("奖池必须归属商圈,抽奖仅限本区域内进行"); |
|||
} |
|||
return new ResultUtil<>().setData(adminService.savePool(pool)); |
|||
} |
|||
|
|||
@ApiOperation(value = "删除奖池") |
|||
@RequestMapping(value = "/pool/delete", method = RequestMethod.POST) |
|||
public Result poolDelete(@RequestParam String id) { |
|||
adminService.deletePool(id); |
|||
return ResultUtil.success("删除成功"); |
|||
} |
|||
|
|||
// ---------------- 奖项 ----------------
|
|||
@ApiOperation(value = "奖池奖项列表") |
|||
@RequestMapping(value = "/reward/list", method = RequestMethod.GET) |
|||
public Result rewardList(@RequestParam String poolId) { |
|||
return new ResultUtil<>().setData(adminService.rewardsOfPool(poolId)); |
|||
} |
|||
|
|||
@ApiOperation(value = "保存奖项") |
|||
@RequestMapping(value = "/reward/save", method = RequestMethod.POST) |
|||
public Result rewardSave(@RequestBody PlanetReward reward) { |
|||
return new ResultUtil<>().setData(adminService.saveReward(reward)); |
|||
} |
|||
|
|||
@ApiOperation(value = "删除奖项") |
|||
@RequestMapping(value = "/reward/delete", method = RequestMethod.POST) |
|||
public Result rewardDelete(@RequestParam String id) { |
|||
adminService.deleteReward(id); |
|||
return ResultUtil.success("删除成功"); |
|||
} |
|||
|
|||
// ---------------- 任务 ----------------
|
|||
@ApiOperation(value = "任务分页") |
|||
@RequestMapping(value = "/task/page", method = RequestMethod.GET) |
|||
public Result taskPage(@RequestParam(required = false) Integer pageNumber, |
|||
@RequestParam(required = false) Integer pageSize, |
|||
@RequestParam(required = false) String regionId) { |
|||
if (StringUtils.isEmpty(regionId)) { |
|||
return ResultUtil.error(REGION_REQUIRED); |
|||
} |
|||
return new ResultUtil<>().setData(adminService.pageTasks(buildPage(pageNumber, pageSize), regionId)); |
|||
} |
|||
|
|||
@ApiOperation(value = "保存任务") |
|||
@RequestMapping(value = "/task/save", method = RequestMethod.POST) |
|||
public Result taskSave(@RequestBody PlanetTask task) { |
|||
if (task == null || StringUtils.isEmpty(task.getRegionId())) { |
|||
return ResultUtil.error("任务必须归属商圈,白嫖星球仅限本区域内进行"); |
|||
} |
|||
return new ResultUtil<>().setData(adminService.saveTask(task)); |
|||
} |
|||
|
|||
@ApiOperation(value = "删除任务") |
|||
@RequestMapping(value = "/task/delete", method = RequestMethod.POST) |
|||
public Result taskDelete(@RequestParam String id) { |
|||
adminService.deleteTask(id); |
|||
return ResultUtil.success("删除成功"); |
|||
} |
|||
|
|||
// ---------------- BUFF ----------------
|
|||
@ApiOperation(value = "BUFF分页") |
|||
@RequestMapping(value = "/buff/page", method = RequestMethod.GET) |
|||
public Result buffPage(@RequestParam(required = false) Integer pageNumber, |
|||
@RequestParam(required = false) Integer pageSize, |
|||
@RequestParam(required = false) String regionId) { |
|||
if (StringUtils.isEmpty(regionId)) { |
|||
return ResultUtil.error(REGION_REQUIRED); |
|||
} |
|||
return new ResultUtil<>().setData(adminService.pageBuffs(buildPage(pageNumber, pageSize), regionId)); |
|||
} |
|||
|
|||
@ApiOperation(value = "保存BUFF") |
|||
@RequestMapping(value = "/buff/save", method = RequestMethod.POST) |
|||
public Result buffSave(@RequestBody PlanetBuff buff) { |
|||
if (buff == null || StringUtils.isEmpty(buff.getRegionId())) { |
|||
return ResultUtil.error("BUFF必须归属商圈,白嫖星球仅限本区域内进行"); |
|||
} |
|||
return new ResultUtil<>().setData(adminService.saveBuff(buff)); |
|||
} |
|||
|
|||
@ApiOperation(value = "删除BUFF") |
|||
@RequestMapping(value = "/buff/delete", method = RequestMethod.POST) |
|||
public Result buffDelete(@RequestParam String id) { |
|||
adminService.deleteBuff(id); |
|||
return ResultUtil.success("删除成功"); |
|||
} |
|||
|
|||
// ---------------- 快讯 ----------------
|
|||
@ApiOperation(value = "快讯分页") |
|||
@RequestMapping(value = "/news/page", method = RequestMethod.GET) |
|||
public Result newsPage(@RequestParam(required = false) Integer pageNumber, |
|||
@RequestParam(required = false) Integer pageSize, |
|||
@RequestParam(required = false) String regionId) { |
|||
if (StringUtils.isEmpty(regionId)) { |
|||
return ResultUtil.error(REGION_REQUIRED); |
|||
} |
|||
return new ResultUtil<>().setData(adminService.pageNews(buildPage(pageNumber, pageSize), regionId)); |
|||
} |
|||
|
|||
@ApiOperation(value = "保存快讯") |
|||
@RequestMapping(value = "/news/save", method = RequestMethod.POST) |
|||
public Result newsSave(@RequestBody PlanetNews news) { |
|||
if (news == null || StringUtils.isEmpty(news.getRegionId())) { |
|||
return ResultUtil.error("快讯必须归属商圈,白嫖星球仅限本区域内进行"); |
|||
} |
|||
return new ResultUtil<>().setData(adminService.saveNews(news)); |
|||
} |
|||
|
|||
@ApiOperation(value = "删除快讯") |
|||
@RequestMapping(value = "/news/delete", method = RequestMethod.POST) |
|||
public Result newsDelete(@RequestParam String id) { |
|||
adminService.deleteNews(id); |
|||
return ResultUtil.success("删除成功"); |
|||
} |
|||
|
|||
// ---------------- 排行榜 ----------------
|
|||
@ApiOperation(value = "排行榜分页") |
|||
@RequestMapping(value = "/rank/page", method = RequestMethod.GET) |
|||
public Result rankPage(@RequestParam(required = false) Integer pageNumber, |
|||
@RequestParam(required = false) Integer pageSize, |
|||
@RequestParam(required = false) String regionId) { |
|||
if (StringUtils.isEmpty(regionId)) { |
|||
return ResultUtil.error(REGION_REQUIRED); |
|||
} |
|||
return new ResultUtil<>().setData(adminService.pageRank(buildPage(pageNumber, pageSize), regionId)); |
|||
} |
|||
|
|||
// ---------------- 追捕记录 ----------------
|
|||
@ApiOperation(value = "追捕记录分页") |
|||
@RequestMapping(value = "/hunt/page", method = RequestMethod.GET) |
|||
public Result huntPage(@RequestParam(required = false) Integer pageNumber, |
|||
@RequestParam(required = false) Integer pageSize, |
|||
@RequestParam(required = false) String regionId) { |
|||
if (StringUtils.isEmpty(regionId)) { |
|||
return ResultUtil.error(REGION_REQUIRED); |
|||
} |
|||
return new ResultUtil<>().setData(adminService.pageHunts(buildPage(pageNumber, pageSize), regionId)); |
|||
} |
|||
|
|||
// ---------------- 开奖记录 ----------------
|
|||
@ApiOperation(value = "开奖记录分页") |
|||
@RequestMapping(value = "/draw/page", method = RequestMethod.GET) |
|||
public Result drawPage(@RequestParam(required = false) Integer pageNumber, |
|||
@RequestParam(required = false) Integer pageSize, |
|||
@RequestParam(required = false) String regionId) { |
|||
if (StringUtils.isEmpty(regionId)) { |
|||
return ResultUtil.error("请先选择商圈,仅可查看本区域开奖记录"); |
|||
} |
|||
return new ResultUtil<>().setData(adminService.pageDraws(buildPage(pageNumber, pageSize), regionId)); |
|||
} |
|||
|
|||
// ---------------- 中奖记录 ----------------
|
|||
@ApiOperation(value = "中奖记录分页") |
|||
@RequestMapping(value = "/winner/page", method = RequestMethod.GET) |
|||
public Result winnerPage(@RequestParam(required = false) Integer pageNumber, |
|||
@RequestParam(required = false) Integer pageSize, |
|||
@RequestParam(required = false) String regionId, |
|||
@RequestParam(required = false) String periodNo) { |
|||
if (StringUtils.isEmpty(regionId)) { |
|||
return ResultUtil.error("请先选择商圈,仅可查看本区域中奖记录"); |
|||
} |
|||
return new ResultUtil<>().setData(adminService.pageWinners(buildPage(pageNumber, pageSize), regionId, periodNo)); |
|||
} |
|||
|
|||
// ---------------- 手动开奖 ----------------
|
|||
@ApiOperation(value = "手动开奖") |
|||
@RequestMapping(value = "/draw/manual", method = RequestMethod.POST) |
|||
public Result manualDraw(@RequestParam String poolId, |
|||
@RequestParam(required = false) String regionId) { |
|||
try { |
|||
return new ResultUtil<>().setData(adminService.manualDraw(poolId, regionId)); |
|||
} catch (HiverException e) { |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,234 @@ |
|||
package cc.hiver.mall.planet.controller; |
|||
|
|||
import cc.hiver.core.common.exception.HiverException; |
|||
import cc.hiver.core.common.utils.ResultUtil; |
|||
import cc.hiver.core.common.vo.Result; |
|||
import cc.hiver.mall.planet.entity.PlanetDrawRecord; |
|||
import cc.hiver.mall.planet.pojo.PlanetBoxResultVo; |
|||
import cc.hiver.mall.planet.pojo.PlanetHomeVo; |
|||
import cc.hiver.mall.planet.pojo.PlanetHuntResultVo; |
|||
import cc.hiver.mall.planet.pojo.PlanetQuery; |
|||
import cc.hiver.mall.planet.service.*; |
|||
import io.swagger.annotations.Api; |
|||
import io.swagger.annotations.ApiOperation; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
import org.springframework.web.bind.annotation.RequestBody; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestMethod; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* 白嫖星球-App端接口 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Slf4j |
|||
@RestController |
|||
@Api(tags = "白嫖星球App接口") |
|||
@RequestMapping("/hiver/app/planet") |
|||
@Transactional |
|||
public class PlanetController { |
|||
|
|||
/** 商圈强隔离:白嫖星球所有功能均限定在本商圈内 */ |
|||
private static final String REGION_REQUIRED = "缺少商圈参数,白嫖星球仅限本区域内进行"; |
|||
|
|||
@Autowired |
|||
private PlanetService planetService; |
|||
|
|||
@Autowired |
|||
private PlanetRankService rankService; |
|||
|
|||
@Autowired |
|||
private PlanetHuntService huntService; |
|||
|
|||
@Autowired |
|||
private PlanetBoxService boxService; |
|||
|
|||
@Autowired |
|||
private PlanetTaskService taskService; |
|||
|
|||
@Autowired |
|||
private PlanetBuffService buffService; |
|||
|
|||
@Autowired |
|||
private PlanetDrawService drawService; |
|||
|
|||
@Autowired |
|||
private cc.hiver.mall.planet.service.PlanetTicketService ticketService; |
|||
|
|||
@ApiOperation(value = "首页聚合数据") |
|||
@RequestMapping(value = "/home", method = RequestMethod.POST) |
|||
public Result home(@RequestBody PlanetQuery query) { |
|||
if (StringUtils.isEmpty(query.getUserId())) { |
|||
return ResultUtil.error("用户id不能为空"); |
|||
} |
|||
if (StringUtils.isEmpty(query.getRegionId())) { |
|||
return ResultUtil.error("缺少商圈参数,白嫖星球仅限本区域内进行"); |
|||
} |
|||
try { |
|||
final PlanetHomeVo vo = planetService.home(query); |
|||
return new ResultUtil<PlanetHomeVo>().setData(vo); |
|||
} catch (HiverException e) { |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
@ApiOperation(value = "通缉榜TOP10") |
|||
@RequestMapping(value = "/rank", method = RequestMethod.POST) |
|||
public Result rank(@RequestBody PlanetQuery query) { |
|||
if (StringUtils.isEmpty(query.getRegionId())) { |
|||
return ResultUtil.error(REGION_REQUIRED); |
|||
} |
|||
return new ResultUtil<>().setData(rankService.rankTop(query.getRegionId(), 10, query.getUserId())); |
|||
} |
|||
|
|||
@ApiOperation(value = "发起追捕") |
|||
@RequestMapping(value = "/hunt", method = RequestMethod.POST) |
|||
public Result hunt(@RequestBody PlanetQuery query) { |
|||
if (StringUtils.isEmpty(query.getRegionId())) { |
|||
return ResultUtil.error(REGION_REQUIRED); |
|||
} |
|||
try { |
|||
final PlanetHuntResultVo vo = huntService.hunt(query.getUserId(), query.getRegionId(), query.getToUserId()); |
|||
return new ResultUtil<PlanetHuntResultVo>().setData(vo); |
|||
} catch (HiverException e) { |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
@ApiOperation(value = "开启每日宝箱") |
|||
@RequestMapping(value = "/box/open", method = RequestMethod.POST) |
|||
public Result openBox(@RequestBody PlanetQuery query) { |
|||
if (StringUtils.isEmpty(query.getRegionId())) { |
|||
return ResultUtil.error(REGION_REQUIRED); |
|||
} |
|||
try { |
|||
final PlanetBoxResultVo vo = boxService.openDailyBox(query.getUserId(), query.getRegionId()); |
|||
return new ResultUtil<PlanetBoxResultVo>().setData(vo); |
|||
} catch (HiverException e) { |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
@ApiOperation(value = "任务列表") |
|||
@RequestMapping(value = "/tasks", method = RequestMethod.POST) |
|||
public Result tasks(@RequestBody PlanetQuery query) { |
|||
if (StringUtils.isEmpty(query.getRegionId())) { |
|||
return ResultUtil.error(REGION_REQUIRED); |
|||
} |
|||
return new ResultUtil<>().setData(taskService.listTasks(query.getUserId(), query.getRegionId())); |
|||
} |
|||
|
|||
@ApiOperation(value = "每日签到") |
|||
@RequestMapping(value = "/sign", method = RequestMethod.POST) |
|||
public Result sign(@RequestBody PlanetQuery query) { |
|||
if (StringUtils.isEmpty(query.getRegionId())) { |
|||
return ResultUtil.error(REGION_REQUIRED); |
|||
} |
|||
try { |
|||
final int reward = taskService.sign(query.getUserId(), query.getRegionId()); |
|||
return ResultUtil.success("签到成功,获得 " + reward + " 张星球券"); |
|||
} catch (HiverException e) { |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
@ApiOperation(value = "领取任务奖励") |
|||
@RequestMapping(value = "/task/claim", method = RequestMethod.POST) |
|||
public Result claim(@RequestBody PlanetQuery query) { |
|||
if (StringUtils.isEmpty(query.getRegionId())) { |
|||
return ResultUtil.error(REGION_REQUIRED); |
|||
} |
|||
try { |
|||
final int reward = taskService.claim(query.getUserId(), query.getRegionId(), query.getTaskCode()); |
|||
return ResultUtil.success("领取成功,获得 " + reward + " 张星球券"); |
|||
} catch (HiverException e) { |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
@ApiOperation(value = "BUFF商店列表") |
|||
@RequestMapping(value = "/buff/list", method = RequestMethod.POST) |
|||
public Result buffList(@RequestBody PlanetQuery query) { |
|||
if (StringUtils.isEmpty(query.getRegionId())) { |
|||
return ResultUtil.error(REGION_REQUIRED); |
|||
} |
|||
return new ResultUtil<>().setData(buffService.shopList(query.getUserId(), query.getRegionId())); |
|||
} |
|||
|
|||
@ApiOperation(value = "购买BUFF") |
|||
@RequestMapping(value = "/buff/buy", method = RequestMethod.POST) |
|||
public Result buyBuff(@RequestBody PlanetQuery query) { |
|||
if (StringUtils.isEmpty(query.getRegionId())) { |
|||
return ResultUtil.error(REGION_REQUIRED); |
|||
} |
|||
try { |
|||
return new ResultUtil<>().setData(buffService.buyBuff(query.getUserId(), query.getRegionId(), query.getBuffId())); |
|||
} catch (HiverException e) { |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
@ApiOperation(value = "最近一期开奖结果") |
|||
@RequestMapping(value = "/draw/result", method = RequestMethod.POST) |
|||
public Result drawResult(@RequestBody PlanetQuery query) { |
|||
if (StringUtils.isEmpty(query.getRegionId())) { |
|||
return ResultUtil.error("缺少商圈参数,开奖仅限本区域内进行"); |
|||
} |
|||
try { |
|||
final PlanetDrawRecord record = drawService.latestDrawRecord(query.getRegionId()); |
|||
final Map<String, Object> data = new HashMap<>(4); |
|||
data.put("record", record); |
|||
data.put("winners", record == null ? null : drawService.winnersOfDraw(record.getId())); |
|||
return new ResultUtil<>().setData(data); |
|||
} catch (HiverException e) { |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
@ApiOperation(value = "我的中奖记录") |
|||
@RequestMapping(value = "/draw/myWinning", method = RequestMethod.POST) |
|||
public Result myWinning(@RequestBody PlanetQuery query) { |
|||
if (StringUtils.isEmpty(query.getRegionId())) { |
|||
return ResultUtil.error("缺少商圈参数,仅可查看本区域中奖记录"); |
|||
} |
|||
try { |
|||
return new ResultUtil<>().setData(drawService.myWinning(query.getUserId(), query.getRegionId())); |
|||
} catch (HiverException e) { |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
@ApiOperation(value = "领取中奖奖励") |
|||
@RequestMapping(value = "/draw/receive", method = RequestMethod.POST) |
|||
public Result receive(@RequestBody PlanetQuery query) { |
|||
if (StringUtils.isEmpty(query.getRegionId())) { |
|||
return ResultUtil.error("缺少商圈参数,仅可领取本区域中奖"); |
|||
} |
|||
try { |
|||
drawService.receive(query.getUserId(), query.getRegionId(), query.getWinnerId()); |
|||
return ResultUtil.success("领取成功"); |
|||
} catch (HiverException e) { |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
@ApiOperation(value = "我的星球券明细(分页)") |
|||
@RequestMapping(value = "/ticket/log", method = RequestMethod.POST) |
|||
public Result ticketLog(@RequestBody PlanetQuery query) { |
|||
if (StringUtils.isEmpty(query.getUserId())) { |
|||
return ResultUtil.error("用户id不能为空"); |
|||
} |
|||
if (StringUtils.isEmpty(query.getRegionId())) { |
|||
return ResultUtil.error(REGION_REQUIRED); |
|||
} |
|||
return new ResultUtil<>().setData( |
|||
ticketService.pageLog(query.getUserId(), query.getRegionId(), query.getPageNumber(), query.getPageSize())); |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
package cc.hiver.mall.planet.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import org.springframework.format.annotation.DateTimeFormat; |
|||
|
|||
import javax.persistence.Id; |
|||
import java.io.Serializable; |
|||
import java.math.BigDecimal; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 白嫖星球-BUFF配置 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-BUFF配置") |
|||
@TableName(value = "t_planet_buff", autoResultMap = true) |
|||
public class PlanetBuff implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Id |
|||
@TableId |
|||
@ApiModelProperty(value = "主键") |
|||
private String id; |
|||
|
|||
@ApiModelProperty(value = "商圈id 空为全局默认") |
|||
private String regionId; |
|||
|
|||
@ApiModelProperty(value = "类型 double/shield/lucky/hunt/stealth") |
|||
private String type; |
|||
|
|||
@ApiModelProperty(value = "名称") |
|||
private String name; |
|||
|
|||
@ApiModelProperty(value = "描述") |
|||
private String description; |
|||
|
|||
@ApiModelProperty(value = "图标") |
|||
private String icon; |
|||
|
|||
@ApiModelProperty(value = "消耗券数量") |
|||
private Integer costTickets; |
|||
|
|||
@ApiModelProperty(value = "持续小时") |
|||
private Integer durationHours; |
|||
|
|||
@ApiModelProperty(value = "效果值") |
|||
private BigDecimal effectValue; |
|||
|
|||
@ApiModelProperty(value = "排序") |
|||
private Integer sort; |
|||
|
|||
@ApiModelProperty(value = "是否启用 1是 0否") |
|||
private Integer enabled; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "创建时间") |
|||
private Date createTime; |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
package cc.hiver.mall.planet.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import org.springframework.format.annotation.DateTimeFormat; |
|||
|
|||
import javax.persistence.Id; |
|||
import java.io.Serializable; |
|||
import java.math.BigDecimal; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 白嫖星球-用户BUFF记录 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-用户BUFF记录") |
|||
@TableName(value = "t_planet_buff_record", autoResultMap = true) |
|||
public class PlanetBuffRecord implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Id |
|||
@TableId |
|||
@ApiModelProperty(value = "主键") |
|||
private String id; |
|||
|
|||
@ApiModelProperty(value = "用户id") |
|||
private String userId; |
|||
|
|||
@ApiModelProperty(value = "商圈id") |
|||
private String regionId; |
|||
|
|||
@ApiModelProperty(value = "BUFF配置id") |
|||
private String buffId; |
|||
|
|||
@ApiModelProperty(value = "类型") |
|||
private String type; |
|||
|
|||
@ApiModelProperty(value = "效果值") |
|||
private BigDecimal effectValue; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "生效时间") |
|||
private Date startTime; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "失效时间") |
|||
private Date endTime; |
|||
|
|||
@ApiModelProperty(value = "状态 0生效 1已过期") |
|||
private Integer status; |
|||
|
|||
@ApiModelProperty(value = "来源 buy购买 box宝箱 hunt防护") |
|||
private String source; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "创建时间") |
|||
private Date createTime; |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
package cc.hiver.mall.planet.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import org.springframework.format.annotation.DateTimeFormat; |
|||
|
|||
import javax.persistence.Id; |
|||
import java.io.Serializable; |
|||
import java.math.BigDecimal; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 白嫖星球-开奖记录 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-开奖记录") |
|||
@TableName(value = "t_planet_draw_record", autoResultMap = true) |
|||
public class PlanetDrawRecord implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Id |
|||
@TableId |
|||
@ApiModelProperty(value = "主键") |
|||
private String id; |
|||
|
|||
@ApiModelProperty(value = "奖池id") |
|||
private String poolId; |
|||
|
|||
@ApiModelProperty(value = "商圈id") |
|||
private String regionId; |
|||
|
|||
@ApiModelProperty(value = "期号") |
|||
private String periodNo; |
|||
|
|||
@ApiModelProperty(value = "奖池金额") |
|||
private BigDecimal poolAmount; |
|||
|
|||
@ApiModelProperty(value = "参与人数") |
|||
private Integer joinCount; |
|||
|
|||
@ApiModelProperty(value = "券总数") |
|||
private Integer ticketTotal; |
|||
|
|||
@ApiModelProperty(value = "中奖人数") |
|||
private Integer winnerCount; |
|||
|
|||
@ApiModelProperty(value = "状态 1已开奖") |
|||
private Integer status; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "开奖时间") |
|||
private Date drawTime; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "创建时间") |
|||
private Date createTime; |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
package cc.hiver.mall.planet.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import org.springframework.format.annotation.DateTimeFormat; |
|||
|
|||
import javax.persistence.Id; |
|||
import java.io.Serializable; |
|||
import java.math.BigDecimal; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 白嫖星球-中奖记录 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-中奖记录") |
|||
@TableName(value = "t_planet_draw_winner", autoResultMap = true) |
|||
public class PlanetDrawWinner implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Id |
|||
@TableId |
|||
@ApiModelProperty(value = "主键") |
|||
private String id; |
|||
|
|||
@ApiModelProperty(value = "开奖记录id") |
|||
private String drawId; |
|||
|
|||
@ApiModelProperty(value = "奖池id") |
|||
private String poolId; |
|||
|
|||
@ApiModelProperty(value = "商圈id") |
|||
private String regionId; |
|||
|
|||
@ApiModelProperty(value = "期号") |
|||
private String periodNo; |
|||
|
|||
@ApiModelProperty(value = "中奖用户id") |
|||
private String userId; |
|||
|
|||
@ApiModelProperty(value = "中奖用户昵称") |
|||
private String userName; |
|||
|
|||
@ApiModelProperty(value = "奖项层级") |
|||
private Integer rewardLevel; |
|||
|
|||
@ApiModelProperty(value = "奖项名称") |
|||
private String levelName; |
|||
|
|||
@ApiModelProperty(value = "奖励类型 0现金 1优惠券") |
|||
private Integer rewardType; |
|||
|
|||
@ApiModelProperty(value = "中奖金额") |
|||
private BigDecimal amount; |
|||
|
|||
@ApiModelProperty(value = "优惠券id") |
|||
private String couponId; |
|||
|
|||
@ApiModelProperty(value = "是否已领取 0否 1是") |
|||
private Integer isReceived; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "领取时间") |
|||
private Date receivedTime; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "创建时间") |
|||
private Date createTime; |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
package cc.hiver.mall.planet.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import org.springframework.format.annotation.DateTimeFormat; |
|||
|
|||
import javax.persistence.Id; |
|||
import java.io.Serializable; |
|||
import java.time.LocalDate; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 白嫖星球-追捕记录 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-追捕记录") |
|||
@TableName(value = "t_planet_hunt_record", autoResultMap = true) |
|||
public class PlanetHuntRecord implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Id |
|||
@TableId |
|||
@ApiModelProperty(value = "主键") |
|||
private String id; |
|||
|
|||
@ApiModelProperty(value = "发起人id") |
|||
private String fromUserId; |
|||
|
|||
@ApiModelProperty(value = "发起人昵称") |
|||
private String fromUserName; |
|||
|
|||
@ApiModelProperty(value = "目标用户id") |
|||
private String toUserId; |
|||
|
|||
@ApiModelProperty(value = "目标用户昵称") |
|||
private String toUserName; |
|||
|
|||
@ApiModelProperty(value = "商圈id") |
|||
private String regionId; |
|||
|
|||
@ApiModelProperty(value = "结果 success/shield/fail") |
|||
private String result; |
|||
|
|||
@ApiModelProperty(value = "缴获券") |
|||
private Integer gainTickets; |
|||
|
|||
@ApiModelProperty(value = "悬赏额外券") |
|||
private Integer bountyTickets; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd") |
|||
@ApiModelProperty(value = "追捕日期") |
|||
private LocalDate huntDate; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "创建时间") |
|||
private Date createTime; |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
package cc.hiver.mall.planet.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import org.springframework.format.annotation.DateTimeFormat; |
|||
|
|||
import javax.persistence.Id; |
|||
import java.io.Serializable; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 白嫖星球-星球快讯 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-星球快讯") |
|||
@TableName(value = "t_planet_news", autoResultMap = true) |
|||
public class PlanetNews implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Id |
|||
@TableId |
|||
@ApiModelProperty(value = "主键") |
|||
private String id; |
|||
|
|||
@ApiModelProperty(value = "商圈id") |
|||
private String regionId; |
|||
|
|||
@ApiModelProperty(value = "类型 rank/hunt/box/draw/sys") |
|||
private String type; |
|||
|
|||
@ApiModelProperty(value = "快讯内容") |
|||
private String content; |
|||
|
|||
@ApiModelProperty(value = "关联用户id") |
|||
private String userId; |
|||
|
|||
@ApiModelProperty(value = "关联用户昵称") |
|||
private String userName; |
|||
|
|||
@ApiModelProperty(value = "是否置顶 0否 1是") |
|||
private Integer isTop; |
|||
|
|||
@ApiModelProperty(value = "是否展示 1是 0否") |
|||
private Integer enabled; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "创建时间") |
|||
private Date createTime; |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
package cc.hiver.mall.planet.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import org.springframework.format.annotation.DateTimeFormat; |
|||
|
|||
import javax.persistence.Id; |
|||
import java.io.Serializable; |
|||
import java.math.BigDecimal; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 白嫖星球-奖池期次 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-奖池期次") |
|||
@TableName(value = "t_planet_pool", autoResultMap = true) |
|||
public class PlanetPool implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Id |
|||
@TableId |
|||
@ApiModelProperty(value = "主键") |
|||
private String id; |
|||
|
|||
@ApiModelProperty(value = "商圈/校区id") |
|||
private String regionId; |
|||
|
|||
@ApiModelProperty(value = "期号") |
|||
private String periodNo; |
|||
|
|||
@ApiModelProperty(value = "本期标题") |
|||
private String title; |
|||
|
|||
@ApiModelProperty(value = "奖池总金额") |
|||
private BigDecimal poolAmount; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "开奖时间") |
|||
private Date drawTime; |
|||
|
|||
@ApiModelProperty(value = "参与人数") |
|||
private Integer joinCount; |
|||
|
|||
@ApiModelProperty(value = "本期投入券总数") |
|||
private Integer ticketTotal; |
|||
|
|||
@ApiModelProperty(value = "中奖人数") |
|||
private Integer winnerCount; |
|||
|
|||
@ApiModelProperty(value = "状态 0进行中 1已开奖") |
|||
private Integer status; |
|||
|
|||
@ApiModelProperty(value = "备注/公告") |
|||
private String remark; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "创建时间") |
|||
private Date createTime; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "更新时间") |
|||
private Date updateTime; |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
package cc.hiver.mall.planet.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import org.springframework.format.annotation.DateTimeFormat; |
|||
|
|||
import javax.persistence.Id; |
|||
import java.io.Serializable; |
|||
import java.math.BigDecimal; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 白嫖星球-奖项配置 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-奖项配置") |
|||
@TableName(value = "t_planet_reward", autoResultMap = true) |
|||
public class PlanetReward implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Id |
|||
@TableId |
|||
@ApiModelProperty(value = "主键") |
|||
private String id; |
|||
|
|||
@ApiModelProperty(value = "奖池id") |
|||
private String poolId; |
|||
|
|||
@ApiModelProperty(value = "商圈id") |
|||
private String regionId; |
|||
|
|||
@ApiModelProperty(value = "奖项层级 1一等 2二等 3三等 4幸运") |
|||
private Integer level; |
|||
|
|||
@ApiModelProperty(value = "奖项名称") |
|||
private String levelName; |
|||
|
|||
@ApiModelProperty(value = "奖励类型 0现金 1优惠券") |
|||
private Integer rewardType; |
|||
|
|||
@ApiModelProperty(value = "单份金额") |
|||
private BigDecimal amount; |
|||
|
|||
@ApiModelProperty(value = "优惠券id") |
|||
private String couponId; |
|||
|
|||
@ApiModelProperty(value = "名额数量") |
|||
private Integer quota; |
|||
|
|||
@ApiModelProperty(value = "排序") |
|||
private Integer sort; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "创建时间") |
|||
private Date createTime; |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
package cc.hiver.mall.planet.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import org.springframework.format.annotation.DateTimeFormat; |
|||
|
|||
import javax.persistence.Id; |
|||
import java.io.Serializable; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 白嫖星球-任务规则 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-任务规则") |
|||
@TableName(value = "t_planet_task", autoResultMap = true) |
|||
public class PlanetTask implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Id |
|||
@TableId |
|||
@ApiModelProperty(value = "主键") |
|||
private String id; |
|||
|
|||
@ApiModelProperty(value = "商圈id 空为全局默认") |
|||
private String regionId; |
|||
|
|||
@ApiModelProperty(value = "任务编码 waimai/group/invite/sign") |
|||
private String code; |
|||
|
|||
@ApiModelProperty(value = "任务名称") |
|||
private String name; |
|||
|
|||
@ApiModelProperty(value = "描述") |
|||
private String description; |
|||
|
|||
@ApiModelProperty(value = "图标") |
|||
private String icon; |
|||
|
|||
@ApiModelProperty(value = "奖励券数量") |
|||
private Integer rewardTickets; |
|||
|
|||
@ApiModelProperty(value = "每日上限 0不限") |
|||
private Integer dailyLimit; |
|||
|
|||
@ApiModelProperty(value = "排序") |
|||
private Integer sort; |
|||
|
|||
@ApiModelProperty(value = "是否启用 1是 0否") |
|||
private Integer enabled; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "创建时间") |
|||
private Date createTime; |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
package cc.hiver.mall.planet.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import org.springframework.format.annotation.DateTimeFormat; |
|||
|
|||
import javax.persistence.Id; |
|||
import java.io.Serializable; |
|||
import java.time.LocalDate; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 白嫖星球-用户券汇总 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-用户券汇总") |
|||
@TableName(value = "t_planet_ticket", autoResultMap = true) |
|||
public class PlanetTicket implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Id |
|||
@TableId |
|||
@ApiModelProperty(value = "主键") |
|||
private String id; |
|||
|
|||
@ApiModelProperty(value = "用户id") |
|||
private String userId; |
|||
|
|||
@ApiModelProperty(value = "商圈id") |
|||
private String regionId; |
|||
|
|||
@ApiModelProperty(value = "昵称") |
|||
private String nickname; |
|||
|
|||
@ApiModelProperty(value = "头像") |
|||
private String avatar; |
|||
|
|||
@ApiModelProperty(value = "学院") |
|||
private String college; |
|||
|
|||
@ApiModelProperty(value = "当前可用券数量") |
|||
private Integer ticketCount; |
|||
|
|||
@ApiModelProperty(value = "历史累计获得券") |
|||
private Integer totalTicket; |
|||
|
|||
@ApiModelProperty(value = "连续签到天数") |
|||
private Integer consecutiveSignDays; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd") |
|||
@ApiModelProperty(value = "最近签到日期") |
|||
private LocalDate lastSignDate; |
|||
|
|||
@ApiModelProperty(value = "连续霸榜天数") |
|||
private Integer rankKeepDays; |
|||
|
|||
@ApiModelProperty(value = "上次排名") |
|||
private Integer lastRankNo; |
|||
|
|||
@ApiModelProperty(value = "今日已追捕次数") |
|||
private Integer huntCountToday; |
|||
|
|||
@ApiModelProperty(value = "今日被追捕成功次数") |
|||
private Integer huntedSuccessToday; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd") |
|||
@ApiModelProperty(value = "最近开宝箱日期") |
|||
private LocalDate boxOpenedDate; |
|||
|
|||
@ApiModelProperty(value = "所属期号") |
|||
private String periodNo; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "创建时间") |
|||
private Date createTime; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "更新时间") |
|||
private Date updateTime; |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
package cc.hiver.mall.planet.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import org.springframework.format.annotation.DateTimeFormat; |
|||
|
|||
import javax.persistence.Id; |
|||
import java.io.Serializable; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 白嫖星球-券流水 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-券流水") |
|||
@TableName(value = "t_planet_ticket_log", autoResultMap = true) |
|||
public class PlanetTicketLog implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Id |
|||
@TableId |
|||
@ApiModelProperty(value = "主键") |
|||
private String id; |
|||
|
|||
@ApiModelProperty(value = "用户id") |
|||
private String userId; |
|||
|
|||
@ApiModelProperty(value = "商圈id") |
|||
private String regionId; |
|||
|
|||
@ApiModelProperty(value = "变更数量(正得负扣)") |
|||
private Integer changeCount; |
|||
|
|||
@ApiModelProperty(value = "变更后余额") |
|||
private Integer balance; |
|||
|
|||
@ApiModelProperty(value = "来源 order/group/invite/sign/box/hunt/buff/draw") |
|||
private String type; |
|||
|
|||
@ApiModelProperty(value = "来源业务id(幂等)") |
|||
private String sourceId; |
|||
|
|||
@ApiModelProperty(value = "期号") |
|||
private String periodNo; |
|||
|
|||
@ApiModelProperty(value = "说明") |
|||
private String remark; |
|||
|
|||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
@ApiModelProperty(value = "创建时间") |
|||
private Date createTime; |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
package cc.hiver.mall.planet.hook; |
|||
|
|||
import cc.hiver.mall.planet.constant.PlanetConstant; |
|||
import cc.hiver.mall.planet.mq.PlanetTicketProducer; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
/** |
|||
* 白嫖星球-业务发券回调 |
|||
* <p> |
|||
* 仅做轻量 MQ 投递,所有 DB 操作放到消费端异步执行,保证不影响主业务速度和流程; |
|||
* 任意异常都被吞掉,绝不向上抛。 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Slf4j |
|||
@Component |
|||
public class PlanetRewardHook { |
|||
|
|||
@Autowired |
|||
private PlanetTicketProducer ticketProducer; |
|||
|
|||
/** |
|||
* 外卖/普通订单完成发券 |
|||
*/ |
|||
public void onOrderFinish(String userId, String regionId, String orderId) { |
|||
rewardByTask(userId, regionId, PlanetConstant.TASK_WAIMAI, PlanetConstant.TICKET_TYPE_ORDER, orderId, "外卖订单完成"); |
|||
} |
|||
|
|||
/** |
|||
* 团购订单完成发券 |
|||
*/ |
|||
public void onGroupFinish(String userId, String regionId, String groupOrderId) { |
|||
rewardByTask(userId, regionId, PlanetConstant.TASK_GROUP, PlanetConstant.TICKET_TYPE_GROUP, groupOrderId, "团购订单完成"); |
|||
} |
|||
|
|||
/** |
|||
* 邀请好友注册发券 |
|||
*/ |
|||
public void onInviteRegister(String inviterUserId, String regionId, String sourceId) { |
|||
rewardByTask(inviterUserId, regionId, PlanetConstant.TASK_INVITE, PlanetConstant.TICKET_TYPE_INVITE, sourceId, "邀请好友注册"); |
|||
} |
|||
|
|||
private void rewardByTask(String userId, String regionId, String taskCode, String type, String sourceId, String remark) { |
|||
if (StringUtils.isEmpty(userId)) { |
|||
return; |
|||
} |
|||
try { |
|||
ticketProducer.sendTaskReward(userId, regionId, taskCode, type, sourceId, remark); |
|||
} catch (Exception e) { |
|||
log.warn("[白嫖星球] 发券投递失败 userId={}, task={}, {}", userId, taskCode, e.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package cc.hiver.mall.planet.mapper; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetBuff; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
@Repository |
|||
public interface PlanetBuffMapper extends BaseMapper<PlanetBuff> { |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package cc.hiver.mall.planet.mapper; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetBuffRecord; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
@Repository |
|||
public interface PlanetBuffRecordMapper extends BaseMapper<PlanetBuffRecord> { |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package cc.hiver.mall.planet.mapper; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetDrawRecord; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
@Repository |
|||
public interface PlanetDrawRecordMapper extends BaseMapper<PlanetDrawRecord> { |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package cc.hiver.mall.planet.mapper; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetDrawWinner; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
@Repository |
|||
public interface PlanetDrawWinnerMapper extends BaseMapper<PlanetDrawWinner> { |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package cc.hiver.mall.planet.mapper; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetHuntRecord; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
@Repository |
|||
public interface PlanetHuntRecordMapper extends BaseMapper<PlanetHuntRecord> { |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package cc.hiver.mall.planet.mapper; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetNews; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
@Repository |
|||
public interface PlanetNewsMapper extends BaseMapper<PlanetNews> { |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package cc.hiver.mall.planet.mapper; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetPool; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
@Repository |
|||
public interface PlanetPoolMapper extends BaseMapper<PlanetPool> { |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package cc.hiver.mall.planet.mapper; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetReward; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
@Repository |
|||
public interface PlanetRewardMapper extends BaseMapper<PlanetReward> { |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package cc.hiver.mall.planet.mapper; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetTask; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
@Repository |
|||
public interface PlanetTaskMapper extends BaseMapper<PlanetTask> { |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package cc.hiver.mall.planet.mapper; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetTicketLog; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
@Repository |
|||
public interface PlanetTicketLogMapper extends BaseMapper<PlanetTicketLog> { |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package cc.hiver.mall.planet.mapper; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetTicket; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
@Repository |
|||
public interface PlanetTicketMapper extends BaseMapper<PlanetTicket> { |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
package cc.hiver.mall.planet.mq; |
|||
|
|||
import org.springframework.amqp.core.Binding; |
|||
import org.springframework.amqp.core.BindingBuilder; |
|||
import org.springframework.amqp.core.DirectExchange; |
|||
import org.springframework.amqp.core.Queue; |
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.context.annotation.Configuration; |
|||
|
|||
/** |
|||
* 白嫖星球 MQ 配置 |
|||
* <p> |
|||
* 发券、生成快讯等非核心动作异步化,避免阻塞下单/注册等主业务流程。 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Configuration |
|||
public class PlanetMqConfig { |
|||
|
|||
public static final String PLANET_EXCHANGE = "planet.direct.exchange"; |
|||
|
|||
/** 发券队列 */ |
|||
public static final String TICKET_GRANT_QUEUE = "planet.ticket.grant.queue"; |
|||
public static final String TICKET_GRANT_ROUTING = "planet.ticket.grant.routing.key"; |
|||
|
|||
/** 快讯生成队列 */ |
|||
public static final String NEWS_QUEUE = "planet.news.queue"; |
|||
public static final String NEWS_ROUTING = "planet.news.routing.key"; |
|||
|
|||
@Bean |
|||
public DirectExchange planetDirectExchange() { |
|||
return new DirectExchange(PLANET_EXCHANGE, true, false); |
|||
} |
|||
|
|||
@Bean |
|||
public Queue planetTicketGrantQueue() { |
|||
return new Queue(TICKET_GRANT_QUEUE, true); |
|||
} |
|||
|
|||
@Bean |
|||
public Binding bindingPlanetTicketGrantQueue() { |
|||
return BindingBuilder.bind(planetTicketGrantQueue()).to(planetDirectExchange()).with(TICKET_GRANT_ROUTING); |
|||
} |
|||
|
|||
@Bean |
|||
public Queue planetNewsQueue() { |
|||
return new Queue(NEWS_QUEUE, true); |
|||
} |
|||
|
|||
@Bean |
|||
public Binding bindingPlanetNewsQueue() { |
|||
return BindingBuilder.bind(planetNewsQueue()).to(planetDirectExchange()).with(NEWS_ROUTING); |
|||
} |
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
package cc.hiver.mall.planet.mq; |
|||
|
|||
import cc.hiver.mall.entity.MallOrder; |
|||
import cc.hiver.mall.planet.entity.PlanetTask; |
|||
import cc.hiver.mall.planet.service.PlanetNewsService; |
|||
import cc.hiver.mall.planet.service.PlanetTaskService; |
|||
import cc.hiver.mall.planet.service.PlanetTicketService; |
|||
import cc.hiver.mall.service.mybatis.MallOrderService; |
|||
import com.alibaba.fastjson.JSONObject; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.amqp.AmqpRejectAndDontRequeueException; |
|||
import org.springframework.amqp.rabbit.annotation.RabbitListener; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Component; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
|
|||
/** |
|||
* 白嫖星球-发券/快讯异步消费者 |
|||
* <p> |
|||
* 真正的发券 DB 操作在此完成;addTickets 通过 sourceId 幂等,可安全应对 MQ 至少一次投递。 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Slf4j |
|||
@Component |
|||
public class PlanetTicketConsumer { |
|||
|
|||
@Autowired |
|||
private PlanetTaskService taskService; |
|||
|
|||
@Autowired |
|||
private PlanetTicketService ticketService; |
|||
|
|||
@Autowired |
|||
private PlanetNewsService newsService; |
|||
|
|||
@Autowired |
|||
private MallOrderService mallOrderService; |
|||
|
|||
@RabbitListener(queues = PlanetMqConfig.TICKET_GRANT_QUEUE) |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void handleTicketGrant(String message) { |
|||
try { |
|||
final JSONObject json = JSONObject.parseObject(message); |
|||
final String userId = json.getString("userId"); |
|||
String regionId = json.getString("regionId"); |
|||
final String taskCode = json.getString("taskCode"); |
|||
final String type = json.getString("type"); |
|||
final String sourceId = json.getString("sourceId"); |
|||
final String remark = json.getString("remark"); |
|||
if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(taskCode)) { |
|||
log.warn("[白嫖星球MQ] 发券消息参数异常: {}", message); |
|||
return; |
|||
} |
|||
// 商圈强隔离:消息缺 regionId 时(如邀请注册),从用户最近一笔订单兜底解析,确保券归属正确商圈
|
|||
if (StringUtils.isEmpty(regionId)) { |
|||
regionId = resolveRegionByUserOrder(userId); |
|||
} |
|||
if (StringUtils.isEmpty(regionId)) { |
|||
log.warn("[白嫖星球MQ] 无法确定用户商圈,跳过发券 userId={}, task={}", userId, taskCode); |
|||
return; |
|||
} |
|||
final PlanetTask task = taskService.getTaskConfig(regionId, taskCode); |
|||
if (task == null || task.getEnabled() == null || task.getEnabled() != 1) { |
|||
log.info("[白嫖星球MQ] 任务未启用,跳过发券 task={}", taskCode); |
|||
return; |
|||
} |
|||
final int reward = task.getRewardTickets() == null ? 1 : task.getRewardTickets(); |
|||
final int added = ticketService.addTickets(userId, regionId, reward, type, sourceId, remark); |
|||
log.info("[白嫖星球MQ] 发券完成 userId={}, task={}, 实发={}", userId, taskCode, added); |
|||
} catch (Exception e) { |
|||
// 业务异常无需重投(重投也会再次失败),仅记录日志,进入死信/丢弃
|
|||
log.error("[白嫖星球MQ] 发券消息处理异常: {}, msg={}", e.getMessage(), message, e); |
|||
throw new AmqpRejectAndDontRequeueException(e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 从用户最近一笔带商圈的订单解析 regionId(用于邀请注册等无商圈上下文的发券)。 |
|||
*/ |
|||
private String resolveRegionByUserOrder(String userId) { |
|||
try { |
|||
final MallOrder order = mallOrderService.getOne(new LambdaQueryWrapper<MallOrder>() |
|||
.select(MallOrder::getRegionId) |
|||
.eq(MallOrder::getUserId, userId) |
|||
.isNotNull(MallOrder::getRegionId) |
|||
.ne(MallOrder::getRegionId, "") |
|||
.orderByDesc(MallOrder::getCreateTime) |
|||
.last("limit 1")); |
|||
return order == null ? null : order.getRegionId(); |
|||
} catch (Exception e) { |
|||
log.warn("[白嫖星球MQ] 解析用户商圈失败 userId={}, {}", userId, e.getMessage()); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
@RabbitListener(queues = PlanetMqConfig.NEWS_QUEUE) |
|||
public void handleNews(String message) { |
|||
try { |
|||
final JSONObject json = JSONObject.parseObject(message); |
|||
newsService.addNews(json.getString("regionId"), json.getString("type"), json.getString("content"), null, null); |
|||
} catch (Exception e) { |
|||
log.error("[白嫖星球MQ] 快讯消息处理异常: {}, msg={}", e.getMessage(), message, e); |
|||
throw new AmqpRejectAndDontRequeueException(e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
package cc.hiver.mall.planet.mq; |
|||
|
|||
import com.alibaba.fastjson.JSON; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.amqp.rabbit.core.RabbitTemplate; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* 白嫖星球-发券/快讯异步消息生产者 |
|||
* <p> |
|||
* 仅做轻量投递,不做任何 DB 操作,保证主业务流程不被发券逻辑拖慢。 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Slf4j |
|||
@Component |
|||
public class PlanetTicketProducer { |
|||
|
|||
@Autowired |
|||
private RabbitTemplate rabbitTemplate; |
|||
|
|||
/** |
|||
* 投递一条「按任务发券」消息。消费端负责解析任务配置并幂等发券。 |
|||
*/ |
|||
public void sendTaskReward(String userId, String regionId, String taskCode, String type, String sourceId, String remark) { |
|||
Map<String, Object> msg = new HashMap<>(8); |
|||
msg.put("userId", userId); |
|||
msg.put("regionId", regionId); |
|||
msg.put("taskCode", taskCode); |
|||
msg.put("type", type); |
|||
msg.put("sourceId", sourceId); |
|||
msg.put("remark", remark); |
|||
try { |
|||
rabbitTemplate.convertAndSend(PlanetMqConfig.PLANET_EXCHANGE, PlanetMqConfig.TICKET_GRANT_ROUTING, JSON.toJSONString(msg)); |
|||
log.info("[白嫖星球MQ] 已投递发券消息 userId={}, task={}, sourceId={}", userId, taskCode, sourceId); |
|||
} catch (Exception e) { |
|||
// 投递失败仅记录,绝不影响主业务
|
|||
log.warn("[白嫖星球MQ] 发券消息投递失败 userId={}, task={}, {}", userId, taskCode, e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 投递一条快讯生成消息。 |
|||
*/ |
|||
public void sendNews(String regionId, String type, String content) { |
|||
Map<String, Object> msg = new HashMap<>(4); |
|||
msg.put("regionId", regionId); |
|||
msg.put("type", type); |
|||
msg.put("content", content); |
|||
try { |
|||
rabbitTemplate.convertAndSend(PlanetMqConfig.PLANET_EXCHANGE, PlanetMqConfig.NEWS_ROUTING, JSON.toJSONString(msg)); |
|||
} catch (Exception e) { |
|||
log.warn("[白嫖星球MQ] 快讯消息投递失败 regionId={}, {}", regionId, e.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
package cc.hiver.mall.planet.pojo; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
import java.io.Serializable; |
|||
|
|||
/** |
|||
* 白嫖星球-宝箱开启结果 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-宝箱开启结果") |
|||
public class PlanetBoxResultVo implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@ApiModelProperty(value = "奖励类型 ticket券/buff增益") |
|||
private String rewardType; |
|||
|
|||
@ApiModelProperty(value = "获得券数量(ticket时)") |
|||
private Integer ticketCount; |
|||
|
|||
@ApiModelProperty(value = "BUFF类型(buff时)") |
|||
private String buffType; |
|||
|
|||
@ApiModelProperty(value = "奖励名称") |
|||
private String rewardName; |
|||
|
|||
@ApiModelProperty(value = "提示文案") |
|||
private String message; |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
package cc.hiver.mall.planet.pojo; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
import java.io.Serializable; |
|||
import java.math.BigDecimal; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 白嫖星球-BUFF项(含当前是否生效) |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-BUFF项") |
|||
public class PlanetBuffVo implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@ApiModelProperty(value = "BUFF配置id") |
|||
private String id; |
|||
|
|||
@ApiModelProperty(value = "类型") |
|||
private String type; |
|||
|
|||
@ApiModelProperty(value = "名称") |
|||
private String name; |
|||
|
|||
@ApiModelProperty(value = "描述") |
|||
private String description; |
|||
|
|||
@ApiModelProperty(value = "图标") |
|||
private String icon; |
|||
|
|||
@ApiModelProperty(value = "消耗券数量") |
|||
private Integer costTickets; |
|||
|
|||
@ApiModelProperty(value = "持续小时") |
|||
private Integer durationHours; |
|||
|
|||
@ApiModelProperty(value = "效果值") |
|||
private BigDecimal effectValue; |
|||
|
|||
@ApiModelProperty(value = "当前是否生效") |
|||
private Boolean active; |
|||
|
|||
@ApiModelProperty(value = "当前生效到期时间") |
|||
private Date activeEndTime; |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
package cc.hiver.mall.planet.pojo; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetNews; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
import java.io.Serializable; |
|||
import java.math.BigDecimal; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 白嫖星球-首页聚合数据 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-首页聚合数据") |
|||
public class PlanetHomeVo implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
// ---------- 宇宙Header ----------
|
|||
@ApiModelProperty(value = "本期奖金池") |
|||
private BigDecimal poolAmount; |
|||
|
|||
@ApiModelProperty(value = "开奖时间") |
|||
private Date drawTime; |
|||
|
|||
@ApiModelProperty(value = "距离开奖剩余毫秒") |
|||
private Long countdownMillis; |
|||
|
|||
@ApiModelProperty(value = "本期参与人数") |
|||
private Integer joinCount; |
|||
|
|||
@ApiModelProperty(value = "期号") |
|||
private String periodNo; |
|||
|
|||
// ---------- 我的星球中心 ----------
|
|||
@ApiModelProperty(value = "我的券数量") |
|||
private Integer myTicketCount; |
|||
|
|||
@ApiModelProperty(value = "我的排名(0未上榜)") |
|||
private Integer myRankNo; |
|||
|
|||
@ApiModelProperty(value = "昵称") |
|||
private String nickname; |
|||
|
|||
@ApiModelProperty(value = "头像") |
|||
private String avatar; |
|||
|
|||
@ApiModelProperty(value = "连续签到天数") |
|||
private Integer consecutiveSignDays; |
|||
|
|||
@ApiModelProperty(value = "今日是否已签到") |
|||
private Boolean signedToday; |
|||
|
|||
@ApiModelProperty(value = "今日免费宝箱是否可开") |
|||
private Boolean boxAvailable; |
|||
|
|||
@ApiModelProperty(value = "今日剩余追捕次数") |
|||
private Integer remainHunt; |
|||
|
|||
@ApiModelProperty(value = "等级(危险等级)") |
|||
private String level; |
|||
|
|||
@ApiModelProperty(value = "我当前生效的BUFF") |
|||
private List<PlanetBuffVo> myBuffs; |
|||
|
|||
// ---------- 各板块 ----------
|
|||
@ApiModelProperty(value = "任务列表") |
|||
private List<PlanetTaskVo> tasks; |
|||
|
|||
@ApiModelProperty(value = "通缉榜TOP10") |
|||
private List<PlanetRankItemVo> rankList; |
|||
|
|||
@ApiModelProperty(value = "BUFF商店") |
|||
private List<PlanetBuffVo> buffShop; |
|||
|
|||
@ApiModelProperty(value = "星球快讯") |
|||
private List<PlanetNews> newsList; |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
package cc.hiver.mall.planet.pojo; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
import java.io.Serializable; |
|||
|
|||
/** |
|||
* 白嫖星球-追捕结果 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-追捕结果") |
|||
public class PlanetHuntResultVo implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@ApiModelProperty(value = "结果 success缴获/shield对方护盾/fail失败") |
|||
private String result; |
|||
|
|||
@ApiModelProperty(value = "缴获券") |
|||
private Integer gainTickets; |
|||
|
|||
@ApiModelProperty(value = "悬赏额外券") |
|||
private Integer bountyTickets; |
|||
|
|||
@ApiModelProperty(value = "本次合计获得券") |
|||
private Integer totalGain; |
|||
|
|||
@ApiModelProperty(value = "目标昵称") |
|||
private String targetName; |
|||
|
|||
@ApiModelProperty(value = "今日剩余追捕次数") |
|||
private Integer remainHunt; |
|||
|
|||
@ApiModelProperty(value = "提示文案") |
|||
private String message; |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
package cc.hiver.mall.planet.pojo; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
import java.io.Serializable; |
|||
|
|||
/** |
|||
* 白嫖星球-App端通用请求参数 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-App端通用请求参数") |
|||
public class PlanetQuery implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@ApiModelProperty(value = "用户id") |
|||
private String userId; |
|||
|
|||
@ApiModelProperty(value = "商圈/校区id") |
|||
private String regionId; |
|||
|
|||
@ApiModelProperty(value = "昵称(首次进入冗余)") |
|||
private String nickname; |
|||
|
|||
@ApiModelProperty(value = "头像(首次进入冗余)") |
|||
private String avatar; |
|||
|
|||
@ApiModelProperty(value = "学院") |
|||
private String college; |
|||
|
|||
@ApiModelProperty(value = "目标用户id(追捕用)") |
|||
private String toUserId; |
|||
|
|||
@ApiModelProperty(value = "任务编码") |
|||
private String taskCode; |
|||
|
|||
@ApiModelProperty(value = "BUFF配置id") |
|||
private String buffId; |
|||
|
|||
@ApiModelProperty(value = "中奖记录id") |
|||
private String winnerId; |
|||
|
|||
@ApiModelProperty(value = "页码(从1开始)") |
|||
private Integer pageNumber; |
|||
|
|||
@ApiModelProperty(value = "每页数量") |
|||
private Integer pageSize; |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
package cc.hiver.mall.planet.pojo; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
import java.io.Serializable; |
|||
|
|||
/** |
|||
* 白嫖星球-通缉榜单项 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-通缉榜单项") |
|||
public class PlanetRankItemVo implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@ApiModelProperty(value = "排名") |
|||
private Integer rankNo; |
|||
|
|||
@ApiModelProperty(value = "用户id") |
|||
private String userId; |
|||
|
|||
@ApiModelProperty(value = "昵称") |
|||
private String nickname; |
|||
|
|||
@ApiModelProperty(value = "头像") |
|||
private String avatar; |
|||
|
|||
@ApiModelProperty(value = "学院") |
|||
private String college; |
|||
|
|||
@ApiModelProperty(value = "当前券数量") |
|||
private Integer ticketCount; |
|||
|
|||
@ApiModelProperty(value = "危险等级 C/B/A/S/SSS") |
|||
private String dangerLevel; |
|||
|
|||
@ApiModelProperty(value = "危险等级名称") |
|||
private String dangerLevelName; |
|||
|
|||
@ApiModelProperty(value = "连续霸榜天数") |
|||
private Integer rankKeepDays; |
|||
|
|||
@ApiModelProperty(value = "悬赏额外券") |
|||
private Integer bountyTickets; |
|||
|
|||
@ApiModelProperty(value = "是否开启防护罩") |
|||
private Boolean shielded; |
|||
|
|||
@ApiModelProperty(value = "是否是自己") |
|||
private Boolean self; |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
package cc.hiver.mall.planet.pojo; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
import java.io.Serializable; |
|||
|
|||
/** |
|||
* 白嫖星球-任务项(含今日完成状态) |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Data |
|||
@ApiModel(value = "白嫖星球-任务项") |
|||
public class PlanetTaskVo implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@ApiModelProperty(value = "任务编码") |
|||
private String code; |
|||
|
|||
@ApiModelProperty(value = "任务名称") |
|||
private String name; |
|||
|
|||
@ApiModelProperty(value = "描述") |
|||
private String description; |
|||
|
|||
@ApiModelProperty(value = "图标") |
|||
private String icon; |
|||
|
|||
@ApiModelProperty(value = "奖励券数量") |
|||
private Integer rewardTickets; |
|||
|
|||
@ApiModelProperty(value = "每日上限 0不限") |
|||
private Integer dailyLimit; |
|||
|
|||
@ApiModelProperty(value = "今日已完成次数") |
|||
private Integer todayCount; |
|||
|
|||
@ApiModelProperty(value = "今日是否可领取(签到类)") |
|||
private Boolean canClaim; |
|||
|
|||
@ApiModelProperty(value = "动作类型 claim可领取/auto订单自动") |
|||
private String actionType; |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
package cc.hiver.mall.planet.service; |
|||
|
|||
import cc.hiver.core.common.vo.PageVo; |
|||
import cc.hiver.mall.planet.entity.*; |
|||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 白嫖星球-后台管理服务 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
public interface PlanetAdminService { |
|||
|
|||
// 奖池
|
|||
Page<PlanetPool> pagePools(PageVo pageVo, String regionId, Integer status); |
|||
|
|||
PlanetPool savePool(PlanetPool pool); |
|||
|
|||
void deletePool(String id); |
|||
|
|||
// 奖项
|
|||
List<PlanetReward> rewardsOfPool(String poolId); |
|||
|
|||
PlanetReward saveReward(PlanetReward reward); |
|||
|
|||
void deleteReward(String id); |
|||
|
|||
// 任务
|
|||
Page<PlanetTask> pageTasks(PageVo pageVo, String regionId); |
|||
|
|||
PlanetTask saveTask(PlanetTask task); |
|||
|
|||
void deleteTask(String id); |
|||
|
|||
// BUFF
|
|||
Page<PlanetBuff> pageBuffs(PageVo pageVo, String regionId); |
|||
|
|||
PlanetBuff saveBuff(PlanetBuff buff); |
|||
|
|||
void deleteBuff(String id); |
|||
|
|||
// 快讯
|
|||
Page<PlanetNews> pageNews(PageVo pageVo, String regionId); |
|||
|
|||
PlanetNews saveNews(PlanetNews news); |
|||
|
|||
void deleteNews(String id); |
|||
|
|||
// 排行榜(查看)
|
|||
Page<PlanetTicket> pageRank(PageVo pageVo, String regionId); |
|||
|
|||
// 追捕记录
|
|||
Page<PlanetHuntRecord> pageHunts(PageVo pageVo, String regionId); |
|||
|
|||
// 开奖记录
|
|||
Page<PlanetDrawRecord> pageDraws(PageVo pageVo, String regionId); |
|||
|
|||
// 中奖记录
|
|||
Page<PlanetDrawWinner> pageWinners(PageVo pageVo, String regionId, String periodNo); |
|||
|
|||
// 手动开奖
|
|||
PlanetDrawRecord manualDraw(String poolId, String regionId); |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
package cc.hiver.mall.planet.service; |
|||
|
|||
import cc.hiver.mall.planet.pojo.PlanetBoxResultVo; |
|||
|
|||
/** |
|||
* 白嫖星球-幸运宝箱服务 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
public interface PlanetBoxService { |
|||
|
|||
/** |
|||
* 开启每日免费宝箱 |
|||
*/ |
|||
PlanetBoxResultVo openDailyBox(String userId, String regionId); |
|||
|
|||
/** |
|||
* 今日是否还可开宝箱 |
|||
*/ |
|||
boolean boxAvailable(String userId, String regionId); |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
package cc.hiver.mall.planet.service; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetBuffRecord; |
|||
import cc.hiver.mall.planet.pojo.PlanetBuffVo; |
|||
|
|||
import java.util.Collection; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
|
|||
/** |
|||
* 白嫖星球-BUFF服务 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
public interface PlanetBuffService { |
|||
|
|||
/** |
|||
* BUFF商店列表(含当前用户是否生效) |
|||
*/ |
|||
List<PlanetBuffVo> shopList(String userId, String regionId); |
|||
|
|||
/** |
|||
* 购买BUFF(消耗券并生效) |
|||
*/ |
|||
PlanetBuffVo buyBuff(String userId, String regionId, String buffId); |
|||
|
|||
/** |
|||
* 当前用户生效中的BUFF列表 |
|||
*/ |
|||
List<PlanetBuffRecord> activeBuffs(String userId); |
|||
|
|||
/** |
|||
* 判断某类型BUFF是否生效 |
|||
*/ |
|||
boolean hasActiveBuff(String userId, String type); |
|||
|
|||
/** |
|||
* 给用户授予一个BUFF(宝箱/自动防护使用) |
|||
*/ |
|||
PlanetBuffRecord grantBuff(String userId, String regionId, String type, String source); |
|||
|
|||
/** |
|||
* 幸运BUFF开奖权重加成系数(1+effectValue),无则1 |
|||
*/ |
|||
double luckyWeightFactor(String userId); |
|||
|
|||
/** |
|||
* 批量查询多个用户当前生效中的 BUFF 类型集合(一次查库,内存组装) |
|||
* |
|||
* @param userIds 用户id集合 |
|||
* @return Map<userId, Set<buffType>>,无生效BUFF的用户不在map中 |
|||
*/ |
|||
Map<String, Set<String>> activeBuffTypes(Collection<String> userIds); |
|||
|
|||
/** |
|||
* 批量查询多个用户的幸运BUFF权重系数(一次查库,内存组装) |
|||
* |
|||
* @param userIds 用户id集合 |
|||
* @return Map<userId, 系数(1+effectValue)>,无幸运BUFF的用户不在map中 |
|||
*/ |
|||
Map<String, Double> luckyWeightFactors(Collection<String> userIds); |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
package cc.hiver.mall.planet.service; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetDrawRecord; |
|||
import cc.hiver.mall.planet.entity.PlanetDrawWinner; |
|||
import cc.hiver.mall.planet.entity.PlanetPool; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 白嫖星球-开奖服务 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
public interface PlanetDrawService { |
|||
|
|||
/** |
|||
* 获取当前进行中的奖池(无则自动创建默认奖池) |
|||
*/ |
|||
PlanetPool getCurrentPool(String regionId); |
|||
|
|||
/** |
|||
* 执行开奖(加权抽取,必中名单) |
|||
* |
|||
* @return 开奖记录 |
|||
*/ |
|||
PlanetDrawRecord drawPool(String poolId); |
|||
|
|||
/** |
|||
* 扫描所有到点未开奖的奖池并开奖(定时任务用) |
|||
*/ |
|||
int drawDuePools(); |
|||
|
|||
/** |
|||
* 最近一期开奖记录 |
|||
*/ |
|||
PlanetDrawRecord latestDrawRecord(String regionId); |
|||
|
|||
/** |
|||
* 某次开奖的中奖名单 |
|||
*/ |
|||
List<PlanetDrawWinner> winnersOfDraw(String drawId); |
|||
|
|||
/** |
|||
* 我的中奖记录(仅限本商圈) |
|||
*/ |
|||
List<PlanetDrawWinner> myWinning(String userId, String regionId); |
|||
|
|||
/** |
|||
* 领取中奖奖励(校验商圈一致) |
|||
*/ |
|||
void receive(String userId, String regionId, String winnerId); |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
package cc.hiver.mall.planet.service; |
|||
|
|||
import cc.hiver.mall.planet.pojo.PlanetHuntResultVo; |
|||
|
|||
/** |
|||
* 白嫖星球-追捕服务 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
public interface PlanetHuntService { |
|||
|
|||
/** |
|||
* 发起追捕 |
|||
*/ |
|||
PlanetHuntResultVo hunt(String fromUserId, String regionId, String toUserId); |
|||
|
|||
/** |
|||
* 今日剩余追捕次数 |
|||
*/ |
|||
int remainHunt(String userId); |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
package cc.hiver.mall.planet.service; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetNews; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 白嫖星球-星球快讯服务 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
public interface PlanetNewsService { |
|||
|
|||
/** |
|||
* 新增快讯 |
|||
*/ |
|||
void addNews(String regionId, String type, String content, String userId, String userName); |
|||
|
|||
/** |
|||
* 获取最新快讯 |
|||
*/ |
|||
List<PlanetNews> latest(String regionId, int limit); |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
package cc.hiver.mall.planet.service; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetTicket; |
|||
import cc.hiver.mall.planet.pojo.PlanetRankItemVo; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 白嫖星球-通缉榜服务 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
public interface PlanetRankService { |
|||
|
|||
/** |
|||
* 获取通缉榜TOP N(排除隐身BUFF用户) |
|||
*/ |
|||
List<PlanetRankItemVo> rankTop(String regionId, int topN, String currentUserId); |
|||
|
|||
/** |
|||
* 获取榜单内某用户的原始券记录(用于追捕悬赏计算),不在榜返回null |
|||
*/ |
|||
PlanetTicket getRankTarget(String regionId, String userId); |
|||
|
|||
/** |
|||
* 用户当前排名(0未上榜) |
|||
*/ |
|||
int userRankNo(String regionId, String userId); |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
package cc.hiver.mall.planet.service; |
|||
|
|||
import cc.hiver.mall.planet.pojo.PlanetHomeVo; |
|||
import cc.hiver.mall.planet.pojo.PlanetQuery; |
|||
|
|||
/** |
|||
* 白嫖星球-首页聚合服务 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
public interface PlanetService { |
|||
|
|||
/** |
|||
* 首页聚合数据 |
|||
*/ |
|||
PlanetHomeVo home(PlanetQuery query); |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
package cc.hiver.mall.planet.service; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetTask; |
|||
import cc.hiver.mall.planet.pojo.PlanetTaskVo; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 白嫖星球-任务服务 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
public interface PlanetTaskService { |
|||
|
|||
/** |
|||
* 任务列表(含今日完成状态) |
|||
*/ |
|||
List<PlanetTaskVo> listTasks(String userId, String regionId); |
|||
|
|||
/** |
|||
* 每日签到 |
|||
* |
|||
* @return 获得券数量 |
|||
*/ |
|||
int sign(String userId, String regionId); |
|||
|
|||
/** |
|||
* 领取任务奖励(目前仅签到可主动领取) |
|||
*/ |
|||
int claim(String userId, String regionId, String taskCode); |
|||
|
|||
/** |
|||
* 根据编码获取生效任务配置 |
|||
*/ |
|||
PlanetTask getTaskConfig(String regionId, String code); |
|||
|
|||
/** |
|||
* 今日是否已签到 |
|||
*/ |
|||
boolean signedToday(String userId, String regionId); |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
package cc.hiver.mall.planet.service; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetTicket; |
|||
import cc.hiver.mall.planet.entity.PlanetTicketLog; |
|||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|||
|
|||
/** |
|||
* 白嫖星球-星球券服务 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
public interface PlanetTicketService { |
|||
|
|||
/** |
|||
* 获取或创建用户券记录(自动补全昵称头像) |
|||
*/ |
|||
PlanetTicket getOrCreate(String userId, String regionId, String nickname, String avatar, String college); |
|||
|
|||
/** |
|||
* 查询用户券记录(不存在返回null) |
|||
*/ |
|||
PlanetTicket getByUser(String userId, String regionId); |
|||
|
|||
/** |
|||
* 增加星球券(支持双倍BUFF与幂等) |
|||
* |
|||
* @param baseCount 基础数量(BUFF前) |
|||
* @param type 来源类型 |
|||
* @param sourceId 来源业务id(非空时做幂等去重) |
|||
* @return 实际增加数量(0表示被幂等拦截) |
|||
*/ |
|||
int addTickets(String userId, String regionId, int baseCount, String type, String sourceId, String remark); |
|||
|
|||
/** |
|||
* 扣减星球券(余额不足抛异常) |
|||
* |
|||
* @return 扣减后余额 |
|||
*/ |
|||
int deductTickets(String userId, String regionId, int count, String type, String sourceId, String remark); |
|||
|
|||
/** |
|||
* 当前用户在商圈内的排名(0表示未上榜) |
|||
*/ |
|||
int getRankNo(String regionId, String userId); |
|||
|
|||
/** |
|||
* 分页查询用户星球券明细流水(按时间倒序) |
|||
*/ |
|||
Page<PlanetTicketLog> pageLog(String userId, String regionId, Integer pageNumber, Integer pageSize); |
|||
} |
|||
@ -0,0 +1,288 @@ |
|||
package cc.hiver.mall.planet.service.impl; |
|||
|
|||
import cc.hiver.core.common.exception.HiverException; |
|||
import cc.hiver.core.common.vo.PageVo; |
|||
import cc.hiver.mall.planet.entity.*; |
|||
import cc.hiver.mall.planet.mapper.*; |
|||
import cc.hiver.mall.planet.service.PlanetAdminService; |
|||
import cc.hiver.mall.planet.service.PlanetDrawService; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.util.Date; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* 白嫖星球-后台管理服务实现 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Service |
|||
public class PlanetAdminServiceImpl implements PlanetAdminService { |
|||
|
|||
@Autowired |
|||
private PlanetPoolMapper poolMapper; |
|||
|
|||
@Autowired |
|||
private PlanetRewardMapper rewardMapper; |
|||
|
|||
@Autowired |
|||
private PlanetTaskMapper taskMapper; |
|||
|
|||
@Autowired |
|||
private PlanetBuffMapper buffMapper; |
|||
|
|||
@Autowired |
|||
private PlanetNewsMapper newsMapper; |
|||
|
|||
@Autowired |
|||
private PlanetTicketMapper ticketMapper; |
|||
|
|||
@Autowired |
|||
private PlanetHuntRecordMapper huntRecordMapper; |
|||
|
|||
@Autowired |
|||
private PlanetDrawRecordMapper drawRecordMapper; |
|||
|
|||
@Autowired |
|||
private PlanetDrawWinnerMapper drawWinnerMapper; |
|||
|
|||
@Autowired |
|||
private PlanetDrawService drawService; |
|||
|
|||
private static String genId() { |
|||
return UUID.randomUUID().toString().replace("-", ""); |
|||
} |
|||
|
|||
private <T> Page<T> page(PageVo pageVo) { |
|||
final int num = pageVo == null || pageVo.getPageNumber() <= 0 ? 1 : pageVo.getPageNumber(); |
|||
final int size = pageVo == null || pageVo.getPageSize() <= 0 ? 10 : pageVo.getPageSize(); |
|||
return new Page<>(num, size); |
|||
} |
|||
|
|||
@Override |
|||
public Page<PlanetPool> pagePools(PageVo pageVo, String regionId, Integer status) { |
|||
final LambdaQueryWrapper<PlanetPool> qw = new LambdaQueryWrapper<>(); |
|||
if (StringUtils.isNotEmpty(regionId)) { |
|||
qw.eq(PlanetPool::getRegionId, regionId); |
|||
} |
|||
if (status != null) { |
|||
qw.eq(PlanetPool::getStatus, status); |
|||
} |
|||
qw.orderByDesc(PlanetPool::getCreateTime); |
|||
return poolMapper.selectPage(page(pageVo), qw); |
|||
} |
|||
|
|||
@Override |
|||
public PlanetPool savePool(PlanetPool pool) { |
|||
if (StringUtils.isEmpty(pool.getId())) { |
|||
pool.setId(genId()); |
|||
if (pool.getStatus() == null) { |
|||
pool.setStatus(0); |
|||
} |
|||
if (pool.getJoinCount() == null) { |
|||
pool.setJoinCount(0); |
|||
} |
|||
if (pool.getTicketTotal() == null) { |
|||
pool.setTicketTotal(0); |
|||
} |
|||
if (pool.getWinnerCount() == null) { |
|||
pool.setWinnerCount(0); |
|||
} |
|||
pool.setCreateTime(new Date()); |
|||
pool.setUpdateTime(new Date()); |
|||
poolMapper.insert(pool); |
|||
} else { |
|||
pool.setUpdateTime(new Date()); |
|||
poolMapper.updateById(pool); |
|||
} |
|||
return pool; |
|||
} |
|||
|
|||
@Override |
|||
public void deletePool(String id) { |
|||
poolMapper.deleteById(id); |
|||
final LambdaQueryWrapper<PlanetReward> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetReward::getPoolId, id); |
|||
rewardMapper.delete(qw); |
|||
} |
|||
|
|||
@Override |
|||
public List<PlanetReward> rewardsOfPool(String poolId) { |
|||
final LambdaQueryWrapper<PlanetReward> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetReward::getPoolId, poolId).orderByAsc(PlanetReward::getSort); |
|||
return rewardMapper.selectList(qw); |
|||
} |
|||
|
|||
@Override |
|||
public PlanetReward saveReward(PlanetReward reward) { |
|||
if (StringUtils.isEmpty(reward.getId())) { |
|||
reward.setId(genId()); |
|||
reward.setCreateTime(new Date()); |
|||
rewardMapper.insert(reward); |
|||
} else { |
|||
rewardMapper.updateById(reward); |
|||
} |
|||
return reward; |
|||
} |
|||
|
|||
@Override |
|||
public void deleteReward(String id) { |
|||
rewardMapper.deleteById(id); |
|||
} |
|||
|
|||
@Override |
|||
public Page<PlanetTask> pageTasks(PageVo pageVo, String regionId) { |
|||
final LambdaQueryWrapper<PlanetTask> qw = new LambdaQueryWrapper<>(); |
|||
if (StringUtils.isNotEmpty(regionId)) { |
|||
qw.eq(PlanetTask::getRegionId, regionId); |
|||
} |
|||
qw.orderByAsc(PlanetTask::getSort); |
|||
return taskMapper.selectPage(page(pageVo), qw); |
|||
} |
|||
|
|||
@Override |
|||
public PlanetTask saveTask(PlanetTask task) { |
|||
if (StringUtils.isEmpty(task.getId())) { |
|||
task.setId(genId()); |
|||
if (task.getEnabled() == null) { |
|||
task.setEnabled(1); |
|||
} |
|||
task.setCreateTime(new Date()); |
|||
taskMapper.insert(task); |
|||
} else { |
|||
taskMapper.updateById(task); |
|||
} |
|||
return task; |
|||
} |
|||
|
|||
@Override |
|||
public void deleteTask(String id) { |
|||
taskMapper.deleteById(id); |
|||
} |
|||
|
|||
@Override |
|||
public Page<PlanetBuff> pageBuffs(PageVo pageVo, String regionId) { |
|||
final LambdaQueryWrapper<PlanetBuff> qw = new LambdaQueryWrapper<>(); |
|||
if (StringUtils.isNotEmpty(regionId)) { |
|||
qw.eq(PlanetBuff::getRegionId, regionId); |
|||
} |
|||
qw.orderByAsc(PlanetBuff::getSort); |
|||
return buffMapper.selectPage(page(pageVo), qw); |
|||
} |
|||
|
|||
@Override |
|||
public PlanetBuff saveBuff(PlanetBuff buff) { |
|||
if (StringUtils.isEmpty(buff.getId())) { |
|||
buff.setId(genId()); |
|||
if (buff.getEnabled() == null) { |
|||
buff.setEnabled(1); |
|||
} |
|||
buff.setCreateTime(new Date()); |
|||
buffMapper.insert(buff); |
|||
} else { |
|||
buffMapper.updateById(buff); |
|||
} |
|||
return buff; |
|||
} |
|||
|
|||
@Override |
|||
public void deleteBuff(String id) { |
|||
buffMapper.deleteById(id); |
|||
} |
|||
|
|||
@Override |
|||
public Page<PlanetNews> pageNews(PageVo pageVo, String regionId) { |
|||
final LambdaQueryWrapper<PlanetNews> qw = new LambdaQueryWrapper<>(); |
|||
if (StringUtils.isNotEmpty(regionId)) { |
|||
qw.eq(PlanetNews::getRegionId, regionId); |
|||
} |
|||
qw.orderByDesc(PlanetNews::getCreateTime); |
|||
return newsMapper.selectPage(page(pageVo), qw); |
|||
} |
|||
|
|||
@Override |
|||
public PlanetNews saveNews(PlanetNews news) { |
|||
if (StringUtils.isEmpty(news.getId())) { |
|||
news.setId(genId()); |
|||
if (news.getEnabled() == null) { |
|||
news.setEnabled(1); |
|||
} |
|||
if (news.getIsTop() == null) { |
|||
news.setIsTop(0); |
|||
} |
|||
news.setCreateTime(new Date()); |
|||
newsMapper.insert(news); |
|||
} else { |
|||
newsMapper.updateById(news); |
|||
} |
|||
return news; |
|||
} |
|||
|
|||
@Override |
|||
public void deleteNews(String id) { |
|||
newsMapper.deleteById(id); |
|||
} |
|||
|
|||
@Override |
|||
public Page<PlanetTicket> pageRank(PageVo pageVo, String regionId) { |
|||
final LambdaQueryWrapper<PlanetTicket> qw = new LambdaQueryWrapper<>(); |
|||
if (StringUtils.isNotEmpty(regionId)) { |
|||
qw.eq(PlanetTicket::getRegionId, regionId); |
|||
} |
|||
qw.orderByDesc(PlanetTicket::getTicketCount); |
|||
return ticketMapper.selectPage(page(pageVo), qw); |
|||
} |
|||
|
|||
@Override |
|||
public Page<PlanetHuntRecord> pageHunts(PageVo pageVo, String regionId) { |
|||
final LambdaQueryWrapper<PlanetHuntRecord> qw = new LambdaQueryWrapper<>(); |
|||
if (StringUtils.isNotEmpty(regionId)) { |
|||
qw.eq(PlanetHuntRecord::getRegionId, regionId); |
|||
} |
|||
qw.orderByDesc(PlanetHuntRecord::getCreateTime); |
|||
return huntRecordMapper.selectPage(page(pageVo), qw); |
|||
} |
|||
|
|||
@Override |
|||
public Page<PlanetDrawRecord> pageDraws(PageVo pageVo, String regionId) { |
|||
final LambdaQueryWrapper<PlanetDrawRecord> qw = new LambdaQueryWrapper<>(); |
|||
if (StringUtils.isNotEmpty(regionId)) { |
|||
qw.eq(PlanetDrawRecord::getRegionId, regionId); |
|||
} |
|||
qw.orderByDesc(PlanetDrawRecord::getDrawTime); |
|||
return drawRecordMapper.selectPage(page(pageVo), qw); |
|||
} |
|||
|
|||
@Override |
|||
public Page<PlanetDrawWinner> pageWinners(PageVo pageVo, String regionId, String periodNo) { |
|||
final LambdaQueryWrapper<PlanetDrawWinner> qw = new LambdaQueryWrapper<>(); |
|||
if (StringUtils.isNotEmpty(regionId)) { |
|||
qw.eq(PlanetDrawWinner::getRegionId, regionId); |
|||
} |
|||
if (StringUtils.isNotEmpty(periodNo)) { |
|||
qw.eq(PlanetDrawWinner::getPeriodNo, periodNo); |
|||
} |
|||
qw.orderByDesc(PlanetDrawWinner::getCreateTime); |
|||
return drawWinnerMapper.selectPage(page(pageVo), qw); |
|||
} |
|||
|
|||
@Override |
|||
public PlanetDrawRecord manualDraw(String poolId, String regionId) { |
|||
if (StringUtils.isEmpty(regionId)) { |
|||
throw new HiverException("缺少商圈参数,开奖仅限本区域内进行"); |
|||
} |
|||
final PlanetPool pool = poolMapper.selectById(poolId); |
|||
if (pool == null) { |
|||
throw new HiverException("奖池不存在"); |
|||
} |
|||
if (!regionId.equals(pool.getRegionId())) { |
|||
throw new HiverException("无权对其它商圈的奖池开奖"); |
|||
} |
|||
return drawService.drawPool(poolId); |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
package cc.hiver.mall.planet.service.impl; |
|||
|
|||
import cc.hiver.core.common.exception.HiverException; |
|||
import cc.hiver.mall.planet.constant.PlanetConstant; |
|||
import cc.hiver.mall.planet.entity.PlanetTicket; |
|||
import cc.hiver.mall.planet.mapper.PlanetTicketMapper; |
|||
import cc.hiver.mall.planet.pojo.PlanetBoxResultVo; |
|||
import cc.hiver.mall.planet.service.PlanetBoxService; |
|||
import cc.hiver.mall.planet.service.PlanetBuffService; |
|||
import cc.hiver.mall.planet.service.PlanetNewsService; |
|||
import cc.hiver.mall.planet.service.PlanetTicketService; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.time.LocalDate; |
|||
import java.util.Date; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ThreadLocalRandom; |
|||
|
|||
/** |
|||
* 白嫖星球-幸运宝箱服务实现 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Service |
|||
public class PlanetBoxServiceImpl implements PlanetBoxService { |
|||
|
|||
@Autowired |
|||
private PlanetTicketService ticketService; |
|||
|
|||
@Autowired |
|||
private PlanetTicketMapper ticketMapper; |
|||
|
|||
@Autowired |
|||
private PlanetBuffService buffService; |
|||
|
|||
@Autowired |
|||
private PlanetNewsService newsService; |
|||
|
|||
@Override |
|||
public PlanetBoxResultVo openDailyBox(String userId, String regionId) { |
|||
final PlanetTicket ticket = ticketService.getOrCreate(userId, regionId, null, null, null); |
|||
final LocalDate today = LocalDate.now(); |
|||
if (ticket.getBoxOpenedDate() != null && ticket.getBoxOpenedDate().isEqual(today)) { |
|||
throw new HiverException("今日宝箱已开启,明天再来"); |
|||
} |
|||
// 标记已开
|
|||
ticket.setBoxOpenedDate(today); |
|||
ticket.setUpdateTime(new Date()); |
|||
ticketMapper.updateById(ticket); |
|||
|
|||
// 抽取奖励:加权随机
|
|||
// 1券50% / 2券22% / 5券8% / 幸运BUFF6% / 防护BUFF6% / 双倍BUFF5% / 掠夺BUFF3%
|
|||
final int roll = ThreadLocalRandom.current().nextInt(100); |
|||
final PlanetBoxResultVo vo = new PlanetBoxResultVo(); |
|||
if (roll < 50) { |
|||
grantTicket(userId, regionId, 1, vo, "幸运宝箱开出 1 张星球券"); |
|||
} else if (roll < 72) { |
|||
grantTicket(userId, regionId, 2, vo, "幸运宝箱开出 2 张星球券"); |
|||
} else if (roll < 80) { |
|||
grantTicket(userId, regionId, 5, vo, "幸运宝箱开出 5 张星球券"); |
|||
} else if (roll < 86) { |
|||
grantBuff(userId, regionId, PlanetConstant.BUFF_LUCKY, "幸运星辰", vo); |
|||
} else if (roll < 92) { |
|||
grantBuff(userId, regionId, PlanetConstant.BUFF_SHIELD, "星际防护罩", vo); |
|||
} else if (roll < 97) { |
|||
grantBuff(userId, regionId, PlanetConstant.BUFF_DOUBLE, "双倍能量", vo); |
|||
} else { |
|||
grantBuff(userId, regionId, PlanetConstant.BUFF_HUNT, "追猎雷达", vo); |
|||
} |
|||
|
|||
newsService.addNews(regionId, PlanetConstant.NEWS_BOX, |
|||
safeName(ticket.getNickname()) + " 幸运开启宝箱," + vo.getRewardName(), |
|||
userId, ticket.getNickname()); |
|||
return vo; |
|||
} |
|||
|
|||
@Override |
|||
public boolean boxAvailable(String userId, String regionId) { |
|||
final PlanetTicket ticket = ticketService.getByUser(userId, regionId); |
|||
if (ticket == null || ticket.getBoxOpenedDate() == null) { |
|||
return true; |
|||
} |
|||
return !ticket.getBoxOpenedDate().isEqual(LocalDate.now()); |
|||
} |
|||
|
|||
private void grantTicket(String userId, String regionId, int count, PlanetBoxResultVo vo, String name) { |
|||
ticketService.addTickets(userId, regionId, count, PlanetConstant.TICKET_TYPE_BOX, |
|||
UUID.randomUUID().toString().replace("-", ""), "宝箱奖励"); |
|||
vo.setRewardType("ticket"); |
|||
vo.setTicketCount(count); |
|||
vo.setRewardName("获得 " + count + " 张星球券"); |
|||
vo.setMessage(name); |
|||
} |
|||
|
|||
private void grantBuff(String userId, String regionId, String type, String name, PlanetBoxResultVo vo) { |
|||
buffService.grantBuff(userId, regionId, type, "box"); |
|||
vo.setRewardType("buff"); |
|||
vo.setBuffType(type); |
|||
vo.setRewardName("获得「" + name + "」增益"); |
|||
vo.setMessage("幸运宝箱开出「" + name + "」增益"); |
|||
} |
|||
|
|||
private String safeName(String name) { |
|||
return name == null || name.isEmpty() ? "神秘同学" : name; |
|||
} |
|||
} |
|||
@ -0,0 +1,276 @@ |
|||
package cc.hiver.mall.planet.service.impl; |
|||
|
|||
import cc.hiver.core.common.exception.HiverException; |
|||
import cc.hiver.mall.planet.constant.PlanetConstant; |
|||
import cc.hiver.mall.planet.entity.PlanetBuff; |
|||
import cc.hiver.mall.planet.entity.PlanetBuffRecord; |
|||
import cc.hiver.mall.planet.mapper.PlanetBuffMapper; |
|||
import cc.hiver.mall.planet.mapper.PlanetBuffRecordMapper; |
|||
import cc.hiver.mall.planet.pojo.PlanetBuffVo; |
|||
import cc.hiver.mall.planet.service.PlanetBuffService; |
|||
import cc.hiver.mall.planet.service.PlanetTicketService; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.util.*; |
|||
|
|||
/** |
|||
* 白嫖星球-BUFF服务实现 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Service |
|||
public class PlanetBuffServiceImpl implements PlanetBuffService { |
|||
|
|||
@Autowired |
|||
private PlanetBuffMapper buffMapper; |
|||
|
|||
@Autowired |
|||
private PlanetBuffRecordMapper buffRecordMapper; |
|||
|
|||
@Autowired |
|||
private PlanetTicketService ticketService; |
|||
|
|||
@Override |
|||
public List<PlanetBuffVo> shopList(String userId, String regionId) { |
|||
final List<PlanetBuff> buffs = listEnabledBuffs(regionId); |
|||
final List<PlanetBuffRecord> actives = activeBuffs(userId); |
|||
final List<PlanetBuffVo> list = new ArrayList<>(); |
|||
for (PlanetBuff buff : buffs) { |
|||
final PlanetBuffVo vo = toVo(buff); |
|||
for (PlanetBuffRecord r : actives) { |
|||
if (r.getType() != null && r.getType().equals(buff.getType())) { |
|||
vo.setActive(true); |
|||
vo.setActiveEndTime(r.getEndTime()); |
|||
break; |
|||
} |
|||
} |
|||
list.add(vo); |
|||
} |
|||
return list; |
|||
} |
|||
|
|||
@Override |
|||
public PlanetBuffVo buyBuff(String userId, String regionId, String buffId) { |
|||
final PlanetBuff buff = buffMapper.selectById(buffId); |
|||
if (buff == null || buff.getEnabled() == null || buff.getEnabled() != 1) { |
|||
throw new HiverException("该BUFF不存在或已下架"); |
|||
} |
|||
if (hasActiveBuff(userId, buff.getType())) { |
|||
throw new HiverException("该增益正在生效中,请勿重复购买"); |
|||
} |
|||
if (buff.getCostTickets() != null && buff.getCostTickets() > 0) { |
|||
ticketService.deductTickets(userId, regionId, buff.getCostTickets(), |
|||
PlanetConstant.TICKET_TYPE_BUFF, buff.getId() + ":" + System.currentTimeMillis(), |
|||
"购买BUFF-" + buff.getName()); |
|||
} |
|||
final PlanetBuffRecord record = grantBuffInner(userId, regionId, buff, "buy"); |
|||
final PlanetBuffVo vo = toVo(buff); |
|||
vo.setActive(true); |
|||
vo.setActiveEndTime(record.getEndTime()); |
|||
return vo; |
|||
} |
|||
|
|||
@Override |
|||
public List<PlanetBuffRecord> activeBuffs(String userId) { |
|||
final LambdaQueryWrapper<PlanetBuffRecord> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetBuffRecord::getUserId, userId) |
|||
.eq(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_ACTIVE) |
|||
.gt(PlanetBuffRecord::getEndTime, new Date()) |
|||
.orderByDesc(PlanetBuffRecord::getEndTime); |
|||
return buffRecordMapper.selectList(qw); |
|||
} |
|||
|
|||
@Override |
|||
public boolean hasActiveBuff(String userId, String type) { |
|||
final LambdaQueryWrapper<PlanetBuffRecord> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetBuffRecord::getUserId, userId) |
|||
.eq(PlanetBuffRecord::getType, type) |
|||
.eq(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_ACTIVE) |
|||
.gt(PlanetBuffRecord::getEndTime, new Date()); |
|||
return buffRecordMapper.selectCount(qw) > 0; |
|||
} |
|||
|
|||
@Override |
|||
public PlanetBuffRecord grantBuff(String userId, String regionId, String type, String source) { |
|||
final PlanetBuff buff = findBuffByType(regionId, type); |
|||
if (buff == null) { |
|||
final PlanetBuff temp = new PlanetBuff(); |
|||
temp.setType(type); |
|||
temp.setDurationHours(PlanetConstant.AUTO_SHIELD_HOURS); |
|||
temp.setEffectValue(BigDecimal.ZERO); |
|||
return grantBuffInner(userId, regionId, temp, source); |
|||
} |
|||
return grantBuffInner(userId, regionId, buff, source); |
|||
} |
|||
|
|||
@Override |
|||
public double luckyWeightFactor(String userId) { |
|||
final LambdaQueryWrapper<PlanetBuffRecord> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetBuffRecord::getUserId, userId) |
|||
.eq(PlanetBuffRecord::getType, PlanetConstant.BUFF_LUCKY) |
|||
.eq(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_ACTIVE) |
|||
.gt(PlanetBuffRecord::getEndTime, new Date()) |
|||
.orderByDesc(PlanetBuffRecord::getEffectValue) |
|||
.last("limit 1"); |
|||
final List<PlanetBuffRecord> list = buffRecordMapper.selectList(qw); |
|||
if (list != null && !list.isEmpty() && list.get(0).getEffectValue() != null) { |
|||
return 1.0 + list.get(0).getEffectValue().doubleValue(); |
|||
} |
|||
return 1.0; |
|||
} |
|||
|
|||
@Override |
|||
public Map<String, Set<String>> activeBuffTypes(Collection<String> userIds) { |
|||
final Map<String, Set<String>> map = new HashMap<>(); |
|||
if (userIds == null || userIds.isEmpty()) { |
|||
return map; |
|||
} |
|||
final LambdaQueryWrapper<PlanetBuffRecord> qw = new LambdaQueryWrapper<>(); |
|||
qw.in(PlanetBuffRecord::getUserId, userIds) |
|||
.eq(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_ACTIVE) |
|||
.gt(PlanetBuffRecord::getEndTime, new Date()) |
|||
.select(PlanetBuffRecord::getUserId, PlanetBuffRecord::getType); |
|||
final List<PlanetBuffRecord> records = buffRecordMapper.selectList(qw); |
|||
for (PlanetBuffRecord r : records) { |
|||
map.computeIfAbsent(r.getUserId(), k -> new HashSet<>()).add(r.getType()); |
|||
} |
|||
return map; |
|||
} |
|||
|
|||
@Override |
|||
public Map<String, Double> luckyWeightFactors(Collection<String> userIds) { |
|||
final Map<String, Double> map = new HashMap<>(); |
|||
if (userIds == null || userIds.isEmpty()) { |
|||
return map; |
|||
} |
|||
final LambdaQueryWrapper<PlanetBuffRecord> qw = new LambdaQueryWrapper<>(); |
|||
qw.in(PlanetBuffRecord::getUserId, userIds) |
|||
.eq(PlanetBuffRecord::getType, PlanetConstant.BUFF_LUCKY) |
|||
.eq(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_ACTIVE) |
|||
.gt(PlanetBuffRecord::getEndTime, new Date()) |
|||
.select(PlanetBuffRecord::getUserId, PlanetBuffRecord::getEffectValue); |
|||
final List<PlanetBuffRecord> records = buffRecordMapper.selectList(qw); |
|||
for (PlanetBuffRecord r : records) { |
|||
if (r.getEffectValue() == null) { |
|||
continue; |
|||
} |
|||
final double factor = 1.0 + r.getEffectValue().doubleValue(); |
|||
// 同类型可能多条,取最大加成
|
|||
map.merge(r.getUserId(), factor, Math::max); |
|||
} |
|||
return map; |
|||
} |
|||
|
|||
private PlanetBuffVo toVo(PlanetBuff buff) { |
|||
final PlanetBuffVo vo = new PlanetBuffVo(); |
|||
vo.setId(buff.getId()); |
|||
vo.setType(buff.getType()); |
|||
vo.setName(buff.getName()); |
|||
vo.setDescription(buff.getDescription()); |
|||
vo.setIcon(buff.getIcon()); |
|||
vo.setCostTickets(buff.getCostTickets()); |
|||
vo.setDurationHours(buff.getDurationHours()); |
|||
vo.setEffectValue(buff.getEffectValue()); |
|||
vo.setActive(false); |
|||
return vo; |
|||
} |
|||
|
|||
private PlanetBuffRecord grantBuffInner(String userId, String regionId, PlanetBuff buff, String source) { |
|||
final PlanetBuffRecord record = new PlanetBuffRecord(); |
|||
record.setId(UUID.randomUUID().toString().replace("-", "")); |
|||
record.setUserId(userId); |
|||
record.setRegionId(regionId); |
|||
record.setBuffId(buff.getId()); |
|||
record.setType(buff.getType()); |
|||
record.setEffectValue(buff.getEffectValue() == null ? BigDecimal.ZERO : buff.getEffectValue()); |
|||
final Date now = new Date(); |
|||
record.setStartTime(now); |
|||
final int hours = buff.getDurationHours() == null ? PlanetConstant.AUTO_SHIELD_HOURS : buff.getDurationHours(); |
|||
record.setEndTime(new Date(now.getTime() + (long) hours * 3600 * 1000)); |
|||
record.setStatus(PlanetConstant.BUFF_STATUS_ACTIVE); |
|||
record.setSource(source); |
|||
record.setCreateTime(now); |
|||
buffRecordMapper.insert(record); |
|||
return record; |
|||
} |
|||
|
|||
private PlanetBuff findBuffByType(String regionId, String type) { |
|||
// 商圈强隔离:仅查本商圈BUFF配置,不再回退全局默认
|
|||
if (StringUtils.isEmpty(regionId)) { |
|||
return null; |
|||
} |
|||
PlanetBuff buff = selectRegionBuff(regionId, type); |
|||
if (buff == null) { |
|||
seedDefaultBuffs(regionId); |
|||
buff = selectRegionBuff(regionId, type); |
|||
} |
|||
return buff; |
|||
} |
|||
|
|||
private PlanetBuff selectRegionBuff(String regionId, String type) { |
|||
final LambdaQueryWrapper<PlanetBuff> rq = new LambdaQueryWrapper<>(); |
|||
rq.eq(PlanetBuff::getType, type).eq(PlanetBuff::getEnabled, 1) |
|||
.eq(PlanetBuff::getRegionId, regionId).last("limit 1"); |
|||
return buffMapper.selectOne(rq); |
|||
} |
|||
|
|||
private List<PlanetBuff> listEnabledBuffs(String regionId) { |
|||
// 商圈强隔离:仅返回本商圈BUFF,首次访问自动初始化默认BUFF
|
|||
if (StringUtils.isEmpty(regionId)) { |
|||
return new ArrayList<>(); |
|||
} |
|||
List<PlanetBuff> list = selectRegionBuffs(regionId); |
|||
if (list.isEmpty()) { |
|||
seedDefaultBuffs(regionId); |
|||
list = selectRegionBuffs(regionId); |
|||
} |
|||
return list; |
|||
} |
|||
|
|||
private List<PlanetBuff> selectRegionBuffs(String regionId) { |
|||
final LambdaQueryWrapper<PlanetBuff> rq = new LambdaQueryWrapper<>(); |
|||
rq.eq(PlanetBuff::getRegionId, regionId).eq(PlanetBuff::getEnabled, 1) |
|||
.orderByAsc(PlanetBuff::getSort); |
|||
final List<PlanetBuff> list = buffMapper.selectList(rq); |
|||
return list == null ? new ArrayList<>() : list; |
|||
} |
|||
|
|||
/** |
|||
* 为指定商圈初始化默认BUFF配置(幂等:按 type 存在则跳过)。 |
|||
*/ |
|||
private void seedDefaultBuffs(String regionId) { |
|||
if (StringUtils.isEmpty(regionId)) { |
|||
return; |
|||
} |
|||
seedBuff(regionId, PlanetConstant.BUFF_DOUBLE, "双倍能量", "24小时内获得星球券翻倍", 5, 24, new BigDecimal("2.00"), 1); |
|||
seedBuff(regionId, PlanetConstant.BUFF_SHIELD, "星际防护罩", "24小时内免疫追捕(养只小松鼠警卫)", 8, 24, BigDecimal.ZERO, 2); |
|||
seedBuff(regionId, PlanetConstant.BUFF_LUCKY, "幸运星辰", "开奖权重 +20%", 6, 24, new BigDecimal("0.20"), 3); |
|||
seedBuff(regionId, PlanetConstant.BUFF_HUNT, "追猎雷达", "追捕成功率提升", 4, 24, new BigDecimal("0.30"), 4); |
|||
seedBuff(regionId, PlanetConstant.BUFF_STEALTH, "隐身斗篷", "不出现在通缉榜", 6, 24, BigDecimal.ZERO, 5); |
|||
} |
|||
|
|||
private void seedBuff(String regionId, String type, String name, String desc, int cost, int hours, BigDecimal effect, int sort) { |
|||
final LambdaQueryWrapper<PlanetBuff> exist = new LambdaQueryWrapper<>(); |
|||
exist.eq(PlanetBuff::getRegionId, regionId).eq(PlanetBuff::getType, type); |
|||
if (buffMapper.selectCount(exist) > 0) { |
|||
return; |
|||
} |
|||
final PlanetBuff buff = new PlanetBuff(); |
|||
buff.setId(UUID.randomUUID().toString().replace("-", "")); |
|||
buff.setRegionId(regionId); |
|||
buff.setType(type); |
|||
buff.setName(name); |
|||
buff.setDescription(desc); |
|||
buff.setCostTickets(cost); |
|||
buff.setDurationHours(hours); |
|||
buff.setEffectValue(effect); |
|||
buff.setSort(sort); |
|||
buff.setEnabled(1); |
|||
buff.setCreateTime(new Date()); |
|||
buffMapper.insert(buff); |
|||
} |
|||
} |
|||
@ -0,0 +1,393 @@ |
|||
package cc.hiver.mall.planet.service.impl; |
|||
|
|||
import cc.hiver.core.common.exception.HiverException; |
|||
import cc.hiver.mall.planet.constant.PlanetConstant; |
|||
import cc.hiver.mall.planet.entity.*; |
|||
import cc.hiver.mall.planet.mapper.*; |
|||
import cc.hiver.mall.planet.service.PlanetBuffService; |
|||
import cc.hiver.mall.planet.service.PlanetDrawService; |
|||
import cc.hiver.mall.planet.service.PlanetNewsService; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.text.SimpleDateFormat; |
|||
import java.util.*; |
|||
import java.util.concurrent.ThreadLocalRandom; |
|||
import java.util.stream.Collectors; |
|||
|
|||
/** |
|||
* 白嫖星球-开奖服务实现 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Slf4j |
|||
@Service |
|||
public class PlanetDrawServiceImpl implements PlanetDrawService { |
|||
|
|||
/** 每日开奖时刻(24小时制),到点由定时任务自动开奖,每日一抽 */ |
|||
private static final int DAILY_DRAW_HOUR = 22; |
|||
|
|||
@Autowired |
|||
private PlanetPoolMapper poolMapper; |
|||
|
|||
@Autowired |
|||
private PlanetRewardMapper rewardMapper; |
|||
|
|||
@Autowired |
|||
private PlanetTicketMapper ticketMapper; |
|||
|
|||
@Autowired |
|||
private PlanetDrawRecordMapper drawRecordMapper; |
|||
|
|||
@Autowired |
|||
private PlanetDrawWinnerMapper drawWinnerMapper; |
|||
|
|||
@Autowired |
|||
private PlanetBuffService buffService; |
|||
|
|||
@Autowired |
|||
private PlanetNewsService newsService; |
|||
|
|||
/** |
|||
* 抽奖功能强制按商圈隔离:缺少 regionId 直接拒绝,杜绝跨区域开奖/查询。 |
|||
*/ |
|||
private void requireRegion(String regionId) { |
|||
if (StringUtils.isEmpty(regionId)) { |
|||
throw new HiverException("缺少商圈参数,抽奖仅限本区域内进行"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public PlanetPool getCurrentPool(String regionId) { |
|||
requireRegion(regionId); |
|||
final LambdaQueryWrapper<PlanetPool> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetPool::getRegionId, regionId) |
|||
.eq(PlanetPool::getStatus, PlanetConstant.POOL_STATUS_RUNNING) |
|||
.orderByDesc(PlanetPool::getCreateTime) |
|||
.last("limit 1"); |
|||
PlanetPool pool = poolMapper.selectOne(qw); |
|||
if (pool == null) { |
|||
pool = createDefaultPool(regionId); |
|||
} |
|||
return pool; |
|||
} |
|||
|
|||
private PlanetPool createDefaultPool(String regionId) { |
|||
final Date now = new Date(); |
|||
final PlanetPool pool = new PlanetPool(); |
|||
pool.setId(UUID.randomUUID().toString().replace("-", "")); |
|||
pool.setRegionId(regionId); |
|||
pool.setPeriodNo(genPeriodNo()); |
|||
pool.setTitle("白嫖星球·本期瓜分"); |
|||
pool.setPoolAmount(new BigDecimal("88.88")); |
|||
pool.setDrawTime(nextDrawTime(now)); |
|||
pool.setJoinCount(0); |
|||
pool.setTicketTotal(0); |
|||
pool.setWinnerCount(0); |
|||
pool.setStatus(PlanetConstant.POOL_STATUS_RUNNING); |
|||
pool.setRemark("每日按星球券加权瓜分奖金池"); |
|||
pool.setCreateTime(now); |
|||
pool.setUpdateTime(now); |
|||
poolMapper.insert(pool); |
|||
// 默认奖项
|
|||
createDefaultRewards(pool); |
|||
return pool; |
|||
} |
|||
|
|||
private void createDefaultRewards(PlanetPool pool) { |
|||
addReward(pool, 1, "一等奖", new BigDecimal("28.00"), 1, 1); |
|||
addReward(pool, 2, "二等奖", new BigDecimal("8.80"), 2, 2); |
|||
addReward(pool, 3, "三等奖", new BigDecimal("1.00"), 10, 3); |
|||
addReward(pool, 4, "幸运奖", new BigDecimal("0.50"), 50, 4); |
|||
} |
|||
|
|||
private void addReward(PlanetPool pool, int level, String name, BigDecimal amount, int quota, int sort) { |
|||
final PlanetReward reward = new PlanetReward(); |
|||
reward.setId(UUID.randomUUID().toString().replace("-", "")); |
|||
reward.setPoolId(pool.getId()); |
|||
reward.setRegionId(pool.getRegionId()); |
|||
reward.setLevel(level); |
|||
reward.setLevelName(name); |
|||
reward.setRewardType(0); |
|||
reward.setAmount(amount); |
|||
reward.setQuota(quota); |
|||
reward.setSort(sort); |
|||
reward.setCreateTime(new Date()); |
|||
rewardMapper.insert(reward); |
|||
} |
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public PlanetDrawRecord drawPool(String poolId) { |
|||
final PlanetPool pool = poolMapper.selectById(poolId); |
|||
if (pool == null) { |
|||
throw new HiverException("奖池不存在"); |
|||
} |
|||
if (pool.getStatus() != null && pool.getStatus() == PlanetConstant.POOL_STATUS_DRAWN) { |
|||
throw new HiverException("该期已开奖"); |
|||
} |
|||
// 奖池必须归属某商圈,开奖只在本商圈内进行
|
|||
requireRegion(pool.getRegionId()); |
|||
// 参与者:仅本商圈内持券用户
|
|||
final LambdaQueryWrapper<PlanetTicket> tq = new LambdaQueryWrapper<>(); |
|||
tq.eq(PlanetTicket::getRegionId, pool.getRegionId()) |
|||
.gt(PlanetTicket::getTicketCount, 0); |
|||
final List<PlanetTicket> participants = ticketMapper.selectList(tq); |
|||
|
|||
// 奖项按层级排序
|
|||
final LambdaQueryWrapper<PlanetReward> rq = new LambdaQueryWrapper<>(); |
|||
rq.eq(PlanetReward::getPoolId, poolId).orderByAsc(PlanetReward::getSort); |
|||
final List<PlanetReward> rewards = rewardMapper.selectList(rq); |
|||
|
|||
// 批量查询所有参与者的幸运系数,一次查库,避免循环逐个查询
|
|||
final Set<String> userIds = participants.stream().map(PlanetTicket::getUserId).collect(Collectors.toCollection(LinkedHashSet::new)); |
|||
final Map<String, Double> luckyMap = buffService.luckyWeightFactors(userIds); |
|||
|
|||
// 构建加权候选(权重= 券数量 * 幸运系数)
|
|||
final List<Candidate> poutPool = new ArrayList<>(); |
|||
int ticketTotal = 0; |
|||
for (PlanetTicket t : participants) { |
|||
final double factor = luckyMap.getOrDefault(t.getUserId(), 1.0); |
|||
final double weight = t.getTicketCount() * factor; |
|||
poutPool.add(new Candidate(t, weight)); |
|||
ticketTotal += t.getTicketCount(); |
|||
} |
|||
|
|||
final Date now = new Date(); |
|||
// 开奖记录
|
|||
final PlanetDrawRecord record = new PlanetDrawRecord(); |
|||
record.setId(UUID.randomUUID().toString().replace("-", "")); |
|||
record.setPoolId(pool.getId()); |
|||
record.setRegionId(pool.getRegionId()); |
|||
record.setPeriodNo(pool.getPeriodNo()); |
|||
record.setPoolAmount(pool.getPoolAmount()); |
|||
record.setJoinCount(participants.size()); |
|||
record.setTicketTotal(ticketTotal); |
|||
record.setStatus(1); |
|||
record.setDrawTime(now); |
|||
record.setCreateTime(now); |
|||
|
|||
int winnerCount = 0; |
|||
for (PlanetReward reward : rewards) { |
|||
final int quota = reward.getQuota() == null ? 0 : reward.getQuota(); |
|||
for (int i = 0; i < quota; i++) { |
|||
if (poutPoolEmpty(poutPool)) { |
|||
break; |
|||
} |
|||
final Candidate winner = pickWeighted(poutPoolUsable(poutPool)); |
|||
if (winner == null) { |
|||
break; |
|||
} |
|||
winner.win = true; |
|||
saveWinner(record, reward, winner.ticket, now); |
|||
winnerCount++; |
|||
} |
|||
} |
|||
record.setWinnerCount(winnerCount); |
|||
drawRecordMapper.insert(record); |
|||
|
|||
// 更新奖池为已开奖
|
|||
pool.setStatus(PlanetConstant.POOL_STATUS_DRAWN); |
|||
pool.setJoinCount(participants.size()); |
|||
pool.setTicketTotal(ticketTotal); |
|||
pool.setWinnerCount(winnerCount); |
|||
pool.setUpdateTime(now); |
|||
poolMapper.updateById(pool); |
|||
|
|||
// 快讯
|
|||
newsService.addNews(pool.getRegionId(), PlanetConstant.NEWS_DRAW, |
|||
"本期奖池 ¥" + pool.getPoolAmount() + " 已开奖," + winnerCount + " 位同学瓜分成功", |
|||
null, null); |
|||
|
|||
// 开启下一期
|
|||
createNextPool(pool); |
|||
log.info("[白嫖星球] 开奖完成 poolId={}, 参与={}, 中奖={}", poolId, participants.size(), winnerCount); |
|||
return record; |
|||
} |
|||
|
|||
private void createNextPool(PlanetPool prev) { |
|||
final Date now = new Date(); |
|||
final PlanetPool pool = new PlanetPool(); |
|||
pool.setId(UUID.randomUUID().toString().replace("-", "")); |
|||
pool.setRegionId(prev.getRegionId()); |
|||
pool.setPeriodNo(genPeriodNo()); |
|||
pool.setTitle(prev.getTitle()); |
|||
pool.setPoolAmount(prev.getPoolAmount()); |
|||
pool.setDrawTime(nextDrawTime(now)); |
|||
pool.setJoinCount(0); |
|||
pool.setTicketTotal(0); |
|||
pool.setWinnerCount(0); |
|||
pool.setStatus(PlanetConstant.POOL_STATUS_RUNNING); |
|||
pool.setRemark(prev.getRemark()); |
|||
pool.setCreateTime(now); |
|||
pool.setUpdateTime(now); |
|||
poolMapper.insert(pool); |
|||
// 复制上期奖项配置
|
|||
final LambdaQueryWrapper<PlanetReward> rq = new LambdaQueryWrapper<>(); |
|||
rq.eq(PlanetReward::getPoolId, prev.getId()).orderByAsc(PlanetReward::getSort); |
|||
for (PlanetReward r : rewardMapper.selectList(rq)) { |
|||
addReward(pool, r.getLevel(), r.getLevelName(), r.getAmount(), r.getQuota() == null ? 0 : r.getQuota(), r.getSort() == null ? 0 : r.getSort()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public int drawDuePools() { |
|||
// 扫描所有到点未开奖的奖池:每个奖池都归属唯一校区,逐个独立开奖,
|
|||
// 各校区参与者/奖项/奖金互不影响,实现“按校区分别开奖”。
|
|||
final LambdaQueryWrapper<PlanetPool> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetPool::getStatus, PlanetConstant.POOL_STATUS_RUNNING) |
|||
.le(PlanetPool::getDrawTime, new Date()); |
|||
final List<PlanetPool> due = poolMapper.selectList(qw); |
|||
int count = 0; |
|||
for (PlanetPool pool : due) { |
|||
// 防御:无校区归属的奖池不参与开奖,避免跨校区
|
|||
if (StringUtils.isEmpty(pool.getRegionId())) { |
|||
log.warn("[白嫖星球] 跳过无校区奖池 poolId={}, periodNo={}", pool.getId(), pool.getPeriodNo()); |
|||
continue; |
|||
} |
|||
try { |
|||
drawPool(pool.getId()); |
|||
count++; |
|||
log.info("[白嫖星球] 校区开奖完成 regionId={}, periodNo={}", pool.getRegionId(), pool.getPeriodNo()); |
|||
} catch (Exception e) { |
|||
log.error("[白嫖星球] 校区开奖失败 regionId={}, poolId={}, {}", pool.getRegionId(), pool.getId(), e.getMessage(), e); |
|||
} |
|||
} |
|||
return count; |
|||
} |
|||
|
|||
@Override |
|||
public PlanetDrawRecord latestDrawRecord(String regionId) { |
|||
requireRegion(regionId); |
|||
final LambdaQueryWrapper<PlanetDrawRecord> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetDrawRecord::getRegionId, regionId) |
|||
.orderByDesc(PlanetDrawRecord::getDrawTime).last("limit 1"); |
|||
return drawRecordMapper.selectOne(qw); |
|||
} |
|||
|
|||
@Override |
|||
public List<PlanetDrawWinner> winnersOfDraw(String drawId) { |
|||
final LambdaQueryWrapper<PlanetDrawWinner> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetDrawWinner::getDrawId, drawId).orderByAsc(PlanetDrawWinner::getRewardLevel); |
|||
return drawWinnerMapper.selectList(qw); |
|||
} |
|||
|
|||
@Override |
|||
public List<PlanetDrawWinner> myWinning(String userId, String regionId) { |
|||
requireRegion(regionId); |
|||
final LambdaQueryWrapper<PlanetDrawWinner> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetDrawWinner::getUserId, userId) |
|||
.eq(PlanetDrawWinner::getRegionId, regionId) |
|||
.orderByDesc(PlanetDrawWinner::getCreateTime).last("limit 50"); |
|||
return drawWinnerMapper.selectList(qw); |
|||
} |
|||
|
|||
@Override |
|||
public void receive(String userId, String regionId, String winnerId) { |
|||
requireRegion(regionId); |
|||
final PlanetDrawWinner winner = drawWinnerMapper.selectById(winnerId); |
|||
if (winner == null || !userId.equals(winner.getUserId()) || !regionId.equals(winner.getRegionId())) { |
|||
throw new HiverException("中奖记录不存在"); |
|||
} |
|||
if (winner.getIsReceived() != null && winner.getIsReceived() == 1) { |
|||
throw new HiverException("已领取,请勿重复操作"); |
|||
} |
|||
winner.setIsReceived(1); |
|||
winner.setReceivedTime(new Date()); |
|||
drawWinnerMapper.updateById(winner); |
|||
// TODO: 现金奖励可在此对接钱包入账;优惠券奖励可对接发券逻辑
|
|||
} |
|||
|
|||
private void saveWinner(PlanetDrawRecord record, PlanetReward reward, PlanetTicket ticket, Date now) { |
|||
final PlanetDrawWinner winner = new PlanetDrawWinner(); |
|||
winner.setId(UUID.randomUUID().toString().replace("-", "")); |
|||
winner.setDrawId(record.getId()); |
|||
winner.setPoolId(record.getPoolId()); |
|||
winner.setRegionId(record.getRegionId()); |
|||
winner.setPeriodNo(record.getPeriodNo()); |
|||
winner.setUserId(ticket.getUserId()); |
|||
winner.setUserName(ticket.getNickname()); |
|||
winner.setRewardLevel(reward.getLevel()); |
|||
winner.setLevelName(reward.getLevelName()); |
|||
winner.setRewardType(reward.getRewardType()); |
|||
winner.setAmount(reward.getAmount()); |
|||
winner.setCouponId(reward.getCouponId()); |
|||
winner.setIsReceived(0); |
|||
winner.setCreateTime(now); |
|||
drawWinnerMapper.insert(winner); |
|||
} |
|||
|
|||
private boolean poutPoolEmpty(List<Candidate> pool) { |
|||
for (Candidate c : pool) { |
|||
if (!c.win) { |
|||
return false; |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
private List<Candidate> poutPoolUsable(List<Candidate> pool) { |
|||
final List<Candidate> usable = new ArrayList<>(); |
|||
for (Candidate c : pool) { |
|||
if (!c.win) { |
|||
usable.add(c); |
|||
} |
|||
} |
|||
return usable; |
|||
} |
|||
|
|||
private Candidate pickWeighted(List<Candidate> usable) { |
|||
if (usable.isEmpty()) { |
|||
return null; |
|||
} |
|||
double total = 0; |
|||
for (Candidate c : usable) { |
|||
total += Math.max(c.weight, 0.0001); |
|||
} |
|||
double r = ThreadLocalRandom.current().nextDouble() * total; |
|||
for (Candidate c : usable) { |
|||
r -= Math.max(c.weight, 0.0001); |
|||
if (r <= 0) { |
|||
return c; |
|||
} |
|||
} |
|||
return usable.get(usable.size() - 1); |
|||
} |
|||
|
|||
private String genPeriodNo() { |
|||
return "P" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); |
|||
} |
|||
|
|||
/** |
|||
* 计算下一次开奖时间:每日固定 {@link #DAILY_DRAW_HOUR} 点开奖,已过则顺延到次日,实现每日一抽。 |
|||
*/ |
|||
private Date nextDrawTime(Date from) { |
|||
final Calendar c = Calendar.getInstance(); |
|||
c.setTime(from); |
|||
c.set(Calendar.HOUR_OF_DAY, DAILY_DRAW_HOUR); |
|||
c.set(Calendar.MINUTE, 0); |
|||
c.set(Calendar.SECOND, 0); |
|||
c.set(Calendar.MILLISECOND, 0); |
|||
if (!c.getTime().after(from)) { |
|||
c.add(Calendar.DAY_OF_MONTH, 1); |
|||
} |
|||
return c.getTime(); |
|||
} |
|||
|
|||
/** 开奖候选 */ |
|||
private static class Candidate { |
|||
private final PlanetTicket ticket; |
|||
private final double weight; |
|||
private boolean win; |
|||
|
|||
Candidate(PlanetTicket ticket, double weight) { |
|||
this.ticket = ticket; |
|||
this.weight = weight; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,198 @@ |
|||
package cc.hiver.mall.planet.service.impl; |
|||
|
|||
import cc.hiver.core.common.exception.HiverException; |
|||
import cc.hiver.mall.planet.constant.PlanetConstant; |
|||
import cc.hiver.mall.planet.entity.PlanetBuffRecord; |
|||
import cc.hiver.mall.planet.entity.PlanetHuntRecord; |
|||
import cc.hiver.mall.planet.entity.PlanetTicket; |
|||
import cc.hiver.mall.planet.mapper.PlanetHuntRecordMapper; |
|||
import cc.hiver.mall.planet.pojo.PlanetHuntResultVo; |
|||
import cc.hiver.mall.planet.service.*; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.time.LocalDate; |
|||
import java.util.Date; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ThreadLocalRandom; |
|||
|
|||
/** |
|||
* 白嫖星球-追捕服务实现 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Slf4j |
|||
@Service |
|||
public class PlanetHuntServiceImpl implements PlanetHuntService { |
|||
|
|||
/** 基础追捕成功率 */ |
|||
private static final double BASE_SUCCESS_RATE = 0.8; |
|||
|
|||
@Autowired |
|||
private PlanetHuntRecordMapper huntRecordMapper; |
|||
|
|||
@Autowired |
|||
private PlanetTicketService ticketService; |
|||
|
|||
@Autowired |
|||
private PlanetRankService rankService; |
|||
|
|||
@Autowired |
|||
private PlanetBuffService buffService; |
|||
|
|||
@Autowired |
|||
private PlanetNewsService newsService; |
|||
|
|||
@Override |
|||
public PlanetHuntResultVo hunt(String fromUserId, String regionId, String toUserId) { |
|||
if (StringUtils.isEmpty(fromUserId) || StringUtils.isEmpty(toUserId)) { |
|||
throw new HiverException("参数不完整"); |
|||
} |
|||
if (fromUserId.equals(toUserId)) { |
|||
throw new HiverException("不能追捕自己"); |
|||
} |
|||
final LocalDate today = LocalDate.now(); |
|||
// 今日追捕次数校验
|
|||
final int huntedToday = countTodayHunts(fromUserId, today); |
|||
if (huntedToday >= PlanetConstant.HUNT_DAILY_LIMIT) { |
|||
throw new HiverException("今日追捕次数已用完,明天再来"); |
|||
} |
|||
final PlanetTicket from = ticketService.getOrCreate(fromUserId, regionId, null, null, null); |
|||
final PlanetTicket target = rankService.getRankTarget(regionId, toUserId); |
|||
if (target == null) { |
|||
throw new HiverException("目标不存在"); |
|||
} |
|||
// 仅允许同区域互动
|
|||
if (StringUtils.isNotEmpty(from.getRegionId()) && StringUtils.isNotEmpty(target.getRegionId()) |
|||
&& !from.getRegionId().equals(target.getRegionId())) { |
|||
throw new HiverException("只能追捕同校区的同学"); |
|||
} |
|||
|
|||
final PlanetHuntResultVo vo = new PlanetHuntResultVo(); |
|||
vo.setTargetName(target.getNickname()); |
|||
vo.setGainTickets(0); |
|||
vo.setBountyTickets(0); |
|||
vo.setTotalGain(0); |
|||
|
|||
// 目标防护罩:防护BUFF 或 今日已被追捕成功达上限
|
|||
final int targetHuntedSuccess = countTodayHuntedSuccess(toUserId, today); |
|||
final boolean shielded = buffService.hasActiveBuff(toUserId, PlanetConstant.BUFF_SHIELD) |
|||
|| targetHuntedSuccess >= PlanetConstant.HUNTED_SUCCESS_LIMIT; |
|||
|
|||
String result; |
|||
if (shielded) { |
|||
result = PlanetConstant.HUNT_SHIELD; |
|||
vo.setMessage("目标启动了星际防护罩,本次追捕未能缴获"); |
|||
} else if (target.getTicketCount() == null || target.getTicketCount() <= 0) { |
|||
result = PlanetConstant.HUNT_FAIL; |
|||
vo.setMessage("目标星球空空如也,未能缴获"); |
|||
} else { |
|||
// 成功率:基础 + 追捕BUFF加成
|
|||
double rate = BASE_SUCCESS_RATE; |
|||
final PlanetBuffRecord huntBuff = firstActiveBuff(fromUserId, PlanetConstant.BUFF_HUNT); |
|||
if (huntBuff != null && huntBuff.getEffectValue() != null) { |
|||
rate += huntBuff.getEffectValue().doubleValue(); |
|||
} |
|||
if (rate > 1) { |
|||
rate = 1; |
|||
} |
|||
final boolean success = ThreadLocalRandom.current().nextDouble() < rate; |
|||
if (success) { |
|||
result = PlanetConstant.HUNT_SUCCESS; |
|||
final int gain = 1; |
|||
// 计算目标当前排名的悬赏奖励
|
|||
final int targetRank = rankService.userRankNo(regionId, toUserId); |
|||
final int bounty = PlanetConstant.bountyByRank(targetRank); |
|||
// 从目标缴获
|
|||
ticketService.deductTickets(toUserId, target.getRegionId(), gain, |
|||
PlanetConstant.TICKET_TYPE_HUNT, UUID.randomUUID().toString().replace("-", ""), |
|||
"被" + safeName(from.getNickname()) + "追捕缴获"); |
|||
// 发起人获得缴获券 + 悬赏券
|
|||
ticketService.addTickets(fromUserId, regionId, gain, |
|||
PlanetConstant.TICKET_TYPE_HUNT, UUID.randomUUID().toString().replace("-", ""), |
|||
"追捕缴获" + safeName(target.getNickname())); |
|||
if (bounty > 0) { |
|||
ticketService.addTickets(fromUserId, regionId, bounty, |
|||
PlanetConstant.TICKET_TYPE_HUNT, UUID.randomUUID().toString().replace("-", ""), |
|||
"通缉悬赏奖励"); |
|||
} |
|||
vo.setGainTickets(gain); |
|||
vo.setBountyTickets(bounty); |
|||
vo.setTotalGain(gain + bounty); |
|||
vo.setMessage("追捕成功!缴获 " + gain + " 张星球券" + (bounty > 0 ? ",额外获得 " + bounty + " 张悬赏券" : "")); |
|||
|
|||
// 目标被追成功达上限自动开防护罩
|
|||
if (targetHuntedSuccess + 1 >= PlanetConstant.HUNTED_SUCCESS_LIMIT) { |
|||
buffService.grantBuff(toUserId, target.getRegionId(), PlanetConstant.BUFF_SHIELD, "hunt"); |
|||
} |
|||
// 快讯
|
|||
newsService.addNews(regionId, PlanetConstant.NEWS_HUNT, |
|||
safeName(target.getNickname()) + " 遭遇追捕,损失 " + (gain) + " 张星球券", |
|||
toUserId, target.getNickname()); |
|||
} else { |
|||
result = PlanetConstant.HUNT_FAIL; |
|||
vo.setMessage("目标飞船灵活机动,本次追捕扑空"); |
|||
} |
|||
} |
|||
vo.setResult(result); |
|||
|
|||
// 写追捕记录
|
|||
saveHuntRecord(from, target, regionId, result, vo.getGainTickets(), vo.getBountyTickets(), today); |
|||
vo.setRemainHunt(Math.max(0, PlanetConstant.HUNT_DAILY_LIMIT - (huntedToday + 1))); |
|||
return vo; |
|||
} |
|||
|
|||
@Override |
|||
public int remainHunt(String userId) { |
|||
final int used = countTodayHunts(userId, LocalDate.now()); |
|||
return Math.max(0, PlanetConstant.HUNT_DAILY_LIMIT - used); |
|||
} |
|||
|
|||
private int countTodayHunts(String userId, LocalDate date) { |
|||
final LambdaQueryWrapper<PlanetHuntRecord> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetHuntRecord::getFromUserId, userId) |
|||
.eq(PlanetHuntRecord::getHuntDate, date); |
|||
return huntRecordMapper.selectCount(qw).intValue(); |
|||
} |
|||
|
|||
private int countTodayHuntedSuccess(String userId, LocalDate date) { |
|||
final LambdaQueryWrapper<PlanetHuntRecord> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetHuntRecord::getToUserId, userId) |
|||
.eq(PlanetHuntRecord::getResult, PlanetConstant.HUNT_SUCCESS) |
|||
.eq(PlanetHuntRecord::getHuntDate, date); |
|||
return huntRecordMapper.selectCount(qw).intValue(); |
|||
} |
|||
|
|||
private PlanetBuffRecord firstActiveBuff(String userId, String type) { |
|||
for (PlanetBuffRecord r : buffService.activeBuffs(userId)) { |
|||
if (type.equals(r.getType())) { |
|||
return r; |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
private void saveHuntRecord(PlanetTicket from, PlanetTicket target, String regionId, String result, |
|||
int gain, int bounty, LocalDate date) { |
|||
final PlanetHuntRecord record = new PlanetHuntRecord(); |
|||
record.setId(UUID.randomUUID().toString().replace("-", "")); |
|||
record.setFromUserId(from.getUserId()); |
|||
record.setFromUserName(from.getNickname()); |
|||
record.setToUserId(target.getUserId()); |
|||
record.setToUserName(target.getNickname()); |
|||
record.setRegionId(regionId); |
|||
record.setResult(result); |
|||
record.setGainTickets(gain); |
|||
record.setBountyTickets(bounty); |
|||
record.setHuntDate(date); |
|||
record.setCreateTime(new Date()); |
|||
huntRecordMapper.insert(record); |
|||
} |
|||
|
|||
private String safeName(String name) { |
|||
return StringUtils.isEmpty(name) ? "神秘同学" : name; |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
package cc.hiver.mall.planet.service.impl; |
|||
|
|||
import cc.hiver.mall.planet.entity.PlanetNews; |
|||
import cc.hiver.mall.planet.mapper.PlanetNewsMapper; |
|||
import cc.hiver.mall.planet.service.PlanetNewsService; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.util.Date; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* 白嫖星球-星球快讯服务实现 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Service |
|||
public class PlanetNewsServiceImpl implements PlanetNewsService { |
|||
|
|||
@Autowired |
|||
private PlanetNewsMapper newsMapper; |
|||
|
|||
@Override |
|||
public void addNews(String regionId, String type, String content, String userId, String userName) { |
|||
final PlanetNews news = new PlanetNews(); |
|||
news.setId(UUID.randomUUID().toString().replace("-", "")); |
|||
news.setRegionId(regionId); |
|||
news.setType(type); |
|||
news.setContent(content); |
|||
news.setUserId(userId); |
|||
news.setUserName(userName); |
|||
news.setIsTop(0); |
|||
news.setEnabled(1); |
|||
news.setCreateTime(new Date()); |
|||
newsMapper.insert(news); |
|||
} |
|||
|
|||
@Override |
|||
public List<PlanetNews> latest(String regionId, int limit) { |
|||
// 商圈强隔离:仅返回本商圈快讯,不再混入全局快讯
|
|||
if (StringUtils.isEmpty(regionId)) { |
|||
return new java.util.ArrayList<>(); |
|||
} |
|||
final LambdaQueryWrapper<PlanetNews> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetNews::getEnabled, 1) |
|||
.eq(PlanetNews::getRegionId, regionId) |
|||
.orderByDesc(PlanetNews::getIsTop).orderByDesc(PlanetNews::getCreateTime) |
|||
.last("limit " + limit); |
|||
return newsMapper.selectList(qw); |
|||
} |
|||
} |
|||
@ -0,0 +1,157 @@ |
|||
package cc.hiver.mall.planet.service.impl; |
|||
|
|||
import cc.hiver.core.common.redis.RedisTemplateHelper; |
|||
import cc.hiver.mall.planet.constant.PlanetConstant; |
|||
import cc.hiver.mall.planet.entity.PlanetTicket; |
|||
import cc.hiver.mall.planet.mapper.PlanetTicketMapper; |
|||
import cc.hiver.mall.planet.pojo.PlanetRankItemVo; |
|||
import cc.hiver.mall.planet.service.PlanetBuffService; |
|||
import cc.hiver.mall.planet.service.PlanetRankService; |
|||
import com.alibaba.fastjson.JSON; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.util.*; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.stream.Collectors; |
|||
|
|||
/** |
|||
* 白嫖星球-通缉榜服务实现 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Slf4j |
|||
@Service |
|||
public class PlanetRankServiceImpl implements PlanetRankService { |
|||
|
|||
/** 通缉榜缓存前缀(基础榜单,不含 self 标记) */ |
|||
private static final String RANK_CACHE_PREFIX = "planet:rank:"; |
|||
/** 缓存有效期(秒),榜单允许短时弱一致,降低高频查库压力 */ |
|||
private static final long RANK_CACHE_SECONDS = 30L; |
|||
|
|||
@Autowired |
|||
private PlanetTicketMapper ticketMapper; |
|||
|
|||
@Autowired |
|||
private PlanetBuffService buffService; |
|||
|
|||
@Autowired |
|||
private RedisTemplateHelper redisTemplateHelper; |
|||
|
|||
@Override |
|||
public List<PlanetRankItemVo> rankTop(String regionId, int topN, String currentUserId) { |
|||
List<PlanetRankItemVo> base = readCache(regionId, topN); |
|||
if (base == null) { |
|||
base = buildRankFromDb(regionId, topN); |
|||
writeCache(regionId, topN, base); |
|||
} |
|||
// self 标记与请求用户相关,缓存不存储,返回时按当前用户即时打标
|
|||
for (PlanetRankItemVo vo : base) { |
|||
vo.setSelf(vo.getUserId() != null && vo.getUserId().equals(currentUserId)); |
|||
} |
|||
return base; |
|||
} |
|||
|
|||
/** |
|||
* 从数据库构建基础榜单:先批量取候选券记录,再一次性批量查 BUFF,内存组装,避免循环查库。 |
|||
*/ |
|||
private List<PlanetRankItemVo> buildRankFromDb(String regionId, int topN) { |
|||
// 多取一些以便过滤隐身用户后仍能凑满topN
|
|||
final LambdaQueryWrapper<PlanetTicket> qw = new LambdaQueryWrapper<>(); |
|||
if (StringUtils.isNotEmpty(regionId)) { |
|||
qw.eq(PlanetTicket::getRegionId, regionId); |
|||
} |
|||
qw.gt(PlanetTicket::getTicketCount, 0) |
|||
.orderByDesc(PlanetTicket::getTicketCount) |
|||
.orderByDesc(PlanetTicket::getTotalTicket) |
|||
.last("limit " + (topN + 20)); |
|||
final List<PlanetTicket> tickets = ticketMapper.selectList(qw); |
|||
if (tickets.isEmpty()) { |
|||
return new ArrayList<>(); |
|||
} |
|||
// 批量查询候选用户的生效 BUFF 类型,一次查库
|
|||
final Set<String> userIds = tickets.stream().map(PlanetTicket::getUserId).collect(Collectors.toCollection(LinkedHashSet::new)); |
|||
final Map<String, Set<String>> buffMap = buffService.activeBuffTypes(userIds); |
|||
|
|||
final List<PlanetRankItemVo> result = new ArrayList<>(); |
|||
int rankNo = 0; |
|||
for (PlanetTicket t : tickets) { |
|||
final Set<String> buffs = buffMap.get(t.getUserId()); |
|||
// 隐身BUFF用户不出现在榜单
|
|||
if (buffs != null && buffs.contains(PlanetConstant.BUFF_STEALTH)) { |
|||
continue; |
|||
} |
|||
rankNo++; |
|||
if (rankNo > topN) { |
|||
break; |
|||
} |
|||
final PlanetRankItemVo vo = new PlanetRankItemVo(); |
|||
vo.setRankNo(rankNo); |
|||
vo.setUserId(t.getUserId()); |
|||
vo.setNickname(t.getNickname()); |
|||
vo.setAvatar(t.getAvatar()); |
|||
vo.setCollege(t.getCollege()); |
|||
vo.setTicketCount(t.getTicketCount()); |
|||
vo.setDangerLevel(PlanetConstant.dangerLevel(t.getTicketCount())); |
|||
vo.setDangerLevelName(PlanetConstant.dangerLevelName(t.getTicketCount())); |
|||
vo.setRankKeepDays(t.getRankKeepDays() == null ? 0 : t.getRankKeepDays()); |
|||
vo.setBountyTickets(PlanetConstant.bountyByRank(rankNo)); |
|||
vo.setShielded(buffs != null && buffs.contains(PlanetConstant.BUFF_SHIELD)); |
|||
vo.setSelf(false); |
|||
result.add(vo); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
private List<PlanetRankItemVo> readCache(String regionId, int topN) { |
|||
try { |
|||
final String json = redisTemplateHelper.get(cacheKey(regionId, topN)); |
|||
if (StringUtils.isNotEmpty(json)) { |
|||
return JSON.parseArray(json, PlanetRankItemVo.class); |
|||
} |
|||
} catch (Exception e) { |
|||
log.warn("[白嫖星球] 读取通缉榜缓存失败 {}", e.getMessage()); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
private void writeCache(String regionId, int topN, List<PlanetRankItemVo> list) { |
|||
try { |
|||
redisTemplateHelper.set(cacheKey(regionId, topN), JSON.toJSONString(list), RANK_CACHE_SECONDS, TimeUnit.SECONDS); |
|||
} catch (Exception e) { |
|||
log.warn("[白嫖星球] 写入通缉榜缓存失败 {}", e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
private String cacheKey(String regionId, int topN) { |
|||
return RANK_CACHE_PREFIX + (StringUtils.isEmpty(regionId) ? "all" : regionId) + ":" + topN; |
|||
} |
|||
|
|||
@Override |
|||
public PlanetTicket getRankTarget(String regionId, String userId) { |
|||
final LambdaQueryWrapper<PlanetTicket> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetTicket::getUserId, userId); |
|||
if (StringUtils.isNotEmpty(regionId)) { |
|||
qw.eq(PlanetTicket::getRegionId, regionId); |
|||
} |
|||
qw.last("limit 1"); |
|||
return ticketMapper.selectOne(qw); |
|||
} |
|||
|
|||
@Override |
|||
public int userRankNo(String regionId, String userId) { |
|||
final PlanetTicket me = getRankTarget(regionId, userId); |
|||
if (me == null || me.getTicketCount() == null || me.getTicketCount() <= 0) { |
|||
return 0; |
|||
} |
|||
final LambdaQueryWrapper<PlanetTicket> qw = new LambdaQueryWrapper<>(); |
|||
if (StringUtils.isNotEmpty(regionId)) { |
|||
qw.eq(PlanetTicket::getRegionId, regionId); |
|||
} |
|||
qw.gt(PlanetTicket::getTicketCount, me.getTicketCount()); |
|||
return ticketMapper.selectCount(qw).intValue() + 1; |
|||
} |
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
package cc.hiver.mall.planet.service.impl; |
|||
|
|||
import cc.hiver.mall.planet.constant.PlanetConstant; |
|||
import cc.hiver.mall.planet.entity.PlanetBuffRecord; |
|||
import cc.hiver.mall.planet.entity.PlanetPool; |
|||
import cc.hiver.mall.planet.entity.PlanetTicket; |
|||
import cc.hiver.mall.planet.pojo.PlanetBuffVo; |
|||
import cc.hiver.mall.planet.pojo.PlanetHomeVo; |
|||
import cc.hiver.mall.planet.pojo.PlanetQuery; |
|||
import cc.hiver.mall.planet.service.*; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.util.ArrayList; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 白嫖星球-首页聚合服务实现 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Service |
|||
public class PlanetServiceImpl implements PlanetService { |
|||
|
|||
@Autowired |
|||
private PlanetTicketService ticketService; |
|||
|
|||
@Autowired |
|||
private PlanetRankService rankService; |
|||
|
|||
@Autowired |
|||
private PlanetTaskService taskService; |
|||
|
|||
@Autowired |
|||
private PlanetBuffService buffService; |
|||
|
|||
@Autowired |
|||
private PlanetBoxService boxService; |
|||
|
|||
@Autowired |
|||
private PlanetHuntService huntService; |
|||
|
|||
@Autowired |
|||
private PlanetDrawService drawService; |
|||
|
|||
@Autowired |
|||
private PlanetNewsService newsService; |
|||
|
|||
@Override |
|||
public PlanetHomeVo home(PlanetQuery query) { |
|||
final String userId = query.getUserId(); |
|||
final String regionId = query.getRegionId(); |
|||
final PlanetHomeVo vo = new PlanetHomeVo(); |
|||
|
|||
// 初始化/补全用户券记录
|
|||
final PlanetTicket ticket = ticketService.getOrCreate(userId, regionId, |
|||
query.getNickname(), query.getAvatar(), query.getCollege()); |
|||
|
|||
// 宇宙Header:当前奖池
|
|||
final PlanetPool pool = drawService.getCurrentPool(regionId); |
|||
vo.setPoolAmount(pool.getPoolAmount() == null ? BigDecimal.ZERO : pool.getPoolAmount()); |
|||
vo.setDrawTime(pool.getDrawTime()); |
|||
vo.setPeriodNo(pool.getPeriodNo()); |
|||
if (pool.getDrawTime() != null) { |
|||
vo.setCountdownMillis(Math.max(0, pool.getDrawTime().getTime() - new Date().getTime())); |
|||
} else { |
|||
vo.setCountdownMillis(0L); |
|||
} |
|||
vo.setJoinCount(pool.getJoinCount() == null ? 0 : pool.getJoinCount()); |
|||
|
|||
// 我的星球中心
|
|||
vo.setMyTicketCount(ticket.getTicketCount() == null ? 0 : ticket.getTicketCount()); |
|||
vo.setNickname(ticket.getNickname()); |
|||
vo.setAvatar(ticket.getAvatar()); |
|||
vo.setConsecutiveSignDays(ticket.getConsecutiveSignDays() == null ? 0 : ticket.getConsecutiveSignDays()); |
|||
vo.setSignedToday(taskService.signedToday(userId, regionId)); |
|||
vo.setBoxAvailable(boxService.boxAvailable(userId, regionId)); |
|||
vo.setRemainHunt(huntService.remainHunt(userId)); |
|||
vo.setLevel(PlanetConstant.dangerLevel(vo.getMyTicketCount())); |
|||
vo.setMyRankNo(rankService.userRankNo(regionId, userId)); |
|||
|
|||
// 我生效的BUFF
|
|||
final List<PlanetBuffVo> myBuffs = new ArrayList<>(); |
|||
for (PlanetBuffRecord r : buffService.activeBuffs(userId)) { |
|||
final PlanetBuffVo bv = new PlanetBuffVo(); |
|||
bv.setId(r.getBuffId()); |
|||
bv.setType(r.getType()); |
|||
bv.setEffectValue(r.getEffectValue()); |
|||
bv.setActive(true); |
|||
bv.setActiveEndTime(r.getEndTime()); |
|||
myBuffs.add(bv); |
|||
} |
|||
vo.setMyBuffs(myBuffs); |
|||
|
|||
// 各板块
|
|||
vo.setTasks(taskService.listTasks(userId, regionId)); |
|||
vo.setRankList(rankService.rankTop(regionId, 10, userId)); |
|||
vo.setBuffShop(buffService.shopList(userId, regionId)); |
|||
vo.setNewsList(newsService.latest(regionId, 20)); |
|||
return vo; |
|||
} |
|||
} |
|||
@ -0,0 +1,199 @@ |
|||
package cc.hiver.mall.planet.service.impl; |
|||
|
|||
import cc.hiver.core.common.exception.HiverException; |
|||
import cc.hiver.mall.planet.constant.PlanetConstant; |
|||
import cc.hiver.mall.planet.entity.PlanetTask; |
|||
import cc.hiver.mall.planet.entity.PlanetTicket; |
|||
import cc.hiver.mall.planet.entity.PlanetTicketLog; |
|||
import cc.hiver.mall.planet.mapper.PlanetTaskMapper; |
|||
import cc.hiver.mall.planet.mapper.PlanetTicketLogMapper; |
|||
import cc.hiver.mall.planet.mapper.PlanetTicketMapper; |
|||
import cc.hiver.mall.planet.pojo.PlanetTaskVo; |
|||
import cc.hiver.mall.planet.service.PlanetNewsService; |
|||
import cc.hiver.mall.planet.service.PlanetTaskService; |
|||
import cc.hiver.mall.planet.service.PlanetTicketService; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.time.LocalDate; |
|||
import java.time.LocalDateTime; |
|||
import java.time.LocalTime; |
|||
import java.time.ZoneId; |
|||
import java.util.ArrayList; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* 白嫖星球-任务服务实现 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Service |
|||
public class PlanetTaskServiceImpl implements PlanetTaskService { |
|||
|
|||
@Autowired |
|||
private PlanetTaskMapper taskMapper; |
|||
|
|||
@Autowired |
|||
private PlanetTicketMapper ticketMapper; |
|||
|
|||
@Autowired |
|||
private PlanetTicketLogMapper ticketLogMapper; |
|||
|
|||
@Autowired |
|||
private PlanetTicketService ticketService; |
|||
|
|||
@Autowired |
|||
private PlanetNewsService newsService; |
|||
|
|||
@Override |
|||
public List<PlanetTaskVo> listTasks(String userId, String regionId) { |
|||
final List<PlanetTask> tasks = listEnabledTasks(regionId); |
|||
final List<PlanetTaskVo> list = new ArrayList<>(); |
|||
for (PlanetTask task : tasks) { |
|||
final PlanetTaskVo vo = new PlanetTaskVo(); |
|||
vo.setCode(task.getCode()); |
|||
vo.setName(task.getName()); |
|||
vo.setDescription(task.getDescription()); |
|||
vo.setIcon(task.getIcon()); |
|||
vo.setRewardTickets(task.getRewardTickets()); |
|||
vo.setDailyLimit(task.getDailyLimit()); |
|||
final int todayCount = countTodayByType(userId, task.getCode()); |
|||
vo.setTodayCount(todayCount); |
|||
if (PlanetConstant.TASK_SIGN.equals(task.getCode())) { |
|||
vo.setActionType("claim"); |
|||
vo.setCanClaim(!signedToday(userId, regionId)); |
|||
} else { |
|||
vo.setActionType("auto"); |
|||
vo.setCanClaim(false); |
|||
} |
|||
list.add(vo); |
|||
} |
|||
return list; |
|||
} |
|||
|
|||
@Override |
|||
public int sign(String userId, String regionId) { |
|||
final PlanetTicket ticket = ticketService.getOrCreate(userId, regionId, null, null, null); |
|||
final LocalDate today = LocalDate.now(); |
|||
if (ticket.getLastSignDate() != null && ticket.getLastSignDate().isEqual(today)) { |
|||
throw new HiverException("今日已签到"); |
|||
} |
|||
// 连续签到天数
|
|||
int days = 1; |
|||
if (ticket.getLastSignDate() != null && ticket.getLastSignDate().isEqual(today.minusDays(1))) { |
|||
days = (ticket.getConsecutiveSignDays() == null ? 0 : ticket.getConsecutiveSignDays()) + 1; |
|||
} |
|||
ticket.setConsecutiveSignDays(days); |
|||
ticket.setLastSignDate(today); |
|||
ticket.setUpdateTime(new Date()); |
|||
ticketMapper.updateById(ticket); |
|||
|
|||
final PlanetTask task = getTaskConfig(regionId, PlanetConstant.TASK_SIGN); |
|||
final int reward = task != null && task.getRewardTickets() != null ? task.getRewardTickets() : 1; |
|||
return ticketService.addTickets(userId, regionId, reward, PlanetConstant.TICKET_TYPE_SIGN, |
|||
"sign:" + userId + ":" + today, "每日签到"); |
|||
} |
|||
|
|||
@Override |
|||
public int claim(String userId, String regionId, String taskCode) { |
|||
if (PlanetConstant.TASK_SIGN.equals(taskCode)) { |
|||
return sign(userId, regionId); |
|||
} |
|||
throw new HiverException("该任务通过完成对应行为自动发放星球券"); |
|||
} |
|||
|
|||
@Override |
|||
public PlanetTask getTaskConfig(String regionId, String code) { |
|||
// 商圈强隔离:仅查本商圈任务配置,不再回退全局默认
|
|||
if (StringUtils.isEmpty(regionId)) { |
|||
return null; |
|||
} |
|||
PlanetTask t = selectRegionTask(regionId, code); |
|||
if (t == null) { |
|||
seedDefaultTasks(regionId); |
|||
t = selectRegionTask(regionId, code); |
|||
} |
|||
return t; |
|||
} |
|||
|
|||
private PlanetTask selectRegionTask(String regionId, String code) { |
|||
final LambdaQueryWrapper<PlanetTask> rq = new LambdaQueryWrapper<>(); |
|||
rq.eq(PlanetTask::getCode, code).eq(PlanetTask::getEnabled, 1) |
|||
.eq(PlanetTask::getRegionId, regionId).last("limit 1"); |
|||
return taskMapper.selectOne(rq); |
|||
} |
|||
|
|||
@Override |
|||
public boolean signedToday(String userId, String regionId) { |
|||
final PlanetTicket ticket = ticketService.getByUser(userId, regionId); |
|||
return ticket != null && ticket.getLastSignDate() != null && ticket.getLastSignDate().isEqual(LocalDate.now()); |
|||
} |
|||
|
|||
private List<PlanetTask> listEnabledTasks(String regionId) { |
|||
// 商圈强隔离:仅返回本商圈任务,首次访问自动初始化默认任务
|
|||
if (StringUtils.isEmpty(regionId)) { |
|||
return new ArrayList<>(); |
|||
} |
|||
List<PlanetTask> list = selectRegionTasks(regionId); |
|||
if (list.isEmpty()) { |
|||
seedDefaultTasks(regionId); |
|||
list = selectRegionTasks(regionId); |
|||
} |
|||
return list; |
|||
} |
|||
|
|||
private List<PlanetTask> selectRegionTasks(String regionId) { |
|||
final LambdaQueryWrapper<PlanetTask> rq = new LambdaQueryWrapper<>(); |
|||
rq.eq(PlanetTask::getRegionId, regionId).eq(PlanetTask::getEnabled, 1) |
|||
.orderByAsc(PlanetTask::getSort); |
|||
final List<PlanetTask> list = taskMapper.selectList(rq); |
|||
return list == null ? new ArrayList<>() : list; |
|||
} |
|||
|
|||
/** |
|||
* 为指定商圈初始化默认任务配置(幂等:按 code 存在则跳过)。 |
|||
*/ |
|||
private void seedDefaultTasks(String regionId) { |
|||
if (StringUtils.isEmpty(regionId)) { |
|||
return; |
|||
} |
|||
seedTask(regionId, PlanetConstant.TASK_WAIMAI, "完成外卖订单", "每完成一笔外卖订单 +1 星球券", 1, 0, 1); |
|||
seedTask(regionId, PlanetConstant.TASK_GROUP, "完成团购订单", "每完成一笔团购订单 +1 星球券", 1, 0, 2); |
|||
seedTask(regionId, PlanetConstant.TASK_INVITE, "邀请好友注册", "每邀请一位好友注册 +3 星球券", 3, 0, 3); |
|||
seedTask(regionId, PlanetConstant.TASK_SIGN, "每日签到", "每日签到领取 +1 星球券", 1, 1, 4); |
|||
} |
|||
|
|||
private void seedTask(String regionId, String code, String name, String desc, int reward, int dailyLimit, int sort) { |
|||
final LambdaQueryWrapper<PlanetTask> exist = new LambdaQueryWrapper<>(); |
|||
exist.eq(PlanetTask::getRegionId, regionId).eq(PlanetTask::getCode, code); |
|||
if (taskMapper.selectCount(exist) > 0) { |
|||
return; |
|||
} |
|||
final PlanetTask task = new PlanetTask(); |
|||
task.setId(UUID.randomUUID().toString().replace("-", "")); |
|||
task.setRegionId(regionId); |
|||
task.setCode(code); |
|||
task.setName(name); |
|||
task.setDescription(desc); |
|||
task.setRewardTickets(reward); |
|||
task.setDailyLimit(dailyLimit); |
|||
task.setSort(sort); |
|||
task.setEnabled(1); |
|||
task.setCreateTime(new Date()); |
|||
taskMapper.insert(task); |
|||
} |
|||
|
|||
private int countTodayByType(String userId, String type) { |
|||
final LocalDateTime start = LocalDateTime.of(LocalDate.now(), LocalTime.MIN); |
|||
final Date startDate = Date.from(start.atZone(ZoneId.systemDefault()).toInstant()); |
|||
final LambdaQueryWrapper<PlanetTicketLog> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetTicketLog::getUserId, userId) |
|||
.eq(PlanetTicketLog::getType, type) |
|||
.ge(PlanetTicketLog::getCreateTime, startDate); |
|||
return ticketLogMapper.selectCount(qw).intValue(); |
|||
} |
|||
} |
|||
@ -0,0 +1,229 @@ |
|||
package cc.hiver.mall.planet.service.impl; |
|||
|
|||
import cc.hiver.core.common.exception.HiverException; |
|||
import cc.hiver.core.entity.User; |
|||
import cc.hiver.core.service.UserService; |
|||
import cc.hiver.mall.planet.constant.PlanetConstant; |
|||
import cc.hiver.mall.planet.entity.PlanetBuffRecord; |
|||
import cc.hiver.mall.planet.entity.PlanetTicket; |
|||
import cc.hiver.mall.planet.entity.PlanetTicketLog; |
|||
import cc.hiver.mall.planet.mapper.PlanetBuffRecordMapper; |
|||
import cc.hiver.mall.planet.mapper.PlanetTicketLogMapper; |
|||
import cc.hiver.mall.planet.mapper.PlanetTicketMapper; |
|||
import cc.hiver.mall.planet.service.PlanetTicketService; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* 白嫖星球-星球券服务实现 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Slf4j |
|||
@Service |
|||
public class PlanetTicketServiceImpl implements PlanetTicketService { |
|||
|
|||
@Autowired |
|||
private PlanetTicketMapper ticketMapper; |
|||
|
|||
@Autowired |
|||
private PlanetTicketLogMapper ticketLogMapper; |
|||
|
|||
@Autowired |
|||
private PlanetBuffRecordMapper buffRecordMapper; |
|||
|
|||
@Autowired |
|||
private UserService userService; |
|||
|
|||
@Override |
|||
public PlanetTicket getOrCreate(String userId, String regionId, String nickname, String avatar, String college) { |
|||
if (StringUtils.isEmpty(userId)) { |
|||
throw new HiverException("用户id不能为空"); |
|||
} |
|||
PlanetTicket ticket = getByUser(userId, regionId); |
|||
if (ticket == null) { |
|||
ticket = new PlanetTicket(); |
|||
ticket.setId(UUID.randomUUID().toString().replace("-", "")); |
|||
ticket.setUserId(userId); |
|||
ticket.setRegionId(regionId); |
|||
ticket.setTicketCount(0); |
|||
ticket.setTotalTicket(0); |
|||
ticket.setConsecutiveSignDays(0); |
|||
ticket.setRankKeepDays(0); |
|||
ticket.setLastRankNo(0); |
|||
ticket.setHuntCountToday(0); |
|||
ticket.setHuntedSuccessToday(0); |
|||
fillUserInfo(ticket, nickname, avatar, college); |
|||
ticket.setCreateTime(new Date()); |
|||
ticket.setUpdateTime(new Date()); |
|||
ticketMapper.insert(ticket); |
|||
} else { |
|||
// 补全冗余信息
|
|||
boolean needUpdate = false; |
|||
if (StringUtils.isEmpty(ticket.getNickname()) && StringUtils.isNotEmpty(nickname)) { |
|||
ticket.setNickname(nickname); |
|||
needUpdate = true; |
|||
} |
|||
if (StringUtils.isEmpty(ticket.getAvatar()) && StringUtils.isNotEmpty(avatar)) { |
|||
ticket.setAvatar(avatar); |
|||
needUpdate = true; |
|||
} |
|||
if (StringUtils.isEmpty(ticket.getCollege()) && StringUtils.isNotEmpty(college)) { |
|||
ticket.setCollege(college); |
|||
needUpdate = true; |
|||
} |
|||
if (needUpdate) { |
|||
ticket.setUpdateTime(new Date()); |
|||
ticketMapper.updateById(ticket); |
|||
} |
|||
} |
|||
return ticket; |
|||
} |
|||
|
|||
private void fillUserInfo(PlanetTicket ticket, String nickname, String avatar, String college) { |
|||
ticket.setNickname(nickname); |
|||
ticket.setAvatar(avatar); |
|||
ticket.setCollege(college); |
|||
if (StringUtils.isEmpty(nickname) || StringUtils.isEmpty(avatar)) { |
|||
try { |
|||
final User user = userService.findById(ticket.getUserId()); |
|||
if (user != null) { |
|||
if (StringUtils.isEmpty(ticket.getNickname())) { |
|||
ticket.setNickname(user.getNickname()); |
|||
} |
|||
if (StringUtils.isEmpty(ticket.getAvatar())) { |
|||
ticket.setAvatar(user.getAvatar()); |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
log.warn("[白嫖星球] 获取用户信息失败 userId={}, {}", ticket.getUserId(), e.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public PlanetTicket getByUser(String userId, String regionId) { |
|||
final LambdaQueryWrapper<PlanetTicket> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetTicket::getUserId, userId); |
|||
if (StringUtils.isNotEmpty(regionId)) { |
|||
qw.eq(PlanetTicket::getRegionId, regionId); |
|||
} |
|||
qw.last("limit 1"); |
|||
return ticketMapper.selectOne(qw); |
|||
} |
|||
|
|||
@Override |
|||
public int addTickets(String userId, String regionId, int baseCount, String type, String sourceId, String remark) { |
|||
if (baseCount <= 0) { |
|||
return 0; |
|||
} |
|||
// 幂等:同一来源业务id只发放一次
|
|||
if (StringUtils.isNotEmpty(sourceId)) { |
|||
final LambdaQueryWrapper<PlanetTicketLog> exist = new LambdaQueryWrapper<>(); |
|||
exist.eq(PlanetTicketLog::getType, type) |
|||
.eq(PlanetTicketLog::getSourceId, sourceId); |
|||
if (ticketLogMapper.selectCount(exist) > 0) { |
|||
log.info("[白嫖星球] 重复发券已拦截 type={}, sourceId={}", type, sourceId); |
|||
return 0; |
|||
} |
|||
} |
|||
final PlanetTicket ticket = getOrCreate(userId, regionId, null, null, null); |
|||
// 双倍BUFF加成
|
|||
int finalCount = baseCount; |
|||
final BigDecimal multiplier = getActiveDoubleMultiplier(userId); |
|||
if (multiplier != null && multiplier.compareTo(BigDecimal.ONE) > 0) { |
|||
finalCount = multiplier.multiply(BigDecimal.valueOf(baseCount)).intValue(); |
|||
} |
|||
ticket.setTicketCount(ticket.getTicketCount() + finalCount); |
|||
ticket.setTotalTicket(ticket.getTotalTicket() + finalCount); |
|||
ticket.setUpdateTime(new Date()); |
|||
ticketMapper.updateById(ticket); |
|||
saveLog(userId, regionId, finalCount, ticket.getTicketCount(), type, sourceId, remark); |
|||
return finalCount; |
|||
} |
|||
|
|||
@Override |
|||
public int deductTickets(String userId, String regionId, int count, String type, String sourceId, String remark) { |
|||
if (count <= 0) { |
|||
throw new HiverException("扣减数量必须大于0"); |
|||
} |
|||
final PlanetTicket ticket = getOrCreate(userId, regionId, null, null, null); |
|||
if (ticket.getTicketCount() < count) { |
|||
throw new HiverException("星球券不足"); |
|||
} |
|||
ticket.setTicketCount(ticket.getTicketCount() - count); |
|||
ticket.setUpdateTime(new Date()); |
|||
ticketMapper.updateById(ticket); |
|||
saveLog(userId, regionId, -count, ticket.getTicketCount(), type, sourceId, remark); |
|||
return ticket.getTicketCount(); |
|||
} |
|||
|
|||
@Override |
|||
public int getRankNo(String regionId, String userId) { |
|||
final PlanetTicket me = getByUser(userId, regionId); |
|||
if (me == null || me.getTicketCount() == null || me.getTicketCount() <= 0) { |
|||
return 0; |
|||
} |
|||
// 比我券多的人数 + 1 即为排名
|
|||
final LambdaQueryWrapper<PlanetTicket> qw = new LambdaQueryWrapper<>(); |
|||
if (StringUtils.isNotEmpty(regionId)) { |
|||
qw.eq(PlanetTicket::getRegionId, regionId); |
|||
} |
|||
qw.gt(PlanetTicket::getTicketCount, me.getTicketCount()); |
|||
final Long greater = ticketMapper.selectCount(qw); |
|||
return greater.intValue() + 1; |
|||
} |
|||
|
|||
@Override |
|||
public Page<PlanetTicketLog> pageLog(String userId, String regionId, Integer pageNumber, Integer pageSize) { |
|||
final int num = pageNumber == null || pageNumber <= 0 ? 1 : pageNumber; |
|||
final int size = pageSize == null || pageSize <= 0 ? 10 : Math.min(pageSize, 50); |
|||
final Page<PlanetTicketLog> page = new Page<>(num, size); |
|||
final LambdaQueryWrapper<PlanetTicketLog> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetTicketLog::getUserId, userId); |
|||
if (StringUtils.isNotEmpty(regionId)) { |
|||
qw.eq(PlanetTicketLog::getRegionId, regionId); |
|||
} |
|||
qw.orderByDesc(PlanetTicketLog::getCreateTime); |
|||
return ticketLogMapper.selectPage(page, qw); |
|||
} |
|||
|
|||
private BigDecimal getActiveDoubleMultiplier(String userId) { |
|||
final LambdaQueryWrapper<PlanetBuffRecord> qw = new LambdaQueryWrapper<>(); |
|||
qw.eq(PlanetBuffRecord::getUserId, userId) |
|||
.eq(PlanetBuffRecord::getType, PlanetConstant.BUFF_DOUBLE) |
|||
.eq(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_ACTIVE) |
|||
.gt(PlanetBuffRecord::getEndTime, new Date()) |
|||
.orderByDesc(PlanetBuffRecord::getEffectValue) |
|||
.last("limit 1"); |
|||
final List<PlanetBuffRecord> list = buffRecordMapper.selectList(qw); |
|||
if (list != null && !list.isEmpty()) { |
|||
final BigDecimal v = list.get(0).getEffectValue(); |
|||
return v == null ? BigDecimal.valueOf(2) : v; |
|||
} |
|||
return BigDecimal.ONE; |
|||
} |
|||
|
|||
private void saveLog(String userId, String regionId, int change, int balance, String type, String sourceId, String remark) { |
|||
final PlanetTicketLog logEntity = new PlanetTicketLog(); |
|||
logEntity.setId(UUID.randomUUID().toString().replace("-", "")); |
|||
logEntity.setUserId(userId); |
|||
logEntity.setRegionId(regionId); |
|||
logEntity.setChangeCount(change); |
|||
logEntity.setBalance(balance); |
|||
logEntity.setType(type); |
|||
logEntity.setSourceId(sourceId); |
|||
logEntity.setRemark(remark); |
|||
logEntity.setCreateTime(new Date()); |
|||
ticketLogMapper.insert(logEntity); |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
package cc.hiver.mall.quartz; |
|||
|
|||
import cc.hiver.mall.planet.constant.PlanetConstant; |
|||
import cc.hiver.mall.planet.entity.PlanetBuffRecord; |
|||
import cc.hiver.mall.planet.entity.PlanetTicket; |
|||
import cc.hiver.mall.planet.mapper.PlanetBuffRecordMapper; |
|||
import cc.hiver.mall.planet.mapper.PlanetTicketMapper; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import java.util.Date; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 白嫖星球-每日重置定时任务 |
|||
* <p> |
|||
* 每日 00:05 执行: |
|||
* 1. 过期BUFF置为失效状态 |
|||
* 2. 重置今日追捕/被追计数(展示用) |
|||
* 3. 更新连续霸榜天数 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Slf4j |
|||
@Component |
|||
public class PlanetDailyResetTask { |
|||
|
|||
@Autowired |
|||
private PlanetTicketMapper ticketMapper; |
|||
|
|||
@Autowired |
|||
private PlanetBuffRecordMapper buffRecordMapper; |
|||
|
|||
@Scheduled(cron = "0 5 0 * * ?") |
|||
public void dailyReset() { |
|||
try { |
|||
// 1. 过期BUFF
|
|||
final LambdaUpdateWrapper<PlanetBuffRecord> bw = new LambdaUpdateWrapper<>(); |
|||
bw.eq(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_ACTIVE) |
|||
.lt(PlanetBuffRecord::getEndTime, new Date()) |
|||
.set(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_EXPIRED); |
|||
buffRecordMapper.update(null, bw); |
|||
|
|||
// 2. 重置每日计数(展示字段)
|
|||
final LambdaUpdateWrapper<PlanetTicket> tw = new LambdaUpdateWrapper<>(); |
|||
tw.set(PlanetTicket::getHuntCountToday, 0) |
|||
.set(PlanetTicket::getHuntedSuccessToday, 0); |
|||
ticketMapper.update(null, tw); |
|||
|
|||
// 3. 更新连续霸榜天数:仍在TOP10的用户 keepDays+1,否则清零
|
|||
updateRankKeepDays(); |
|||
log.info("[白嫖星球] 每日重置任务完成"); |
|||
} catch (Exception e) { |
|||
log.error("[白嫖星球] 每日重置任务异常: {}", e.getMessage(), e); |
|||
} |
|||
} |
|||
|
|||
private void updateRankKeepDays() { |
|||
// 取所有持券用户,按区域分别计算前10
|
|||
final LambdaQueryWrapper<PlanetTicket> qw = new LambdaQueryWrapper<>(); |
|||
qw.gt(PlanetTicket::getTicketCount, 0).orderByDesc(PlanetTicket::getTicketCount); |
|||
final List<PlanetTicket> all = ticketMapper.selectList(qw); |
|||
// 简化处理:全局/各区域统一按券数排序后,记录其在本区域的名次
|
|||
// 这里仅对每个用户基于其当前是否进入本区域TOP10更新keepDays
|
|||
final java.util.Map<String, Integer> regionRankCounter = new java.util.HashMap<>(); |
|||
for (PlanetTicket t : all) { |
|||
final String region = t.getRegionId() == null ? "" : t.getRegionId(); |
|||
final int rank = regionRankCounter.merge(region, 1, Integer::sum); |
|||
int keep = t.getRankKeepDays() == null ? 0 : t.getRankKeepDays(); |
|||
if (rank <= 10) { |
|||
keep = keep + 1; |
|||
t.setLastRankNo(rank); |
|||
} else { |
|||
keep = 0; |
|||
t.setLastRankNo(0); |
|||
} |
|||
t.setRankKeepDays(keep); |
|||
t.setUpdateTime(new Date()); |
|||
ticketMapper.updateById(t); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
package cc.hiver.mall.quartz; |
|||
|
|||
import cc.hiver.mall.planet.service.PlanetDrawService; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
/** |
|||
* 白嫖星球-到点开奖定时任务 |
|||
* 每分钟扫描到达开奖时间且未开奖的奖池并执行加权开奖 |
|||
* |
|||
* @author hiver |
|||
*/ |
|||
@Slf4j |
|||
@Component |
|||
public class PlanetDrawTask { |
|||
|
|||
@Autowired |
|||
private PlanetDrawService drawService; |
|||
|
|||
@Scheduled(cron = "0 * * * * ?") |
|||
public void drawDuePools() { |
|||
try { |
|||
final int count = drawService.drawDuePools(); |
|||
if (count > 0) { |
|||
log.info("[白嫖星球] 本轮开奖奖池数量={}", count); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("[白嫖星球] 开奖任务异常: {}", e.getMessage(), e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,2 @@ |
|||
ALTER TABLE ie_user_profile |
|||
ADD COLUMN persona_images VARCHAR(3000) NULL COMMENT '人格卡片图片,JSON数组,最多5张'; |
|||
@ -0,0 +1,244 @@ |
|||
-- ============================================================= |
|||
-- 白嫖星球 游戏化福利系统 建表脚本 |
|||
-- 模块前缀:t_planet_* |
|||
-- 主键统一 varchar(64),时间字段统一 datetime |
|||
-- ============================================================= |
|||
|
|||
-- 1. 奖池/期次表 ------------------------------------------------- |
|||
DROP TABLE IF EXISTS `t_planet_pool`; |
|||
CREATE TABLE `t_planet_pool` ( |
|||
`id` varchar(64) NOT NULL COMMENT '主键', |
|||
`region_id` varchar(64) DEFAULT NULL COMMENT '商圈/校区id', |
|||
`period_no` varchar(64) DEFAULT NULL COMMENT '期号', |
|||
`title` varchar(100) DEFAULT NULL COMMENT '本期标题', |
|||
`pool_amount` decimal(10,2) DEFAULT '0.00' COMMENT '奖池总金额(元)', |
|||
`draw_time` datetime DEFAULT NULL COMMENT '开奖时间', |
|||
`join_count` int DEFAULT '0' COMMENT '参与人数', |
|||
`ticket_total` int DEFAULT '0' COMMENT '本期投入券总数', |
|||
`winner_count` int DEFAULT '0' COMMENT '中奖人数', |
|||
`status` int DEFAULT '0' COMMENT '状态 0进行中 1已开奖', |
|||
`remark` varchar(255) DEFAULT NULL COMMENT '备注/公告', |
|||
`create_time` datetime DEFAULT NULL, |
|||
`update_time` datetime DEFAULT NULL, |
|||
PRIMARY KEY (`id`), |
|||
KEY `idx_region` (`region_id`), |
|||
KEY `idx_region_period` (`region_id`,`period_no`), |
|||
KEY `idx_status` (`status`) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT='白嫖星球-奖池期次'; |
|||
|
|||
-- 2. 奖项配置表 ------------------------------------------------- |
|||
DROP TABLE IF EXISTS `t_planet_reward`; |
|||
CREATE TABLE `t_planet_reward` ( |
|||
`id` varchar(64) NOT NULL COMMENT '主键', |
|||
`pool_id` varchar(64) DEFAULT NULL COMMENT '奖池id', |
|||
`region_id` varchar(64) DEFAULT NULL COMMENT '商圈id', |
|||
`level` int DEFAULT NULL COMMENT '奖项层级 1一等 2二等 3三等 4幸运', |
|||
`level_name` varchar(50) DEFAULT NULL COMMENT '奖项名称', |
|||
`reward_type` int DEFAULT '0' COMMENT '奖励类型 0现金 1优惠券', |
|||
`amount` decimal(10,2) DEFAULT '0.00' COMMENT '单份金额(元)', |
|||
`coupon_id` varchar(64) DEFAULT NULL COMMENT '优惠券id', |
|||
`quota` int DEFAULT '0' COMMENT '名额数量', |
|||
`sort` int DEFAULT '0' COMMENT '排序', |
|||
`create_time` datetime DEFAULT NULL, |
|||
PRIMARY KEY (`id`), |
|||
KEY `idx_pool` (`pool_id`), |
|||
KEY `idx_region` (`region_id`) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT='白嫖星球-奖项配置'; |
|||
|
|||
-- 3. 用户券汇总表 ----------------------------------------------- |
|||
DROP TABLE IF EXISTS `t_planet_ticket`; |
|||
CREATE TABLE `t_planet_ticket` ( |
|||
`id` varchar(64) NOT NULL COMMENT '主键', |
|||
`user_id` varchar(64) NOT NULL COMMENT '用户id', |
|||
`region_id` varchar(64) DEFAULT NULL COMMENT '商圈id', |
|||
`nickname` varchar(100) DEFAULT NULL COMMENT '昵称冗余', |
|||
`avatar` varchar(500) DEFAULT NULL COMMENT '头像冗余', |
|||
`college` varchar(100) DEFAULT NULL COMMENT '学院', |
|||
`ticket_count` int DEFAULT '0' COMMENT '当前可用券数量', |
|||
`total_ticket` int DEFAULT '0' COMMENT '历史累计获得券', |
|||
`consecutive_sign_days` int DEFAULT '0' COMMENT '连续签到天数', |
|||
`last_sign_date` date DEFAULT NULL COMMENT '最近签到日期', |
|||
`rank_keep_days` int DEFAULT '0' COMMENT '连续霸榜天数', |
|||
`last_rank_no` int DEFAULT '0' COMMENT '上次排名', |
|||
`hunt_count_today` int DEFAULT '0' COMMENT '今日已追捕次数', |
|||
`hunted_success_today` int DEFAULT '0' COMMENT '今日被追捕成功次数', |
|||
`box_opened_date` date DEFAULT NULL COMMENT '最近开宝箱日期', |
|||
`period_no` varchar(64) DEFAULT NULL COMMENT '所属期号', |
|||
`create_time` datetime DEFAULT NULL, |
|||
`update_time` datetime DEFAULT NULL, |
|||
PRIMARY KEY (`id`), |
|||
UNIQUE KEY `uk_user_region` (`user_id`,`region_id`), |
|||
KEY `idx_region_ticket` (`region_id`,`ticket_count`) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT='白嫖星球-用户券汇总'; |
|||
|
|||
-- 4. 券流水表 --------------------------------------------------- |
|||
DROP TABLE IF EXISTS `t_planet_ticket_log`; |
|||
CREATE TABLE `t_planet_ticket_log` ( |
|||
`id` varchar(64) NOT NULL COMMENT '主键', |
|||
`user_id` varchar(64) NOT NULL COMMENT '用户id', |
|||
`region_id` varchar(64) DEFAULT NULL COMMENT '商圈id', |
|||
`change_count` int DEFAULT '0' COMMENT '变更数量(正得负扣)', |
|||
`balance` int DEFAULT '0' COMMENT '变更后余额', |
|||
`type` varchar(20) DEFAULT NULL COMMENT '来源 order/group/invite/sign/box/hunt/buff/draw', |
|||
`source_id` varchar(64) DEFAULT NULL COMMENT '来源业务id(幂等)', |
|||
`period_no` varchar(64) DEFAULT NULL COMMENT '期号', |
|||
`remark` varchar(255) DEFAULT NULL COMMENT '说明', |
|||
`create_time` datetime DEFAULT NULL, |
|||
PRIMARY KEY (`id`), |
|||
KEY `idx_user` (`user_id`), |
|||
KEY `idx_source` (`type`,`source_id`), |
|||
KEY `idx_create_time` (`create_time`) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT='白嫖星球-券流水'; |
|||
|
|||
-- 5. 任务规则表 ------------------------------------------------- |
|||
DROP TABLE IF EXISTS `t_planet_task`; |
|||
CREATE TABLE `t_planet_task` ( |
|||
`id` varchar(64) NOT NULL COMMENT '主键', |
|||
`region_id` varchar(64) DEFAULT NULL COMMENT '商圈id 空为全局默认', |
|||
`code` varchar(20) DEFAULT NULL COMMENT '任务编码 waimai/group/invite/sign', |
|||
`name` varchar(50) DEFAULT NULL COMMENT '任务名称', |
|||
`description` varchar(255) DEFAULT NULL COMMENT '描述', |
|||
`icon` varchar(500) DEFAULT NULL COMMENT '图标', |
|||
`reward_tickets` int DEFAULT '1' COMMENT '奖励券数量', |
|||
`daily_limit` int DEFAULT '0' COMMENT '每日上限 0不限', |
|||
`sort` int DEFAULT '0' COMMENT '排序', |
|||
`enabled` int DEFAULT '1' COMMENT '是否启用 1是 0否', |
|||
`create_time` datetime DEFAULT NULL, |
|||
PRIMARY KEY (`id`), |
|||
KEY `idx_code` (`code`) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT='白嫖星球-任务规则'; |
|||
|
|||
-- 6. BUFF配置表 ------------------------------------------------- |
|||
DROP TABLE IF EXISTS `t_planet_buff`; |
|||
CREATE TABLE `t_planet_buff` ( |
|||
`id` varchar(64) NOT NULL COMMENT '主键', |
|||
`region_id` varchar(64) DEFAULT NULL COMMENT '商圈id 空为全局默认', |
|||
`type` varchar(20) DEFAULT NULL COMMENT '类型 double/shield/lucky/hunt/stealth', |
|||
`name` varchar(50) DEFAULT NULL COMMENT '名称', |
|||
`description` varchar(255) DEFAULT NULL COMMENT '描述', |
|||
`icon` varchar(500) DEFAULT NULL COMMENT '图标', |
|||
`cost_tickets` int DEFAULT '0' COMMENT '消耗券数量', |
|||
`duration_hours` int DEFAULT '24' COMMENT '持续小时', |
|||
`effect_value` decimal(10,2) DEFAULT '0.00' COMMENT '效果值(如加权0.2)', |
|||
`sort` int DEFAULT '0' COMMENT '排序', |
|||
`enabled` int DEFAULT '1' COMMENT '是否启用 1是 0否', |
|||
`create_time` datetime DEFAULT NULL, |
|||
PRIMARY KEY (`id`), |
|||
KEY `idx_type` (`type`) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT='白嫖星球-BUFF配置'; |
|||
|
|||
-- 7. 用户BUFF记录表 --------------------------------------------- |
|||
DROP TABLE IF EXISTS `t_planet_buff_record`; |
|||
CREATE TABLE `t_planet_buff_record` ( |
|||
`id` varchar(64) NOT NULL COMMENT '主键', |
|||
`user_id` varchar(64) NOT NULL COMMENT '用户id', |
|||
`region_id` varchar(64) DEFAULT NULL COMMENT '商圈id', |
|||
`buff_id` varchar(64) DEFAULT NULL COMMENT 'BUFF配置id', |
|||
`type` varchar(20) DEFAULT NULL COMMENT '类型', |
|||
`effect_value` decimal(10,2) DEFAULT '0.00' COMMENT '效果值', |
|||
`start_time` datetime DEFAULT NULL COMMENT '生效时间', |
|||
`end_time` datetime DEFAULT NULL COMMENT '失效时间', |
|||
`status` int DEFAULT '0' COMMENT '状态 0生效 1已过期', |
|||
`source` varchar(20) DEFAULT 'buy' COMMENT '来源 buy购买 box宝箱 hunt防护', |
|||
`create_time` datetime DEFAULT NULL, |
|||
PRIMARY KEY (`id`), |
|||
KEY `idx_user_status` (`user_id`,`status`), |
|||
KEY `idx_end_time` (`end_time`) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT='白嫖星球-用户BUFF记录'; |
|||
|
|||
-- 8. 追捕记录表 ------------------------------------------------- |
|||
DROP TABLE IF EXISTS `t_planet_hunt_record`; |
|||
CREATE TABLE `t_planet_hunt_record` ( |
|||
`id` varchar(64) NOT NULL COMMENT '主键', |
|||
`from_user_id` varchar(64) DEFAULT NULL COMMENT '发起人id', |
|||
`from_user_name` varchar(100) DEFAULT NULL COMMENT '发起人昵称', |
|||
`to_user_id` varchar(64) DEFAULT NULL COMMENT '目标用户id', |
|||
`to_user_name` varchar(100) DEFAULT NULL COMMENT '目标用户昵称', |
|||
`region_id` varchar(64) DEFAULT NULL COMMENT '商圈id', |
|||
`result` varchar(20) DEFAULT NULL COMMENT '结果 success/shield/fail', |
|||
`gain_tickets` int DEFAULT '0' COMMENT '缴获券', |
|||
`bounty_tickets` int DEFAULT '0' COMMENT '悬赏额外券', |
|||
`hunt_date` date DEFAULT NULL COMMENT '追捕日期', |
|||
`create_time` datetime DEFAULT NULL, |
|||
PRIMARY KEY (`id`), |
|||
KEY `idx_from_date` (`from_user_id`,`hunt_date`), |
|||
KEY `idx_to_date` (`to_user_id`,`hunt_date`) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT='白嫖星球-追捕记录'; |
|||
|
|||
-- 9. 开奖记录表 ------------------------------------------------- |
|||
DROP TABLE IF EXISTS `t_planet_draw_record`; |
|||
CREATE TABLE `t_planet_draw_record` ( |
|||
`id` varchar(64) NOT NULL COMMENT '主键', |
|||
`pool_id` varchar(64) DEFAULT NULL COMMENT '奖池id', |
|||
`region_id` varchar(64) DEFAULT NULL COMMENT '商圈id', |
|||
`period_no` varchar(64) DEFAULT NULL COMMENT '期号', |
|||
`pool_amount` decimal(10,2) DEFAULT '0.00' COMMENT '奖池金额', |
|||
`join_count` int DEFAULT '0' COMMENT '参与人数', |
|||
`ticket_total` int DEFAULT '0' COMMENT '券总数', |
|||
`winner_count` int DEFAULT '0' COMMENT '中奖人数', |
|||
`status` int DEFAULT '1' COMMENT '状态 1已开奖', |
|||
`draw_time` datetime DEFAULT NULL COMMENT '开奖时间', |
|||
`create_time` datetime DEFAULT NULL, |
|||
PRIMARY KEY (`id`), |
|||
KEY `idx_pool` (`pool_id`), |
|||
KEY `idx_region_period` (`region_id`,`period_no`) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT='白嫖星球-开奖记录'; |
|||
|
|||
-- 10. 中奖记录表 ------------------------------------------------ |
|||
DROP TABLE IF EXISTS `t_planet_draw_winner`; |
|||
CREATE TABLE `t_planet_draw_winner` ( |
|||
`id` varchar(64) NOT NULL COMMENT '主键', |
|||
`draw_id` varchar(64) DEFAULT NULL COMMENT '开奖记录id', |
|||
`pool_id` varchar(64) DEFAULT NULL COMMENT '奖池id', |
|||
`region_id` varchar(64) DEFAULT NULL COMMENT '商圈id', |
|||
`period_no` varchar(64) DEFAULT NULL COMMENT '期号', |
|||
`user_id` varchar(64) DEFAULT NULL COMMENT '中奖用户id', |
|||
`user_name` varchar(100) DEFAULT NULL COMMENT '中奖用户昵称', |
|||
`reward_level` int DEFAULT NULL COMMENT '奖项层级', |
|||
`level_name` varchar(50) DEFAULT NULL COMMENT '奖项名称', |
|||
`reward_type` int DEFAULT '0' COMMENT '奖励类型 0现金 1优惠券', |
|||
`amount` decimal(10,2) DEFAULT '0.00' COMMENT '中奖金额', |
|||
`coupon_id` varchar(64) DEFAULT NULL COMMENT '优惠券id', |
|||
`is_received` int DEFAULT '0' COMMENT '是否已领取 0否 1是', |
|||
`received_time` datetime DEFAULT NULL COMMENT '领取时间', |
|||
`create_time` datetime DEFAULT NULL, |
|||
PRIMARY KEY (`id`), |
|||
KEY `idx_user` (`user_id`), |
|||
KEY `idx_draw` (`draw_id`), |
|||
KEY `idx_region_period` (`region_id`,`period_no`) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT='白嫖星球-中奖记录'; |
|||
|
|||
-- 11. 星球快讯表 ------------------------------------------------ |
|||
DROP TABLE IF EXISTS `t_planet_news`; |
|||
CREATE TABLE `t_planet_news` ( |
|||
`id` varchar(64) NOT NULL COMMENT '主键', |
|||
`region_id` varchar(64) DEFAULT NULL COMMENT '商圈id', |
|||
`type` varchar(20) DEFAULT NULL COMMENT '类型 rank/hunt/box/draw/sys', |
|||
`content` varchar(255) DEFAULT NULL COMMENT '快讯内容', |
|||
`user_id` varchar(64) DEFAULT NULL COMMENT '关联用户id', |
|||
`user_name` varchar(100) DEFAULT NULL COMMENT '关联用户昵称', |
|||
`is_top` int DEFAULT '0' COMMENT '是否置顶 0否 1是', |
|||
`enabled` int DEFAULT '1' COMMENT '是否展示 1是 0否', |
|||
`create_time` datetime DEFAULT NULL, |
|||
PRIMARY KEY (`id`), |
|||
KEY `idx_region_time` (`region_id`,`create_time`) |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT='白嫖星球-星球快讯'; |
|||
|
|||
-- ============================================================= |
|||
-- 初始化全局默认配置(region_id 为空字符串表示全局默认) |
|||
-- ============================================================= |
|||
|
|||
-- 默认任务 |
|||
INSERT INTO `t_planet_task` (`id`,`region_id`,`code`,`name`,`description`,`reward_tickets`,`daily_limit`,`sort`,`enabled`,`create_time`) VALUES |
|||
('ptk_waimai','','waimai','完成外卖订单','每完成一笔外卖订单 +1 星球券',1,0,1,1,NOW()), |
|||
('ptk_group','','group','完成团购订单','每完成一笔团购订单 +1 星球券',1,0,2,1,NOW()), |
|||
('ptk_invite','','invite','邀请好友注册','每邀请一位好友注册 +3 星球券',3,0,3,1,NOW()), |
|||
('ptk_sign','','sign','每日签到','每日签到领取 +1 星球券',1,1,4,1,NOW()); |
|||
|
|||
-- 默认BUFF |
|||
INSERT INTO `t_planet_buff` (`id`,`region_id`,`type`,`name`,`description`,`cost_tickets`,`duration_hours`,`effect_value`,`sort`,`enabled`,`create_time`) VALUES |
|||
('pbf_double','','double','双倍能量','24小时内获得星球券翻倍',5,24,2.00,1,1,NOW()), |
|||
('pbf_shield','','shield','星际防护罩','24小时内免疫追捕(养只小松鼠警卫)',8,24,0.00,2,1,NOW()), |
|||
('pbf_lucky','','lucky','幸运星辰','开奖权重 +20%',6,24,0.20,3,1,NOW()), |
|||
('pbf_hunt','','hunt','追猎雷达','追捕成功率提升',4,24,0.30,4,1,NOW()), |
|||
('pbf_stealth','','stealth','隐身斗篷','不出现在通缉榜',6,24,0.00,5,1,NOW()); |
|||
Loading…
Reference in new issue