From 56203a5895d75d4757566123e8dced6a6f30a7bf Mon Sep 17 00:00:00 2001 From: 777 <123@qwe.com> Date: Tue, 23 Dec 2025 17:10:31 +0800 Subject: [PATCH] nnnn --- doc/20251211.sql | 73 ++++---- .../cai/controller/PrizeInfoController.java | 95 ++++++++++ .../cai/controller/PrizeOnlineController.java | 95 ++++++++++ .../java/com/ruoyi/cai/domain/PrizeInfo.java | 79 +++++++++ .../com/ruoyi/cai/domain/PrizeOnline.java | 87 +++++++++ .../com/ruoyi/cai/lottery/LotteryService.java | 167 ++++++++++++++++++ .../com/ruoyi/cai/mapper/PrizeInfoMapper.java | 14 ++ .../ruoyi/cai/mapper/PrizeOnlineMapper.java | 14 ++ .../ruoyi/cai/service/PrizeInfoService.java | 14 ++ .../ruoyi/cai/service/PrizeOnlineService.java | 14 ++ .../service/impl/PrizeInfoServiceImpl.java | 20 +++ .../service/impl/PrizeOnlineServiceImpl.java | 20 +++ .../ruoyi/cai/util/LoginLogByFileUtil.java | 2 +- .../resources/mapper/cai/PrizeInfoMapper.xml | 25 +++ .../mapper/cai/PrizeOnlineMapper.xml | 27 +++ 15 files changed, 714 insertions(+), 32 deletions(-) create mode 100644 ruoyi-cai/src/main/java/com/ruoyi/cai/controller/PrizeInfoController.java create mode 100644 ruoyi-cai/src/main/java/com/ruoyi/cai/controller/PrizeOnlineController.java create mode 100644 ruoyi-cai/src/main/java/com/ruoyi/cai/domain/PrizeInfo.java create mode 100644 ruoyi-cai/src/main/java/com/ruoyi/cai/domain/PrizeOnline.java create mode 100644 ruoyi-cai/src/main/java/com/ruoyi/cai/lottery/LotteryService.java create mode 100644 ruoyi-cai/src/main/java/com/ruoyi/cai/mapper/PrizeInfoMapper.java create mode 100644 ruoyi-cai/src/main/java/com/ruoyi/cai/mapper/PrizeOnlineMapper.java create mode 100644 ruoyi-cai/src/main/java/com/ruoyi/cai/service/PrizeInfoService.java create mode 100644 ruoyi-cai/src/main/java/com/ruoyi/cai/service/PrizeOnlineService.java create mode 100644 ruoyi-cai/src/main/java/com/ruoyi/cai/service/impl/PrizeInfoServiceImpl.java create mode 100644 ruoyi-cai/src/main/java/com/ruoyi/cai/service/impl/PrizeOnlineServiceImpl.java create mode 100644 ruoyi-cai/src/main/resources/mapper/cai/PrizeInfoMapper.xml create mode 100644 ruoyi-cai/src/main/resources/mapper/cai/PrizeOnlineMapper.xml diff --git a/doc/20251211.sql b/doc/20251211.sql index 3d3aea13..abc7c63f 100644 --- a/doc/20251211.sql +++ b/doc/20251211.sql @@ -1,33 +1,44 @@ -CREATE TABLE `cai_draw_sku_info` +CREATE TABLE `cai_prize_info` ( - `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '子账户ID', - `sku_type` tinyint not null comment '奖品类型', - `sku_price` bigint(20) not null default 0 comment '奖品价值估算', - `sku_name` varchar(255) not null comment '奖品名称', - `auto_give` tinyint not null default 0 comment '是否自动兑奖', - - - `usercode` varchar(100) NOT NULL COMMENT '用户', - `message` varchar(100) NOT NULL COMMENT '账户明细说明', - `action_type` varchar(36) DEFAULT NULL COMMENT '触发来源 1-充值 2-分销 3-抽奖', - `tar_user_id` bigint DEFAULT NULL COMMENT '目标ID,用户、抽奖ID', - `tar_usercode` varchar(20) DEFAULT NULL COMMENT '目标用户Code,有用户才有用', - `tar_name` varchar(255) DEFAULT NULL COMMENT '目标名称,用户名称,抽奖名称', - `tar_price` bigint DEFAULT NULL COMMENT '礼物价值', - `tar_img` varchar(255) DEFAULT NULL COMMENT '目标提前缓存的', - `tar_json` JSON DEFAULT NULL COMMENT '目标额外字段', - `change_value` bigint NOT NULL DEFAULT '0.00' COMMENT '变化值,为正 或者为负', - `operate_ip` varchar(15) DEFAULT '' COMMENT '操作IP', - `is_admin` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否为后台用户手动调整', - `trace_link_type` varchar(36) DEFAULT NULL COMMENT '跟踪类型 1-充值 2-分销 3-抽奖', - `trace_id` varchar(50) DEFAULT NULL COMMENT '跟踪ID 订单号-礼物ID', - `give_flag` tinyint NOT NULL DEFAULT '0' COMMENT '是否兑换', - `give_time` datetime comment '兑换时间', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`) USING BTREE, - INDEX `user_id` (`user_id`) USING BTREE + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '奖品ID', + `prize_name` varchar(100) NOT NULL COMMENT '奖品名称', + `prize_desc` varchar(500) DEFAULT '' COMMENT '奖品描述', + `prize_img` varchar(255) DEFAULT '' COMMENT '奖品图片地址', + `win_probability` decimal(5, 4) NOT NULL COMMENT '中奖率(0-1,如0.0100表示1%)', + `guarantee_draws` int NOT NULL DEFAULT 0 COMMENT '保底抽数(0表示无保底,谢谢惠顾奖无效)', + `min_win_draws` int NOT NULL DEFAULT 0 COMMENT '最低中奖抽数(0表示无限制,谢谢惠顾奖无效)', + `stock` int NOT NULL DEFAULT 0 COMMENT '奖品库存(谢谢惠顾奖填0,不校验)', + `prize_type` tinyint not null comment '奖品类型', + `prize_price` bigint(20) not null default 0 comment '奖品价值估算', + `auto_give` tinyint not null default 0 comment '是否自动兑奖', + `is_thank` tinyint NOT NULL DEFAULT 0 COMMENT '是否为谢谢惠顾奖:0-否,1-是(全局仅一个)', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_is_thank` (`is_thank`) COMMENT '谢谢惠顾奖索引' ) ENGINE = InnoDB - AUTO_INCREMENT = 1 - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_general_ci - ROW_FORMAT = DYNAMIC COMMENT ='抽奖奖池'; + DEFAULT CHARSET = utf8mb4 COMMENT ='奖品基础表'; + + +CREATE TABLE `cai_prize_online` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '奖品ID', + `prize_id` bigint NOT NULL COMMENT '奖品ID', + `gender` tinyint(1) NOT NULL DEFAULT '0' COMMENT '性别 1-女 2-男', + `prize_name` varchar(100) NOT NULL COMMENT '奖品名称', + `prize_desc` varchar(500) DEFAULT '' COMMENT '奖品描述', + `prize_img` varchar(255) DEFAULT '' COMMENT '奖品图片地址', + `win_probability` decimal(5, 4) NOT NULL COMMENT '中奖率(0-1,如0.0100表示1%)', + `guarantee_draws` int NOT NULL DEFAULT 0 COMMENT '保底抽数(0表示无保底,谢谢惠顾奖无效)', + `min_win_draws` int NOT NULL DEFAULT 0 COMMENT '最低中奖抽数(0表示无限制,谢谢惠顾奖无效)', + `stock` int NOT NULL DEFAULT 0 COMMENT '奖品库存(谢谢惠顾奖填0,不校验)', + `prize_type` tinyint not null comment '奖品类型', + `prize_price` bigint(20) not null default 0 comment '奖品价值估算', + `auto_give` tinyint not null default 0 comment '是否自动兑奖', + `is_thank` tinyint NOT NULL DEFAULT 0 COMMENT '是否为谢谢惠顾奖:0-否,1-是(全局仅一个)', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_is_thank` (`is_thank`) COMMENT '谢谢惠顾奖索引' +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='已发布奖品表'; diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/controller/PrizeInfoController.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/controller/PrizeInfoController.java new file mode 100644 index 00000000..2915e57a --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/controller/PrizeInfoController.java @@ -0,0 +1,95 @@ +package com.ruoyi.cai.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.cai.domain.PrizeInfo; +import com.ruoyi.cai.service.PrizeInfoService; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.annotation.RepeatSubmit; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.PageQuery; +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.validate.AddGroup; +import com.ruoyi.common.core.validate.EditGroup; +import com.ruoyi.common.enums.BusinessType; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.Arrays; + +/** + * 抽奖奖品 + * + * @author 77 + * @date 2025-12-23 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/cai/prizeInfo") +public class PrizeInfoController extends BaseController { + + private final PrizeInfoService prizeInfoService; + + /** + * 查询抽奖奖品列表 + */ + @SaCheckPermission("cai:prizeInfo:list") + @GetMapping("/list") + public TableDataInfo list(PrizeInfo bo, PageQuery pageQuery) { + Page page = prizeInfoService.page(pageQuery.build(), Wrappers.lambdaQuery(PrizeInfo.class)); + return TableDataInfo.build(page); + } + + /** + * 获取抽奖奖品详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("cai:prizeInfo:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(prizeInfoService.getById(id)); + } + + /** + * 新增抽奖奖品 + */ + @SaCheckPermission("cai:prizeInfo:add") + @Log(title = "抽奖奖品", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody PrizeInfo bo) { + return toAjax(prizeInfoService.save(bo)); + } + + /** + * 修改抽奖奖品 + */ + @SaCheckPermission("cai:prizeInfo:edit") + @Log(title = "抽奖奖品", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody PrizeInfo bo) { + return toAjax(prizeInfoService.updateById(bo)); + } + + /** + * 删除抽奖奖品 + * + * @param ids 主键串 + */ + @SaCheckPermission("cai:prizeInfo:remove") + @Log(title = "抽奖奖品", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(prizeInfoService.removeBatchByIds(Arrays.asList(ids), true)); + } +} diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/controller/PrizeOnlineController.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/controller/PrizeOnlineController.java new file mode 100644 index 00000000..24501614 --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/controller/PrizeOnlineController.java @@ -0,0 +1,95 @@ +package com.ruoyi.cai.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.cai.domain.PrizeOnline; +import com.ruoyi.cai.service.PrizeOnlineService; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.annotation.RepeatSubmit; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.PageQuery; +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.validate.AddGroup; +import com.ruoyi.common.core.validate.EditGroup; +import com.ruoyi.common.enums.BusinessType; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.Arrays; + +/** + * 已发布奖品 + * + * @author ruoyi + * @date 2025-12-23 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/cai/prizeOnline") +public class PrizeOnlineController extends BaseController { + + private final PrizeOnlineService prizeOnlineService; + + /** + * 查询已发布奖品列表 + */ + @SaCheckPermission("cai:prizeOnline:list") + @GetMapping("/list") + public TableDataInfo list(PrizeOnline bo, PageQuery pageQuery) { + Page page = prizeOnlineService.page(pageQuery.build(), Wrappers.lambdaQuery(PrizeOnline.class)); + return TableDataInfo.build(page); + } + + /** + * 获取已发布奖品详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("cai:prizeOnline:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(prizeOnlineService.getById(id)); + } + + /** + * 新增已发布奖品 + */ + @SaCheckPermission("cai:prizeOnline:add") + @Log(title = "已发布奖品", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody PrizeOnline bo) { + return toAjax(prizeOnlineService.save(bo)); + } + + /** + * 修改已发布奖品 + */ + @SaCheckPermission("cai:prizeOnline:edit") + @Log(title = "已发布奖品", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody PrizeOnline bo) { + return toAjax(prizeOnlineService.updateById(bo)); + } + + /** + * 删除已发布奖品 + * + * @param ids 主键串 + */ + @SaCheckPermission("cai:prizeOnline:remove") + @Log(title = "已发布奖品", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(prizeOnlineService.removeBatchByIds(Arrays.asList(ids), true)); + } +} diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/domain/PrizeInfo.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/domain/PrizeInfo.java new file mode 100644 index 00000000..dff22788 --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/domain/PrizeInfo.java @@ -0,0 +1,79 @@ +package com.ruoyi.cai.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.io.Serializable; +import java.util.Date; +import java.math.BigDecimal; + +import java.math.BigDecimal; +import com.ruoyi.common.core.domain.BaseEntity; +import org.joda.time.LocalDateTime; + +/** + * 抽奖奖品对象 cai_prize_info + * + * @author 77 + * @date 2025-12-23 + */ +@Data +@TableName("cai_prize_info") +public class PrizeInfo implements Serializable { + + private static final long serialVersionUID=1L; + + /** + * 奖品ID + */ + @TableId(value = "id",type = IdType.AUTO) + private Long id; + /** + * 奖品名称 + */ + private String prizeName; + /** + * 奖品描述 + */ + private String prizeDesc; + /** + * 奖品图片地址 + */ + private String prizeImg; + /** + * 中奖率(0-1,如0.0100表示1%) + */ + private BigDecimal winProbability; + /** + * 保底抽数(0表示无保底,谢谢惠顾奖无效) + */ + private Long guaranteeDraws; + /** + * 最低中奖抽数(0表示无限制,谢谢惠顾奖无效) + */ + private Long minWinDraws; + /** + * 奖品库存(谢谢惠顾奖填0,不校验) + */ + private Long stock; + /** + * 奖品类型 + */ + private Long prizeType; + /** + * 奖品价值估算 + */ + private Long prizePrice; + /** + * 是否自动兑奖 + */ + private Boolean autoGive; + /** + * 是否为谢谢惠顾奖:0-否,1-是(全局仅一个) + */ + private Boolean thank; + + private LocalDateTime createTime; + private LocalDateTime updateTime; + +} diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/domain/PrizeOnline.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/domain/PrizeOnline.java new file mode 100644 index 00000000..9d30f897 --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/domain/PrizeOnline.java @@ -0,0 +1,87 @@ +package com.ruoyi.cai.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.io.Serializable; +import java.util.Date; +import java.math.BigDecimal; + +import java.math.BigDecimal; +import com.ruoyi.common.core.domain.BaseEntity; +import org.joda.time.LocalDateTime; + +/** + * 已发布奖品对象 cai_prize_online + * + * @author ruoyi + * @date 2025-12-23 + */ +@Data +@TableName("cai_prize_online") +public class PrizeOnline implements Serializable { + + private static final long serialVersionUID=1L; + + /** + * 奖品ID + */ + @TableId(value = "id",type = IdType.AUTO) + private Long id; + /** + * 奖品ID + */ + private Long prizeId; + /** + * 性别 1-女 2-男 + */ + private Integer gender; + /** + * 奖品名称 + */ + private String prizeName; + /** + * 奖品描述 + */ + private String prizeDesc; + /** + * 奖品图片地址 + */ + private String prizeImg; + /** + * 中奖率(0-1,如0.0100表示1%) + */ + private BigDecimal winProbability; + /** + * 保底抽数(0表示无保底,谢谢惠顾奖无效) + */ + private Long guaranteeDraws; + /** + * 最低中奖抽数(0表示无限制,谢谢惠顾奖无效) + */ + private Long minWinDraws; + /** + * 奖品库存(谢谢惠顾奖填0,不校验) + */ + private Long stock; + /** + * 奖品类型 + */ + private Long prizeType; + /** + * 奖品价值估算 + */ + private Long prizePrice; + /** + * 是否自动兑奖 + */ + private Boolean autoGive; + /** + * 是否为谢谢惠顾奖:0-否,1-是(全局仅一个) + */ + private Boolean thank; + + private LocalDateTime createTime; + private LocalDateTime updateTime; + +} diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/lottery/LotteryService.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/lottery/LotteryService.java new file mode 100644 index 00000000..7e7d447d --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/lottery/LotteryService.java @@ -0,0 +1,167 @@ +package com.ruoyi.cai.lottery; + +import com.lottery.entity.Prize; +import com.lottery.entity.UserDrawRecord; +import com.lottery.mapper.UserDrawRecordMapper; +import com.ruoyi.cai.domain.PrizeInfo; +import com.ruoyi.cai.service.PrizeOnlineService; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; + +@Service +public class LotteryService { + + private static final Long THANKS_PRIZE_ID = 1L; + private static final String USER_DRAW_COUNT_KEY = "user:draw:count:%s"; + // 每个用户的锁,防止并发抽奖(单机版用ReentrantLock) + private final ReentrantLock lock = new ReentrantLock(); + + @Autowired + private PrizeOnlineService prizeOnlineService; + @Autowired + private RedisTemplate redisTemplate; + + /** + * 用户抽奖 + */ + public PrizeInfo draw(Long userId) { + // 加锁防止并发抽奖 + lock.lock(); + try { + // 1. 获取用户累计抽数 + int continuousDraws = getContinuousDraws(userId); + int newContinuousDraws = continuousDraws + 1; + + // 2. 获取启用的奖品(排除谢谢惠顾) + List prizes = prizeService.getEnablePrizesExcludeThanks(); + if (prizes.isEmpty()) { + // 只有谢谢惠顾 + saveDrawRecord(userId, THANKS_PRIZE_ID, newContinuousDraws); + return prizeService.getPrizeFromCache(THANKS_PRIZE_ID); + } + + // 3. 处理抽奖规则(保底、最低中奖抽数、中奖率) + Prize winPrize = null; + boolean isGuarantee = false; + + // 3.1 检查保底规则 + for (Prize prize : prizes) { + if (prize.getGuaranteeDraws() > 0 && newContinuousDraws >= prize.getGuaranteeDraws()) { + // 触发保底,直接中该奖品 + if (prizeService.deductStock(prize.getId())) { + winPrize = prize; + isGuarantee = true; + break; + } + } + } + + // 3.2 未触发保底,按中奖率和最低中奖抽数规则抽奖 + if (winPrize == null) { + winPrize = randomDraw(prizes, newContinuousDraws); + } + + // 4. 处理中奖结果 + Long prizeId = winPrize != null ? winPrize.getId() : THANKS_PRIZE_ID; + int finalContinuousDraws = winPrize != null ? 0 : newContinuousDraws; // 中奖后重置累计抽数 + + // 5. 保存抽奖记录 + saveDrawRecord(userId, prizeId, finalContinuousDraws); + + // 6. 更新用户累计抽数缓存 + updateContinuousDrawsCache(userId, finalContinuousDraws); + + return prizeService.getPrizeFromCache(prizeId); + } finally { + lock.unlock(); + } + } + + /** + * 随机抽奖(处理中奖率和最低中奖抽数) + */ + private Prize randomDraw(List prizes, int continuousDraws) { + // 计算总中奖率 + double totalProbability = 0.0; + for (Prize prize : prizes) { + // 检查最低中奖抽数规则:未达到则该奖品中奖率为0 + if (continuousDraws < prize.getMinWinDraws()) { + continue; + } + totalProbability += prize.getWinProbability(); + } + + if (totalProbability <= 0) { + // 所有奖品都未达到最低中奖抽数,返回null(谢谢惠顾) + return null; + } + + // 随机数(0-总中奖率) + double random = new Random().nextDouble() * totalProbability; + double current = 0.0; + + for (Prize prize : prizes) { + if (continuousDraws < prize.getMinWinDraws()) { + continue; + } + current += prize.getWinProbability(); + if (random <= current) { + // 抽中该奖品,检查库存 + if (prizeService.deductStock(prize.getId())) { + return prize; + } else { + // 库存不足,重新抽奖(递归) + return randomDraw(prizes, continuousDraws); + } + } + } + + return null; + } + + /** + * 获取用户累计抽数(缓存+数据库) + */ + private int getContinuousDraws(Long userId) { + String key = String.format(USER_DRAW_COUNT_KEY, userId); + // 先从缓存获取 + Integer count = (Integer) redisTemplate.opsForValue().get(key); + if (count != null) { + return count; + } + // 从数据库查询 + count = userDrawRecordMapper.selectLastContinuousDraws(userId); + count = count == null ? 0 : count; + // 存入缓存 + redisTemplate.opsForValue().set(key, count); + return count; + } + + /** + * 更新用户累计抽数缓存 + */ + private void updateContinuousDrawsCache(Long userId, int count) { + String key = String.format(USER_DRAW_COUNT_KEY, userId); + redisTemplate.opsForValue().set(key, count); + } + + /** + * 保存抽奖记录 + */ + @Transactional(rollbackFor = Exception.class) + private void saveDrawRecord(Long userId, Long prizeId, int continuousDraws) { + UserDrawRecord record = new UserDrawRecord(); + record.setUserId(userId); + record.setPrizeId(prizeId); + record.setDrawTime(LocalDateTime.now()); + record.setContinuousDraws(continuousDraws); + userDrawRecordMapper.insert(record); + } +} diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/mapper/PrizeInfoMapper.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/mapper/PrizeInfoMapper.java new file mode 100644 index 00000000..4bf8e3e1 --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/mapper/PrizeInfoMapper.java @@ -0,0 +1,14 @@ +package com.ruoyi.cai.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.cai.domain.PrizeInfo; + +/** + * 抽奖奖品Mapper接口 + * + * @author 77 + * @date 2025-12-23 + */ +public interface PrizeInfoMapper extends BaseMapper { + +} diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/mapper/PrizeOnlineMapper.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/mapper/PrizeOnlineMapper.java new file mode 100644 index 00000000..8bb37b60 --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/mapper/PrizeOnlineMapper.java @@ -0,0 +1,14 @@ +package com.ruoyi.cai.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.cai.domain.PrizeOnline; + +/** + * 已发布奖品Mapper接口 + * + * @author ruoyi + * @date 2025-12-23 + */ +public interface PrizeOnlineMapper extends BaseMapper { + +} diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/service/PrizeInfoService.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/service/PrizeInfoService.java new file mode 100644 index 00000000..57a660a9 --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/service/PrizeInfoService.java @@ -0,0 +1,14 @@ +package com.ruoyi.cai.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.ruoyi.cai.domain.PrizeInfo; + +/** + * 抽奖奖品Service接口 + * + * @author 77 + * @date 2025-12-23 + */ +public interface PrizeInfoService extends IService { + +} diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/service/PrizeOnlineService.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/service/PrizeOnlineService.java new file mode 100644 index 00000000..7cbb2248 --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/service/PrizeOnlineService.java @@ -0,0 +1,14 @@ +package com.ruoyi.cai.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.ruoyi.cai.domain.PrizeOnline; + +/** + * 已发布奖品Service接口 + * + * @author ruoyi + * @date 2025-12-23 + */ +public interface PrizeOnlineService extends IService { + +} diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/service/impl/PrizeInfoServiceImpl.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/service/impl/PrizeInfoServiceImpl.java new file mode 100644 index 00000000..7acac043 --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/service/impl/PrizeInfoServiceImpl.java @@ -0,0 +1,20 @@ +package com.ruoyi.cai.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.ruoyi.cai.domain.PrizeInfo; +import com.ruoyi.cai.mapper.PrizeInfoMapper; +import com.ruoyi.cai.service.PrizeInfoService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 抽奖奖品Service业务层处理 + * + * @author 77 + * @date 2025-12-23 + */ +@RequiredArgsConstructor +@Service +public class PrizeInfoServiceImpl extends ServiceImpl implements PrizeInfoService { + +} diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/service/impl/PrizeOnlineServiceImpl.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/service/impl/PrizeOnlineServiceImpl.java new file mode 100644 index 00000000..caf7aa22 --- /dev/null +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/service/impl/PrizeOnlineServiceImpl.java @@ -0,0 +1,20 @@ +package com.ruoyi.cai.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.ruoyi.cai.domain.PrizeOnline; +import com.ruoyi.cai.mapper.PrizeOnlineMapper; +import com.ruoyi.cai.service.PrizeOnlineService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * 已发布奖品Service业务层处理 + * + * @author ruoyi + * @date 2025-12-23 + */ +@RequiredArgsConstructor +@Service +public class PrizeOnlineServiceImpl extends ServiceImpl implements PrizeOnlineService { + +} diff --git a/ruoyi-cai/src/main/java/com/ruoyi/cai/util/LoginLogByFileUtil.java b/ruoyi-cai/src/main/java/com/ruoyi/cai/util/LoginLogByFileUtil.java index b55c49eb..229cae1b 100644 --- a/ruoyi-cai/src/main/java/com/ruoyi/cai/util/LoginLogByFileUtil.java +++ b/ruoyi-cai/src/main/java/com/ruoyi/cai/util/LoginLogByFileUtil.java @@ -20,7 +20,7 @@ import java.util.stream.Collectors; @Slf4j public class LoginLogByFileUtil { private static final String LOG_FILE_PATH = "/home/server/api/logs/sys-console.log"; - private static final int COMMAND_TIMEOUT_MS = 5000; + private static final int COMMAND_TIMEOUT_MS = 9000; private static final Charset LOG_FILE_CHARSET = StandardCharsets.UTF_8; // 确认日志为UTF-8编码 private static final int MAX_LOG_LINES = 10; diff --git a/ruoyi-cai/src/main/resources/mapper/cai/PrizeInfoMapper.xml b/ruoyi-cai/src/main/resources/mapper/cai/PrizeInfoMapper.xml new file mode 100644 index 00000000..eeb5444d --- /dev/null +++ b/ruoyi-cai/src/main/resources/mapper/cai/PrizeInfoMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ruoyi-cai/src/main/resources/mapper/cai/PrizeOnlineMapper.xml b/ruoyi-cai/src/main/resources/mapper/cai/PrizeOnlineMapper.xml new file mode 100644 index 00000000..5f5f3958 --- /dev/null +++ b/ruoyi-cai/src/main/resources/mapper/cai/PrizeOnlineMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + +