Explorar o código

新增抽奖能力

程序员七七 hai 5 meses
pai
achega
8fefa4f078
Modificáronse 42 ficheiros con 4241 adicións e 10 borrados
  1. BIN=BIN
      .DS_Store
  2. 59 1
      sql/webchat.sql
  3. 130 0
      src/main/java/com/webchat/common/constants/LotteryConstants.java
  4. 46 0
      src/main/java/com/webchat/common/enums/RedisKeyEnum.java
  5. 122 0
      src/main/java/com/webchat/controller/admin/LotteryActivityController.java
  6. 57 0
      src/main/java/com/webchat/controller/admin/LotteryController.java
  7. 60 0
      src/main/java/com/webchat/controller/admin/LotteryItemController.java
  8. 90 0
      src/main/java/com/webchat/controller/admin/LotteryOrderController.java
  9. 34 0
      src/main/java/com/webchat/domain/vo/request/lottery/LotteryActivitySaveVO.java
  10. 24 0
      src/main/java/com/webchat/domain/vo/request/lottery/LotteryActivityStatusUpdateVO.java
  11. 54 0
      src/main/java/com/webchat/domain/vo/request/lottery/LotteryItemSaveVO.java
  12. 24 0
      src/main/java/com/webchat/domain/vo/request/lottery/LotteryOrderStatusUpdateVO.java
  13. 43 0
      src/main/java/com/webchat/domain/vo/request/lottery/LotterySponsorSaveVO.java
  14. 52 0
      src/main/java/com/webchat/domain/vo/response/lottery/LotteryActivityBaseVO.java
  15. 44 0
      src/main/java/com/webchat/domain/vo/response/lottery/LotteryActivityVO.java
  16. 62 0
      src/main/java/com/webchat/domain/vo/response/lottery/LotteryItemVO.java
  17. 43 0
      src/main/java/com/webchat/domain/vo/response/lottery/LotterySponsorVO.java
  18. 54 0
      src/main/java/com/webchat/domain/vo/response/lottery/LotteryUserOrderVO.java
  19. 1 1
      src/main/java/com/webchat/repository/dao/IUserDAO.java
  20. 38 0
      src/main/java/com/webchat/repository/dao/lottery/ILotteryActivityDAO.java
  21. 34 0
      src/main/java/com/webchat/repository/dao/lottery/ILotteryItemDAO.java
  22. 25 0
      src/main/java/com/webchat/repository/dao/lottery/ILotteryOrderDAO.java
  23. 60 0
      src/main/java/com/webchat/repository/entity/lottery/LotteryActivityEntity.java
  24. 64 0
      src/main/java/com/webchat/repository/entity/lottery/LotteryItemEntity.java
  25. 53 0
      src/main/java/com/webchat/repository/entity/lottery/LotteryOrderEntity.java
  26. 187 0
      src/main/java/com/webchat/service/lottery/LotteryActivityService.java
  27. 346 0
      src/main/java/com/webchat/service/lottery/LotteryCacheService.java
  28. 145 0
      src/main/java/com/webchat/service/lottery/LotteryItemService.java
  29. 326 0
      src/main/java/com/webchat/service/lottery/LotteryOrderService.java
  30. 208 0
      src/main/java/com/webchat/service/lottery/LotteryService.java
  31. 97 0
      src/main/java/com/webchat/service/queue/LotteryOrderQueue.java
  32. 28 0
      src/main/java/com/webchat/service/queue/dto/LotteryOrderQueueDTO.java
  33. 72 0
      src/main/resources/static/css/admin/console.css
  34. 7 0
      src/main/resources/static/css/common/common.css
  35. 26 7
      src/main/resources/templates/admin/console.html
  36. 0 1
      src/main/resources/templates/admin/default.html
  37. 291 0
      src/main/resources/templates/admin/lottery-activity.html
  38. 316 0
      src/main/resources/templates/admin/lottery-cms-item.html
  39. 274 0
      src/main/resources/templates/admin/lottery-order.html
  40. 99 0
      src/main/resources/templates/client/header.html
  41. 541 0
      src/main/resources/templates/client/lottery.html
  42. 5 0
      src/main/resources/templates/client/video-chat.html

BIN=BIN
.DS_Store


+ 59 - 1
sql/webchat.sql

@@ -287,4 +287,62 @@ INSERT INTO `webchat`.`web_chat_slide_verification`(`image`, `x`, `y`, `count`,
 INSERT INTO `webchat`.`web_chat_slide_verification`(`image`, `x`, `y`, `count`, `STATUS`, `CREATE_BY`, `CREATE_DATE`, `UPDATE_BY`, `UPDATE_DATE`, `VERSION`) VALUES ('/image/slide/file_5b9df5f2de52439dbc74c86a6f9e749d.jpeg', 0, 0, 0, 1, NULL, '2024-09-19 01:16:46', NULL, NULL, 1);
 INSERT INTO `webchat`.`web_chat_slide_verification`(`image`, `x`, `y`, `count`, `STATUS`, `CREATE_BY`, `CREATE_DATE`, `UPDATE_BY`, `UPDATE_DATE`, `VERSION`) VALUES ('/image/slide/file_cc3fc5dea48a43c2923f9e001b9f1b85.jpeg', 0, 0, 0, 1, NULL, '2024-09-19 01:16:51', NULL, NULL, 1);
 INSERT INTO `webchat`.`web_chat_slide_verification`(`image`, `x`, `y`, `count`, `STATUS`, `CREATE_BY`, `CREATE_DATE`, `UPDATE_BY`, `UPDATE_DATE`, `VERSION`) VALUES ('/image/slide/file_f36ec169377842dd8ec0c65a43619811.jpeg', 0, 0, 0, 1, NULL, '2024-09-19 01:16:56', NULL, NULL, 1);
-INSERT INTO `webchat`.`web_chat_slide_verification`(`image`, `x`, `y`, `count`, `STATUS`, `CREATE_BY`, `CREATE_DATE`, `UPDATE_BY`, `UPDATE_DATE`, `VERSION`) VALUES ('/image/slide/file_b44c3489577b4dfcaae9c09220ff677c.jpeg', 0, 0, 0, 1, NULL, '2024-09-19 01:17:00', NULL, NULL, 1);
+INSERT INTO `webchat`.`web_chat_slide_verification`(`image`, `x`, `y`, `count`, `STATUS`, `CREATE_BY`, `CREATE_DATE`, `UPDATE_BY`, `UPDATE_DATE`, `VERSION`) VALUES ('/image/slide/file_b44c3489577b4dfcaae9c09220ff677c.jpeg', 0, 0, 0, 1, NULL, '2024-09-19 01:17:00', NULL, NULL, 1);
+
+
+-- 抽奖活动配置表
+CREATE TABLE webchat.`web_chat_lottery_activity` (
+       `ID` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+       `ACTIVITY_ID` char(100) NOT NULL COMMENT '抽奖活动业务ID',
+       `NAME` varchar(100) NOT NULL COMMENT '活动名称',
+       `DESCRIPTION` varchar(500) NOT NULL COMMENT '活动描述',
+       `INTEGRAL` int(11) NOT NULL DEFAULT 0 COMMENT '每次抽奖需要消耗的元气值个数',
+       `COVER` varchar(300) NOT NULL COMMENT '活动封面图',
+       `STATUS` int(11) NOT NULL COMMENT '活动状态',
+       `CREATE_BY` char(100) DEFAULT NULL COMMENT '创建人',
+       `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+       `UPDATE_BY` char(100) DEFAULT NULL COMMENT '更新人',
+       `UPDATE_DATE` datetime DEFAULT NULL COMMENT '更新时间',
+       `VERSION` int DEFAULT '0' COMMENT '版本',
+       PRIMARY KEY (`ID`),
+       KEY `INDEX_ACTIVITY_ID_STATUS` (`ACTIVITY_ID`, `STATUS`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='抽奖活动配置表';
+
+-- 抽奖活动奖品表
+CREATE TABLE webchat.`web_chat_lottery_item` (
+       `ID` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+       `ACTIVITY_ID` char(100) NOT NULL COMMENT '抽奖活动业务ID',
+       `TYPE` int(11) NOT NULL COMMENT '奖品类型',
+       `NAME` varchar(100) NOT NULL COMMENT '奖品名称',
+       `SLOT` int(11) NOT NULL DEFAULT 1 COMMENT '奖品槽位1~8',
+       `ICON` varchar(300) NOT NULL COMMENT '奖品ICON',
+       `COVER` varchar(300) NOT NULL COMMENT '奖品列表显示封面图',
+       `STOCK` int(11) NOT NULL COMMENT '奖品库存',
+       `CREATE_BY` char(100) DEFAULT NULL COMMENT '创建人',
+       `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+       `UPDATE_BY` char(100) DEFAULT NULL COMMENT '更新人',
+       `UPDATE_DATE` datetime DEFAULT NULL COMMENT '更新时间',
+       `VERSION` int DEFAULT '0' COMMENT '版本',
+       PRIMARY KEY (`ID`),
+       KEY `INDEX_ACTIVITY_ID_SLOT_STOCK` (`ACTIVITY_ID`, `SLOT`, `STOCK`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='抽奖活动奖品表';
+
+-- 抽奖结果订单表
+CREATE TABLE webchat.`web_chat_lottery_order` (
+       `ID` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
+       `ORDER_ID` char(100) NOT NULL COMMENT '订单号',
+       `ACTIVITY_ID` char(100) NOT NULL COMMENT '抽奖活动业务ID',
+       `ITEM_ID` bigint NOT NULL COMMENT '中奖奖品ID',
+       `USER_ID` char(100) DEFAULT NULL COMMENT '中奖用户ID',
+       `STATUS` int(11) NOT NULL COMMENT '订单状态',
+       `CREATE_BY` char(100) DEFAULT NULL COMMENT '创建人',
+       `CREATE_DATE` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+       `UPDATE_BY` char(100) DEFAULT NULL COMMENT '更新人',
+       `UPDATE_DATE` datetime DEFAULT NULL COMMENT '更新时间',
+       `VERSION` int DEFAULT '0' COMMENT '版本',
+       PRIMARY KEY (`ID`),
+       KEY `INDEX_ORDER_ID` (`ORDER_ID`),
+       KEY `INDEX_USER_ID` (`USER_ID`),
+       KEY `INDEX_ITEM_ID` (`ITEM_ID`),
+       KEY `INDEX_ACTIVITY_ID_STATUS` (`ACTIVITY_ID`, `STATUS`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='抽奖结果订单表';

+ 130 - 0
src/main/java/com/webchat/common/constants/LotteryConstants.java

@@ -0,0 +1,130 @@
+package com.webchat.common.constants;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/17 00:14
+ * @description
+ */
+public class LotteryConstants {
+
+    /**
+     * 抽奖活动ID前缀 LotteryActivity: LA
+     */
+    public static final String LOTTERY_ACTIVITY_ID_PREFIX = "LA";
+    /**
+     * 抽奖订单活动ID前缀 LotteryOrder: LO
+     */
+    public static final String LOTTERY_ORDER_ID_PREFIX = "LO";
+    /**
+     * 活动奖品数量应该为8个
+     */
+    public static final int LOTTERY_ITEM_COUNT = 8;
+    /**
+     * 系统
+     */
+    public static final String SYSTEM = "SYSTEM";
+
+    /**
+     * 抽奖活动状态枚举
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum LotteryActivityStatus {
+
+        PREPARATION(1, "筹备中"),
+        RUNNING(2, "进行中"),
+        CLOSED(3, "已结束"),
+        DELETED(4, "已删除");
+
+        private Integer status;
+        private String statusName;
+    }
+
+    /**
+     * 抽奖活动状态枚举
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum LotteryOrderStatus {
+
+        UNCHANGED(1, "未兑换"),
+        EXCHANGED(2, "已兑换"),
+        DELETED(3, "已删除");
+
+        private Integer status;
+        private String statusName;
+    }
+
+    /**
+     * 抽奖奖品类型
+     */
+    @Getter
+    @AllArgsConstructor
+    public enum LotteryItemType {
+
+        INTEGRAL(1, "猿气值"),
+        GOODS(2, "实体商品"),
+        MONEY(3, "现金红包");
+
+        private Integer type;
+        private String typeName;
+    }
+
+    /**
+     * 根据活动状态获取活动中文名称
+     *
+     * @param status
+     * @return
+     */
+    public static String getLotteryActivityStatusName(Integer status) {
+        if (status == null) {
+            return "";
+        }
+        for (LotteryActivityStatus lotteryActivityStatus : LotteryActivityStatus.values()) {
+            if (lotteryActivityStatus.status.equals(status)) {
+                return lotteryActivityStatus.statusName;
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 根据活动状态获取活动中文名称
+     *
+     * @param type
+     * @return
+     */
+    public static String getLotteryActivityItemTypeName(Integer type) {
+        if (type == null) {
+            return "";
+        }
+        for (LotteryItemType lotteryItemType : LotteryItemType.values()) {
+            if (lotteryItemType.type.equals(type)) {
+                return lotteryItemType.typeName;
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 根据订单状态获取中文描述
+     *
+     * @param status
+     * @return
+     */
+    public static String getLotteryOrderStatusName(Integer status) {
+        if (status == null) {
+            return "";
+        }
+        for (LotteryOrderStatus lotteryOrderStatus : LotteryOrderStatus.values()) {
+            if (lotteryOrderStatus.status.equals(status)) {
+                return lotteryOrderStatus.statusName;
+            }
+        }
+        return "";
+    }
+}

+ 46 - 0
src/main/java/com/webchat/common/enums/RedisKeyEnum.java

@@ -206,6 +206,52 @@ public enum RedisKeyEnum {
     SLIDE_VERIFICATION_TOKEN_CACHE("SLIDE_VERIFICATION_TOKEN_CACHE", 60L),
     SLIDE_VERIFICATION_TOKEN_VALIDATE_RESULT_CACHE("SLIDE_VERIFICATION_TOKEN_VALIDATE_RESULT_CACHE", 60L),
 
+    /**
+     * 抽奖活动缓存
+     */
+    LOTTERY_ACTIVITY_DETAIL_CACHE("LOTTERY_ACTIVITY_DETAIL_CACHE", 30 * 24 * 60 * 60L),
+
+    /**
+     * 历史抽奖活动缓存
+     */
+    LOTTERY_ACTIVITY_HISTORY_CACHE("LOTTERY_ACTIVITY_HISTORY_CACHE", -1L),
+
+    /**
+     * 最近一期活动ID缓存
+     */
+    LOTTERY_ACTIVITY_LAST_ID_CACHE("LOTTERY_ACTIVITY_LAST_ID_CACHE", -1L),
+
+    /**
+     * 抽奖活动奖品余量缓存
+     */
+    LOTTERY_ACTIVITY_ITEM_STOCK_CACHE("LOTTERY_ACTIVITY_ITEM_STOCK_CACHE", -1L),
+
+    /**
+     * 抽奖商品详情缓存
+     */
+    LOTTERY_ACTIVITY_ITEM_DETAIL_CACHE("LOTTERY_ACTIVITY_ITEM_DETAIL_CACHE", 30 * 24 * 60 * 60L),
+
+    /**
+     * 中奖结果缓存
+     */
+    LOTTERY_ACTIVITY_ORDER_CACHE("LOTTERY_ACTIVITY_ORDER_CACHE", 30 * 24 * 60 * 60L),
+
+    /**
+     * 订单详情缓存
+     */
+    LOTTERY_ACTIVITY_ORDER_DETAIL_CACHE("LOTTERY_ACTIVITY_ORDER_DETAIL_CACHE", 30 * 24 * 60 * 60L),
+
+    /**
+     * 用户中奖结果缓存
+     */
+    LOTTERY_ACTIVITY_USER_ORDER_CACHE("LOTTERY_ACTIVITY_USER_ORDER_CACHE", 30 * 24 * 60 * 60L),
+
+    /**
+     * 抽奖结果订单
+     */
+    QUEUE_LOTTERY_ORDER_MESSAGE("QUEUE_LOTTERY_ORDER_MESSAGE", -1L),
+
+
     ;
 
 

+ 122 - 0
src/main/java/com/webchat/controller/admin/LotteryActivityController.java

@@ -0,0 +1,122 @@
+package com.webchat.controller.admin;
+
+import com.webchat.common.bean.APIPageResponseBean;
+import com.webchat.common.bean.APIResponseBean;
+import com.webchat.common.bean.APIResponseBeanUtil;
+import com.webchat.common.helper.SessionHelper;
+import com.webchat.config.annotation.ValidatePermission;
+import com.webchat.domain.vo.request.lottery.LotteryActivitySaveVO;
+import com.webchat.domain.vo.request.lottery.LotteryActivityStatusUpdateVO;
+import com.webchat.domain.vo.response.lottery.LotteryActivityBaseVO;
+import com.webchat.domain.vo.response.lottery.LotteryActivityVO;
+import com.webchat.service.lottery.LotteryActivityService;
+import io.swagger.annotations.Api;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 23:40
+ * @description
+ */
+@Api(tags = "【幸运抽奖】抽奖活动")
+@RestController
+@RequestMapping("/api/lottery/activity")
+public class LotteryActivityController {
+
+    @Autowired
+    private LotteryActivityService lotteryActivityService;
+
+    /**
+     * 创建或更新抽奖活动
+     *
+     * @return
+     */
+    @ValidatePermission
+    @PostMapping("/save")
+    public APIResponseBean<Long> save(@RequestBody LotteryActivitySaveVO lotteryActivityVO) {
+        String userId = SessionHelper.getCurrentUserId();
+        return APIResponseBeanUtil.success(lotteryActivityService.save(lotteryActivityVO, userId));
+    }
+
+    /**
+     * 更新活动状态
+     *
+     * @param lotteryActivityStatusUpdate
+     * @return
+     */
+    @ValidatePermission
+    @PostMapping("/updateStatus")
+    public APIResponseBean<Boolean> updateStatus(@RequestBody LotteryActivityStatusUpdateVO lotteryActivityStatusUpdate) {
+        String userId = SessionHelper.getCurrentUserId();
+        lotteryActivityService.updateLotteryActivityStatus(lotteryActivityStatusUpdate, userId);
+        return APIResponseBeanUtil.success(true);
+    }
+
+    /**
+     * 抽奖活动详情
+     *
+     * @param lotteryActivityId
+     * @return
+     */
+    @GetMapping("/detail/{lotteryActivityId}")
+    public APIResponseBean<LotteryActivityVO> detail(@PathVariable String lotteryActivityId) {
+        return APIResponseBeanUtil.success(lotteryActivityService.getLotteryActivityDetailFromCache(lotteryActivityId));
+    }
+
+    /**
+     * 最新一期活动信息
+     *
+     * @return
+     */
+    @GetMapping("/last")
+    public APIResponseBean<LotteryActivityVO> last() {
+        return APIResponseBeanUtil.success(lotteryActivityService.getLastLotteryActivityBaseFromCache());
+    }
+
+    /**
+     * 查询最新一期活动
+     *
+     * @return
+     */
+    @GetMapping("/lastId")
+    public APIResponseBean<String> lastId() {
+        return APIResponseBeanUtil.success(lotteryActivityService.getLastLotteryActivityIdFromCache());
+    }
+
+    /**
+     * 历史列表查询
+     *
+     * @param lastId
+     * @param size
+     * @return
+     */
+    @GetMapping("/history/list")
+    public APIResponseBean<List<LotteryActivityVO>> queryHistory(@RequestParam(value = "lastId", required = false) Long lastId,
+                                                                 @RequestParam(value = "lastId", required = false, defaultValue = "8") Integer size) {
+
+
+        return APIResponseBeanUtil.success(lotteryActivityService.getHistoryActivityList(lastId, size));
+    }
+
+    /**
+     * 活动列表检索
+     * @param name
+     * @param status
+     * @param pageNo
+     * @param pageSize
+     * @return
+     */
+    @ValidatePermission
+    @GetMapping("/page")
+    public APIPageResponseBean<List<LotteryActivityBaseVO>> page(@RequestParam(value = "name", required = false) String name,
+                                                                 @RequestParam(value = "status", required = false) Integer status,
+                                                                 @RequestParam(value = "pageNo", required = false, defaultValue = "1") Integer pageNo,
+                                                                 @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize) {
+
+        return lotteryActivityService.pageList(name, status, pageNo, pageSize);
+    }
+}

+ 57 - 0
src/main/java/com/webchat/controller/admin/LotteryController.java

@@ -0,0 +1,57 @@
+package com.webchat.controller.admin;
+import com.webchat.common.bean.APIResponseBean;
+import com.webchat.common.bean.APIResponseBeanUtil;
+import com.webchat.common.enums.ClickEvent;
+import com.webchat.common.helper.SessionHelper;
+import com.webchat.config.annotation.SafeClick;
+import com.webchat.config.annotation.ValidateLogin;
+import com.webchat.service.lottery.LotteryService;
+import io.swagger.annotations.Api;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 23:40
+ * @description
+ */
+@Api(tags = "【幸运抽奖】抽奖服务")
+@RestController
+@RequestMapping("/api/lottery")
+public class LotteryController {
+
+    @Autowired
+    private LotteryService lotteryService;
+
+    /**
+     * 抽奖
+     * @return
+     */
+    @ValidateLogin
+    @SafeClick(event = ClickEvent.SUBMIT, time = 5000L, message = "异常操作多次后账号将会永久拉黑")
+    @PostMapping("/luckDraw/{lotteryActivityId}")
+    public APIResponseBean<Long> luckDraw(@PathVariable String lotteryActivityId) {
+        String userId = SessionHelper.getCurrentUserId();
+        return APIResponseBeanUtil.success(lotteryService.luckDraw(lotteryActivityId, userId));
+    }
+
+    /**
+     * 抽奖
+     * @return
+     */
+    @ValidateLogin
+    @SafeClick(event = ClickEvent.SUBMIT, time = 5000L, message = "异常操作多次后账号将会永久拉黑")
+    @PostMapping("/luckDraw/{lotteryActivityId}/10")
+    public APIResponseBean<List<Long>> luckDraw10Times(@PathVariable String lotteryActivityId) {
+        String userId = SessionHelper.getCurrentUserId();
+        List<Long> batchLuckDrawResult = lotteryService.luckDraw(lotteryActivityId, userId, 10);
+        return APIResponseBeanUtil.success(batchLuckDrawResult);
+    }
+
+}

+ 60 - 0
src/main/java/com/webchat/controller/admin/LotteryItemController.java

@@ -0,0 +1,60 @@
+package com.webchat.controller.admin;
+
+import com.webchat.common.bean.APIPageResponseBean;
+import com.webchat.common.bean.APIResponseBean;
+import com.webchat.common.bean.APIResponseBeanUtil;
+import com.webchat.common.helper.SessionHelper;
+import com.webchat.config.annotation.ValidatePermission;
+import com.webchat.domain.vo.request.lottery.LotteryItemSaveVO;
+import com.webchat.domain.vo.response.lottery.LotteryItemVO;
+import com.webchat.service.lottery.LotteryItemService;
+import io.swagger.annotations.Api;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 23:40
+ * @description
+ */
+@Api(tags = "【幸运抽奖】奖品服务")
+@RestController
+@RequestMapping("/api/lottery/item")
+public class LotteryItemController {
+
+    @Autowired
+    private LotteryItemService lotteryItemService;
+
+    /**
+     * 新建或更新奖品信息
+     * @return
+     */
+    @ValidatePermission
+    @PostMapping("/save")
+    public APIResponseBean<Long> save(@RequestBody LotteryItemSaveVO lotteryItemSaveVO) {
+        String userId = SessionHelper.getCurrentUserId();
+        return APIResponseBeanUtil.success(lotteryItemService.save(lotteryItemSaveVO, userId));
+    }
+
+    /**
+     * 商品列表查询
+     * @param name
+     * @param slot
+     * @param activityId
+     * @param pageNo
+     * @param pageSize
+     * @return
+     */
+    @ValidatePermission
+    @GetMapping("/page")
+    public APIPageResponseBean<List<LotteryItemVO>> pageList(@RequestParam(value = "activityId", required = false) String activityId,
+                                                             @RequestParam(value = "name", required = false) String name,
+                                                             @RequestParam(value = "slot", required = false) Integer slot,
+                                                             @RequestParam(value = "pageNo", required = false, defaultValue = "1") Integer pageNo,
+                                                             @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize) {
+        return lotteryItemService.pageList(activityId, name, slot, pageNo, pageSize);
+    }
+}

+ 90 - 0
src/main/java/com/webchat/controller/admin/LotteryOrderController.java

@@ -0,0 +1,90 @@
+package com.webchat.controller.admin;
+
+import com.webchat.common.bean.APIPageResponseBean;
+import com.webchat.common.bean.APIResponseBean;
+import com.webchat.common.bean.APIResponseBeanUtil;
+import com.webchat.common.helper.SessionHelper;
+import com.webchat.config.annotation.ValidatePermission;
+import com.webchat.domain.vo.request.lottery.LotteryOrderStatusUpdateVO;
+import com.webchat.domain.vo.response.lottery.LotteryUserOrderVO;
+import com.webchat.service.lottery.LotteryOrderService;
+import io.swagger.annotations.Api;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 23:40
+ * @description
+ */
+@Api(tags = "【幸运抽奖】订单服务")
+@RestController
+@RequestMapping("/api/lottery/order")
+public class LotteryOrderController {
+
+    @Autowired
+    private LotteryOrderService lotteryOrderService;
+
+
+    /**
+     * 查询当前登录用户中奖订单
+     *
+     * @return
+     */
+    @GetMapping("/byCurrUser/{activityId}")
+    public APIResponseBean<List<LotteryUserOrderVO>> listUserOrder(@PathVariable String activityId) {
+        String userId = SessionHelper.getCurrentUserId();
+        if (StringUtils.isBlank(userId)) {
+            return APIResponseBeanUtil.success(Collections.emptyList());
+        }
+        return APIResponseBeanUtil.success(lotteryOrderService.queryActivityUserOrder(activityId, userId, Integer.MAX_VALUE));
+    }
+
+    /**
+     * 查询活动下的TOP50中奖订单记录
+     *
+     * @param activityId
+     * @return
+     */
+    @GetMapping("/byActivity/{activityId}")
+    public APIResponseBean<List<LotteryUserOrderVO>> listActivityOrder(@PathVariable String activityId) {
+        String userId = SessionHelper.getCurrentUserId();
+        if (StringUtils.isBlank(userId)) {
+            return APIResponseBeanUtil.success(Collections.emptyList());
+        }
+        return APIResponseBeanUtil.success(lotteryOrderService.queryActivityOrder(activityId, 100));
+    }
+
+    /**
+     * 订单详情翻页
+     * @return
+     */
+    @ValidatePermission
+    @GetMapping("/page")
+    public APIPageResponseBean<List<LotteryUserOrderVO>> pageList(@RequestParam(value = "activityId", required = false) String activityId,
+                                                                  @RequestParam(value = "orderId", required = false) String orderId,
+                                                                  @RequestParam(value = "status", required = false) Integer status,
+                                                                  @RequestParam(value = "pageNo", required = false, defaultValue = "1") Integer pageNo,
+                                                                  @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize) {
+        return lotteryOrderService.pageList(activityId, orderId, status, pageNo, pageSize);
+    }
+
+    /**
+     * 更新活动状态
+     *
+     * @param lotteryOrderStatusUpdate
+     * @return
+     */
+    @ValidatePermission
+    @PostMapping("/updateStatus")
+    public APIResponseBean<Boolean> updateStatus(@RequestBody LotteryOrderStatusUpdateVO lotteryOrderStatusUpdate) {
+        String userId = SessionHelper.getCurrentUserId();
+        lotteryOrderService.updateLotteryOrderStatus(lotteryOrderStatusUpdate, userId);
+        return APIResponseBeanUtil.success(true);
+    }
+}

+ 34 - 0
src/main/java/com/webchat/domain/vo/request/lottery/LotteryActivitySaveVO.java

@@ -0,0 +1,34 @@
+package com.webchat.domain.vo.request.lottery;
+
+import lombok.Data;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 22:53
+ * @description
+ */
+@Data
+public class LotteryActivitySaveVO {
+
+    /**
+     * 活动id
+     */
+    private Long id;
+
+    /**
+     * 活动名称
+     */
+    private String name;
+
+    /**
+     * 活动描述
+     */
+    private String description;
+
+    /**
+     * 活动海报图
+     */
+    private String cover;
+
+}

+ 24 - 0
src/main/java/com/webchat/domain/vo/request/lottery/LotteryActivityStatusUpdateVO.java

@@ -0,0 +1,24 @@
+package com.webchat.domain.vo.request.lottery;
+
+import lombok.Data;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/17 23:15
+ * @description
+ */
+@Data
+public class LotteryActivityStatusUpdateVO {
+
+    /**
+     * 活动id
+     */
+    private String activityId;
+
+    /**
+     * 更新抽奖活动状态
+     * @see com.coder.common.constants.LotteryConstants.LotteryActivityStatus
+     */
+    private Integer status;
+}

+ 54 - 0
src/main/java/com/webchat/domain/vo/request/lottery/LotteryItemSaveVO.java

@@ -0,0 +1,54 @@
+package com.webchat.domain.vo.request.lottery;
+
+import lombok.Data;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 22:53
+ * @description
+ */
+@Data
+public class LotteryItemSaveVO {
+
+    /**
+     * 活动id
+     */
+    private Long id;
+
+    /**
+     * 关联活动ID
+     */
+    private String activityId;
+
+    /**
+     * 商品类型
+     * @see com.coder.common.constants.LotteryConstants.LotteryItemType
+     */
+    private Integer type;
+
+    /**
+     * 奖品名称
+     */
+    private String name;
+
+    /**
+     * 奖品icon
+     */
+    private String icon;
+
+    /**
+     * 奖品封面图
+     */
+    private String cover;
+
+    /**
+     * 卡槽 1,2,3 ...... 8
+     */
+    private Integer slot;
+
+    /**
+     * 库存数量
+     */
+    private Integer stock;
+}

+ 24 - 0
src/main/java/com/webchat/domain/vo/request/lottery/LotteryOrderStatusUpdateVO.java

@@ -0,0 +1,24 @@
+package com.webchat.domain.vo.request.lottery;
+
+import lombok.Data;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/17 23:15
+ * @description
+ */
+@Data
+public class LotteryOrderStatusUpdateVO {
+
+    /**
+     * 活动id
+     */
+    private String orderId;
+
+    /**
+     * 更新抽奖活动状态
+     * @see com.coder.common.constants.LotteryConstants.LotteryOrderStatus
+     */
+    private Integer status;
+}

+ 43 - 0
src/main/java/com/webchat/domain/vo/request/lottery/LotterySponsorSaveVO.java

@@ -0,0 +1,43 @@
+package com.webchat.domain.vo.request.lottery;
+
+import lombok.Data;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 22:53
+ * @description 抽奖活动赞助商配置
+ */
+@Data
+public class LotterySponsorSaveVO {
+
+    /**
+     * 活动id
+     */
+    private Long id;
+
+    /**
+     * 关联活动ID
+     */
+    private String activityId;
+
+    /**
+     * 赞助商logo
+     */
+    private String logo;
+
+    /**
+     * 赞助推广链接
+     */
+    private String url;
+
+    /**
+     * 赞助话术
+     */
+    private String content;
+
+    /**
+     * 评分
+     */
+    private Integer score;
+}

+ 52 - 0
src/main/java/com/webchat/domain/vo/response/lottery/LotteryActivityBaseVO.java

@@ -0,0 +1,52 @@
+package com.webchat.domain.vo.response.lottery;
+
+import lombok.Data;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/18 01:37
+ * @description
+ */
+@Data
+public class LotteryActivityBaseVO {
+
+    private Long id;
+
+    /**
+     * 活动id
+     */
+    private String activityId;
+
+    /**
+     * 活动名称
+     */
+    private String name;
+
+    /**
+     * 活动描述
+     */
+    private String description;
+
+    /**
+     * 每抽奖一次需要消耗的元气值
+     */
+    private Integer integral;
+
+    /**
+     * 活动海报图
+     */
+    private String cover;
+
+    /**
+     * 活动当前状态
+     */
+    private Integer status;
+
+    /**
+     * 状态名称
+     */
+    private String statusName;
+
+    private Long createTime;
+}

+ 44 - 0
src/main/java/com/webchat/domain/vo/response/lottery/LotteryActivityVO.java

@@ -0,0 +1,44 @@
+package com.webchat.domain.vo.response.lottery;
+
+import com.webchat.common.constants.LotteryConstants;
+import com.webchat.common.exception.BusinessException;
+import lombok.Data;
+import org.springframework.util.Assert;
+
+import java.util.List;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/17 01:01
+ * @description
+ */
+@Data
+public class LotteryActivityVO extends LotteryActivityBaseVO {
+
+    /**
+     * 抽奖选项
+     */
+    private List<LotteryItemVO> items;
+
+
+    public void validateIntegral() {
+        Assert.isTrue(getIntegral() != null && getIntegral() > 0, "抽奖积分配置异常!");
+    }
+
+    public void validateRunningStatus() {
+        Integer status = getStatus();
+        if (LotteryConstants.LotteryActivityStatus.RUNNING.getStatus().equals(status)) {
+            return;
+        }
+        if (LotteryConstants.LotteryActivityStatus.PREPARATION.getStatus().equals(status)) {
+            throw new BusinessException("活动筹备中!");
+        }
+        if (LotteryConstants.LotteryActivityStatus.CLOSED.getStatus().equals(status)) {
+            throw new BusinessException("活动已结束!");
+        }
+        if (LotteryConstants.LotteryActivityStatus.DELETED.getStatus().equals(status)) {
+            throw new BusinessException("活动已经下线!");
+        }
+    }
+}

+ 62 - 0
src/main/java/com/webchat/domain/vo/response/lottery/LotteryItemVO.java

@@ -0,0 +1,62 @@
+package com.webchat.domain.vo.response.lottery;
+
+import com.webchat.common.constants.LotteryConstants;
+import lombok.Data;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/17 01:01
+ * @description
+ */
+@Data
+public class LotteryItemVO {
+
+    /**
+     * 活动id
+     */
+    private Long id;
+
+    /**
+     * 关联活动ID
+     */
+    private String activityId;
+
+    /**
+     * 商品类型
+     */
+    private Integer type;
+
+    private String typeName;
+
+    /**
+     * 奖品名称
+     */
+    private String name;
+
+    /**
+     * 奖品icon
+     */
+    private String icon;
+
+    /**
+     * 奖品封面图
+     */
+    private String cover;
+
+    /**
+     * 卡槽 1,2,3 ...... 8
+     */
+    private Integer slot;
+
+    /**
+     * 库存数量
+     */
+    private Integer stock;
+
+    private Long createTime;
+
+    public String getTypeName() {
+        return LotteryConstants.getLotteryActivityItemTypeName(type);
+    }
+}

+ 43 - 0
src/main/java/com/webchat/domain/vo/response/lottery/LotterySponsorVO.java

@@ -0,0 +1,43 @@
+package com.webchat.domain.vo.response.lottery;
+
+import lombok.Data;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/18 01:36
+ * @description
+ */
+@Data
+public class LotterySponsorVO {
+
+    /**
+     * 活动id
+     */
+    private Long id;
+
+    /**
+     * 关联活动ID
+     */
+    private String activityId;
+
+    /**
+     * 赞助商logo
+     */
+    private String logo;
+
+    /**
+     * 赞助推广链接
+     */
+    private String url;
+
+    /**
+     * 赞助话术
+     */
+    private String content;
+
+    /**
+     * 评分
+     */
+    private Integer score;
+}

+ 54 - 0
src/main/java/com/webchat/domain/vo/response/lottery/LotteryUserOrderVO.java

@@ -0,0 +1,54 @@
+package com.webchat.domain.vo.response.lottery;
+
+import com.webchat.domain.vo.response.UserBaseResponseInfoVO;
+import lombok.Data;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/19 10:58
+ * @description
+ */
+@Data
+public class LotteryUserOrderVO {
+
+    /**
+     * 订单号
+     */
+    private String orderId;
+
+    /**
+     * 中奖用户
+     */
+    private UserBaseResponseInfoVO user;
+
+    /**
+     * 活动
+     */
+    private LotteryActivityBaseVO activity;
+
+    /**
+     * 中奖商品
+     */
+    private LotteryItemVO item;
+
+    /**
+     * 订单状态code
+     */
+    private Integer orderStatus;
+
+    /**
+     * 订单状态
+     */
+    private String orderStatusName;
+
+    /**
+     * 中奖时间
+     */
+    private long luckTime;
+
+    /**
+     * 兑换时间
+     */
+    private long exchangeTime;
+}

+ 1 - 1
src/main/java/com/webchat/repository/dao/IUserDAO.java

@@ -40,7 +40,7 @@ public interface IUserDAO extends JpaSpecificationExecutor<UserEntity>, JpaRepos
      */
     UserEntity findByMobileAndPassword(String mobile, String password);
 
-    @Query(value = "select count(*) from coder_util_user t where t.create_date = now()", nativeQuery = true)
+    @Query(value = "select count(*) from web_chat_user t where t.create_date = now()", nativeQuery = true)
     Long todayCount();
 
     @Query(value = "select u from UserEntity u where (u.createDate between ?1 and ?2) or (u.updateDate between ?1 and ?2)")

+ 38 - 0
src/main/java/com/webchat/repository/dao/lottery/ILotteryActivityDAO.java

@@ -0,0 +1,38 @@
+package com.webchat.repository.dao.lottery;
+
+import com.webchat.repository.entity.lottery.LotteryActivityEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 23:49
+ * @description
+ */
+@Repository
+public interface ILotteryActivityDAO extends JpaSpecificationExecutor<LotteryActivityEntity>,
+        JpaRepository<LotteryActivityEntity, Long> {
+
+    /**
+     * 根据活动id查询活动详情数据
+     * @param activityId
+     * @return
+     */
+    LotteryActivityEntity findByActivityId(String activityId);
+
+    /**
+     * 查询历史所有抽奖活动
+     * @param status
+     * @return
+     */
+    List<LotteryActivityEntity> findAllByStatusNotOrderByCreateDateDesc(Integer status);
+
+    @Query(value = "select a.activity_id from web_chat_lottery_activity a where a.status != 4 order by a. id desc limit 1",
+            nativeQuery = true)
+    String findByLastLotteryActivity();
+}

+ 34 - 0
src/main/java/com/webchat/repository/dao/lottery/ILotteryItemDAO.java

@@ -0,0 +1,34 @@
+package com.webchat.repository.dao.lottery;
+
+import com.webchat.repository.entity.lottery.LotteryItemEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 23:49
+ * @description
+ */
+@Repository
+public interface ILotteryItemDAO extends JpaSpecificationExecutor<LotteryItemEntity>,
+        JpaRepository<LotteryItemEntity, Long> {
+
+    /**
+     * 根据活动id查询奖品列表
+     * @param activityId
+     * @return
+     */
+    List<LotteryItemEntity> findAllByActivityIdOrderBySlotAsc(String activityId);
+
+    /**
+     * 查询活动下奖品数量
+     * @param activityId
+     * @return
+     */
+    Long countByActivityId(String activityId);
+    Long countByActivityIdAndSlot(String activityId, int slot);
+}

+ 25 - 0
src/main/java/com/webchat/repository/dao/lottery/ILotteryOrderDAO.java

@@ -0,0 +1,25 @@
+package com.webchat.repository.dao.lottery;
+
+import com.webchat.repository.entity.lottery.LotteryOrderEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 23:49
+ * @description
+ */
+@Repository
+public interface ILotteryOrderDAO extends JpaSpecificationExecutor<LotteryOrderEntity>,
+        JpaRepository<LotteryOrderEntity, Long> {
+
+    /**
+     * 根据订单id查询订单详情
+     * @param orderId
+     * @return
+     */
+    LotteryOrderEntity findByOrderId(String orderId);
+
+}

+ 60 - 0
src/main/java/com/webchat/repository/entity/lottery/LotteryActivityEntity.java

@@ -0,0 +1,60 @@
+package com.webchat.repository.entity.lottery;
+
+import com.webchat.repository.entity.BaseEntity;
+import lombok.Data;
+
+import javax.persistence.*;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 23:46
+ * @description
+ */
+@Data
+@Entity
+@Table(name = "web_chat_lottery_activity")
+public class LotteryActivityEntity extends BaseEntity {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    protected Long id;
+
+    /**
+     * 活动id
+     */
+    @Column(name = "activity_id")
+    private String activityId;
+
+    /**
+     * 活动名称
+     */
+    @Column(name = "name")
+    private String name;
+
+    /**
+     * 活动描述
+     */
+    @Column(name = "description")
+    private String description;
+
+    /**
+     * 每抽奖一次需要消耗的元气值
+     */
+    @Column(name = "integral")
+    private Integer integral;
+
+    /**
+     * 活动海报图
+     */
+    @Column(name = "cover")
+    private String cover;
+
+    /**
+     * 活动状态
+     * @see LotteryConstants.LotteryActivityStatus
+     */
+    @Column(name = "status")
+    private Integer status;
+
+}

+ 64 - 0
src/main/java/com/webchat/repository/entity/lottery/LotteryItemEntity.java

@@ -0,0 +1,64 @@
+package com.webchat.repository.entity.lottery;
+
+import com.webchat.repository.entity.BaseEntity;
+import lombok.Data;
+
+import javax.persistence.*;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 23:46
+ * @description
+ */
+@Data
+@Entity
+@Table(name = "web_chat_lottery_item")
+public class LotteryItemEntity extends BaseEntity {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    protected Long id;
+
+    /**
+     * 活动ID
+     */
+    @Column(name = "activity_id")
+    private String activityId;
+
+    /**
+     * 奖品类型
+     */
+    @Column(name = "type")
+    private Integer type;
+
+    /**
+     * 奖品名称
+     */
+    @Column(name = "name")
+    private String name;
+
+    /**
+     * 奖品icon
+     */
+    @Column(name = "icon")
+    private String icon;
+
+    /**
+     * 奖品封面图
+     */
+    @Column(name = "cover")
+    private String cover;
+
+    /**
+     * 卡槽 1,2,3 ...... 8
+     */
+    @Column(name = "slot")
+    private Integer slot;
+
+    /**
+     * 库存数量
+     */
+    @Column(name = "stock")
+    private Integer stock;
+}

+ 53 - 0
src/main/java/com/webchat/repository/entity/lottery/LotteryOrderEntity.java

@@ -0,0 +1,53 @@
+package com.webchat.repository.entity.lottery;
+
+import com.webchat.repository.entity.BaseEntity;
+import lombok.Data;
+
+import javax.persistence.*;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 23:46
+ * @description
+ */
+@Data
+@Entity
+@Table(name = "web_chat_lottery_order")
+public class LotteryOrderEntity extends BaseEntity {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    protected Long id;
+
+    /**
+     * 中奖订单id
+     */
+    @Column(name = "order_id")
+    private String orderId;
+
+    /**
+     * 活动ID
+     */
+    @Column(name = "activity_id")
+    private String activityId;
+
+    /**
+     * 中奖用户id
+     */
+    @Column(name = "user_id")
+    private String userId;
+
+    /**
+     * 中奖奖品id
+     */
+    @Column(name = "item_id")
+    private Long itemId;
+
+    /**
+     * 订单状态
+     * @see LotteryConstants.LotteryOrderStatus
+     */
+    @Column(name = "status")
+    private Integer status;
+}

+ 187 - 0
src/main/java/com/webchat/service/lottery/LotteryActivityService.java

@@ -0,0 +1,187 @@
+package com.webchat.service.lottery;
+
+import com.webchat.common.bean.APIPageResponseBean;
+import com.webchat.common.constants.LotteryConstants;
+import com.webchat.common.util.IDGenerateUtil;
+import com.webchat.domain.vo.request.lottery.LotteryActivitySaveVO;
+import com.webchat.domain.vo.request.lottery.LotteryActivityStatusUpdateVO;
+import com.webchat.domain.vo.response.lottery.LotteryActivityBaseVO;
+import com.webchat.domain.vo.response.lottery.LotteryActivityVO;
+import com.webchat.repository.dao.lottery.ILotteryActivityDAO;
+import com.webchat.repository.entity.lottery.LotteryActivityEntity;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+
+import javax.persistence.criteria.Predicate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 23:40
+ * @description
+ */
+@Slf4j
+@Service
+public class LotteryActivityService {
+
+    @Autowired
+    private LotteryItemService lotteryItemService;
+    @Autowired
+    private ILotteryActivityDAO lotteryActivityDAO;
+    @Autowired
+    private LotteryCacheService lotteryCacheService;
+
+    /**
+     * 创建或更新抽奖活动
+     *
+     * @param lotteryActivityVO
+     * @param userId
+     * @return
+     */
+    public String save(LotteryActivitySaveVO lotteryActivityVO, String userId) {
+        LotteryActivityEntity entity = this.convert(lotteryActivityVO, userId);
+        // 保存抽奖活动信息入库
+        entity = lotteryActivityDAO.save(entity);
+        // 刷新抽奖活动缓存
+        lotteryCacheService.refreshLotteryActivityCacheByEntity(entity);
+        return entity.getActivityId();
+    }
+
+    /**
+     * 更新抽奖活动状态
+     *
+     * @param lotteryActivityStatusUpdate
+     * @param userId
+     */
+    public void updateLotteryActivityStatus(LotteryActivityStatusUpdateVO lotteryActivityStatusUpdate, String userId) {
+        String activityId = lotteryActivityStatusUpdate.getActivityId();
+        Integer updateStatus = lotteryActivityStatusUpdate.getStatus();
+        if (LotteryConstants.LotteryActivityStatus.RUNNING.getStatus().equals(updateStatus)) {
+            // 发布活动(进行中): 校验活动的奖品数量
+            lotteryItemService.validateLotteryItemCount(activityId);
+        }
+        LotteryActivityEntity lotteryActivityEntity = lotteryActivityDAO.findByActivityId(activityId);
+        lotteryActivityEntity.setStatus(updateStatus);
+        lotteryActivityEntity.setUpdateBy(userId);
+        lotteryActivityEntity.setUpdateDate(new Date());
+        lotteryActivityDAO.save(lotteryActivityEntity);
+        // 刷新抽奖活动缓存
+        lotteryCacheService.refreshLotteryActivityCacheByEntity(lotteryActivityEntity);
+        log.info("【抽奖活动状态变更】{} 修改抽奖活动 {} 状态为: {}", userId, activityId, updateStatus);
+    }
+
+    /**
+     * 查询抽奖活动详情
+     *
+     * @return
+     */
+    public LotteryActivityVO getLotteryActivityDetailFromCache(String lotteryActivityId) {
+        return lotteryCacheService.getLotteryActivityDetailFromCache(lotteryActivityId);
+    }
+
+    public LotteryActivityBaseVO getLastLotteryActivityBaseFromCache() {
+        String activityId = lotteryCacheService.getLastLotteryActivityIdFromCache();
+        return lotteryCacheService.getLotteryActivityDetailFromCache(activityId);
+    }
+
+    /**
+     * 查询最新一期活动Id
+     *
+     * @return
+     */
+    public String getLastLotteryActivityIdFromCache() {
+        return lotteryCacheService.getLastLotteryActivityIdFromCache();
+    }
+
+    /**
+     * 查询历史活动
+     * @param lastId
+     * @param size
+     * @return
+     */
+    public List<LotteryActivityBaseVO> getHistoryActivityList(Long lastId, int size) {
+        lastId = lastId == null ? lastId.MAX_VALUE : lastId;
+        return lotteryCacheService.queryHistoryActivityFromCache(lastId, size);
+    }
+
+
+    /**
+     * 列表查询
+     * @param name
+     * @param status
+     * @param pageNo
+     * @param pageSize
+     * @return
+     */
+    public APIPageResponseBean<List<LotteryActivityBaseVO>> pageList(String name, Integer status, int pageNo, int pageSize) {
+        Pageable pageable = PageRequest.of(pageNo - 1, pageSize, Sort.by(Sort.Order.desc("id")));
+        Specification<LotteryActivityEntity> specification = ((root, criteriaQuery, criteriaBuilder) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            if (status != null) {
+                predicates.add(criteriaBuilder.equal(root.get("status").as(Integer.class), status));
+            }
+            if (StringUtils.isNotBlank(name)) {
+                predicates.add(criteriaBuilder.like(root.get("name").as(String.class), "%" + name + "%"));
+            }
+            Predicate[] pre = new Predicate[predicates.size()];
+            criteriaQuery.where(predicates.toArray(pre));
+            return criteriaQuery.getRestriction();
+        });
+        Page<LotteryActivityEntity> lotteryActivityEntities = lotteryActivityDAO.findAll(specification, pageable);
+        if (CollectionUtils.isEmpty(lotteryActivityEntities.getContent())) {
+            return APIPageResponseBean.success(pageNo, pageSize, lotteryActivityEntities.getTotalPages(), Collections.emptyList());
+        }
+        List<LotteryActivityBaseVO> lotteryActivityBaseVOList = lotteryActivityEntities.getContent().stream().map(la -> {
+            LotteryActivityBaseVO lotteryActivityBaseVO = new LotteryActivityVO();
+            lotteryActivityBaseVO.setActivityId(la.getActivityId());
+            lotteryActivityBaseVO.setCover(la.getCover());
+            lotteryActivityBaseVO.setName(la.getName());
+            lotteryActivityBaseVO.setIntegral(la.getIntegral());
+            lotteryActivityBaseVO.setDescription(la.getDescription());
+            lotteryActivityBaseVO.setStatus(la.getStatus());
+            lotteryActivityBaseVO.setStatusName(LotteryConstants.getLotteryActivityStatusName(la.getStatus()));
+            lotteryActivityBaseVO.setCreateTime(la.getCreateDate().getTime());
+            return lotteryActivityBaseVO;
+        }).collect(Collectors.toList());
+        return APIPageResponseBean.success(pageNo, pageSize, lotteryActivityEntities.getTotalElements(), lotteryActivityBaseVOList);
+    }
+
+    private LotteryActivityEntity convert(LotteryActivitySaveVO lotteryActivityVO, String userId) {
+        Long id = lotteryActivityVO.getId();
+        Date now = new Date();
+        LotteryActivityEntity entity;
+        if (id != null) {
+            entity = lotteryActivityDAO.findById(id).orElse(null);
+            Assert.isTrue(entity != null, "更新失败: 活动不存在!");
+        } else {
+            entity = new LotteryActivityEntity();
+            entity.setActivityId(IDGenerateUtil.createId(LotteryConstants.LOTTERY_ACTIVITY_ID_PREFIX));
+            // 首次新建: 筹备中
+            entity.setStatus(LotteryConstants.LotteryActivityStatus.PREPARATION.getStatus());
+            entity.setCreateBy(userId);
+            entity.setCreateDate(now);
+        }
+        entity.setName(lotteryActivityVO.getName());
+        entity.setDescription(lotteryActivityVO.getDescription());
+        entity.setIntegral(0);
+        entity.setCover(lotteryActivityVO.getCover());
+        entity.setUpdateBy(userId);
+        entity.setUpdateDate(now);
+        return entity;
+    }
+
+}

+ 346 - 0
src/main/java/com/webchat/service/lottery/LotteryCacheService.java

@@ -0,0 +1,346 @@
+package com.webchat.service.lottery;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.webchat.common.constants.LotteryConstants;
+import com.webchat.common.enums.RedisKeyEnum;
+import com.webchat.common.util.JsonUtil;
+import com.webchat.domain.vo.response.lottery.LotteryActivityBaseVO;
+import com.webchat.domain.vo.response.lottery.LotteryActivityVO;
+import com.webchat.domain.vo.response.lottery.LotteryItemVO;
+import com.webchat.repository.dao.lottery.ILotteryActivityDAO;
+import com.webchat.repository.dao.lottery.ILotteryItemDAO;
+import com.webchat.repository.entity.lottery.LotteryActivityEntity;
+import com.webchat.repository.entity.lottery.LotteryItemEntity;
+import com.webchat.service.RedisService;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.DefaultTypedTuple;
+import org.springframework.data.redis.core.ZSetOperations;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 23:40
+ * @description
+ */
+@Service
+public class LotteryCacheService {
+
+    @Autowired
+    private ILotteryActivityDAO lotteryActivityDAO;
+    @Autowired
+    private ILotteryItemDAO lotteryItemDAO;
+    @Autowired
+    private RedisService redisService;
+
+    /**
+     * 刷新活动缓存,在活动保存后
+     *
+     * @param activityId
+     */
+    @Async
+    public LotteryActivityVO refreshLotteryActivityCacheById(String activityId) {
+        LotteryActivityEntity entity = lotteryActivityDAO.findByActivityId(activityId);
+        return this.refreshLotteryActivityCacheByEntity(entity);
+    }
+
+    @Async
+    public LotteryActivityVO refreshLotteryActivityCacheByEntity(LotteryActivityEntity entity) {
+        if (entity == null) {
+            return null;
+        }
+        String lotteryActivityId = entity.getActivityId();
+        List<LotteryItemVO> lotteryItemVOList = this.getLotteryItemVoListFromDB(lotteryActivityId);
+        /**
+         * 刷新奖品库存缓存
+         */
+        this.refreshLotteryItemStockCache(lotteryActivityId, lotteryItemVOList);
+        /**
+         * 刷新抽奖奖品详情缓存
+         */
+        this.refreshLotteryItemDetailCache(lotteryActivityId, lotteryItemVOList, null);
+        /**
+         * 刷新活动列表缓存
+         */
+        this.refreshLotteryActivityListCache();
+        /**
+         * 刷新历史抽奖活动缓存
+         */
+        this.refreshLotteryActivityHistoryCache();
+        /**
+         * 刷新最新一期活动id缓存
+         */
+        this.refreshLastLotteryActivityIdCache();
+        /**
+         * 刷新活动详情缓存
+         */
+        return refreshLotteryActivityDetailCache(entity, lotteryItemVOList);
+    }
+
+    /**
+     * 查询最新一期活动id
+     *
+     * @return
+     */
+    public String getLastLotteryActivityIdFromCache() {
+        String key = RedisKeyEnum.LOTTERY_ACTIVITY_LAST_ID_CACHE.getKey();
+        String activityId = redisService.get(key);
+        if (StringUtils.isNotBlank(activityId)) {
+            return activityId;
+        }
+        return this.refreshLastLotteryActivityIdCache();
+    }
+
+    /**
+     * 刷新最新一期活动ID缓存
+     *
+     * @return
+     */
+    private String refreshLastLotteryActivityIdCache() {
+        String key = RedisKeyEnum.LOTTERY_ACTIVITY_LAST_ID_CACHE.getKey();
+        String activityId = lotteryActivityDAO.findByLastLotteryActivity();
+        redisService.remove(key);
+        if (StringUtils.isNotBlank(activityId)) {
+            redisService.set(key, activityId, RedisKeyEnum.LOTTERY_ACTIVITY_LAST_ID_CACHE.getExpireTime());
+        }
+        return activityId;
+    }
+
+    /**
+     * 刷新历史抽奖活动缓存
+     */
+    private void refreshLotteryActivityHistoryCache() {
+        List<LotteryActivityEntity> lotteryActivityEntities =
+                lotteryActivityDAO.findAllByStatusNotOrderByCreateDateDesc(
+                        LotteryConstants.LotteryActivityStatus.DELETED.getStatus());
+        String key = RedisKeyEnum.LOTTERY_ACTIVITY_HISTORY_CACHE.getKey();
+        if (CollectionUtils.isEmpty(lotteryActivityEntities)) {
+            redisService.remove(key);
+            return;
+        }
+        Set<ZSetOperations.TypedTuple<String>> tuples = lotteryActivityEntities.stream().map(
+                activity -> {
+                    String value = String.valueOf(activity.getActivityId());
+                    Double score = Double.valueOf(activity.getId());
+                    return new DefaultTypedTuple<>(value, score);
+                }).collect(Collectors.toSet());
+        redisService.zadd(key, tuples, RedisKeyEnum.LOTTERY_ACTIVITY_HISTORY_CACHE.getExpireTime());
+    }
+
+    /**
+     * 查询历史活动
+     * @param lastId
+     * @param size
+     * @return
+     */
+    public List<LotteryActivityBaseVO> queryHistoryActivityFromCache(Long lastId, int size) {
+        String key = RedisKeyEnum.LOTTERY_ACTIVITY_HISTORY_CACHE.getKey();
+        Long maxScore = lastId == null ? Long.MAX_VALUE : lastId;
+        Set<String> activityIdStrSet = redisService.zreverseRangeByScore(key, maxScore, 0, size);
+        if (CollectionUtils.isEmpty(activityIdStrSet)) {
+            return Collections.emptyList();
+        }
+        return activityIdStrSet.stream().map(id -> {
+            return this.getLotteryActivityDetailFromCache(id);
+        }).collect(Collectors.toList());
+    }
+
+    /**
+     * 刷新抽奖活动详情缓存
+     *
+     * @param entity
+     * @param lotteryItemVOList
+     * @return
+     */
+    private LotteryActivityVO refreshLotteryActivityDetailCache(LotteryActivityEntity entity,
+                                                                List<LotteryItemVO> lotteryItemVOList) {
+        String lotteryActivityId = entity.getActivityId();
+        RedisKeyEnum lotteryActivityCacheKey = RedisKeyEnum.LOTTERY_ACTIVITY_DETAIL_CACHE;
+        String key = lotteryActivityCacheKey.getKey(lotteryActivityId);
+        LotteryActivityVO lotteryActivityVO = this.covertLotteryActivityVO(entity);
+        lotteryActivityVO.setItems(lotteryItemVOList);
+        lotteryActivityVO.setStatusName(LotteryConstants.getLotteryActivityStatusName(entity.getStatus()));
+        redisService.set(key, JsonUtil.toJsonString(lotteryActivityVO), lotteryActivityCacheKey.getExpireTime());
+        return lotteryActivityVO;
+    }
+
+    /**
+     * 刷新抽奖活动奖品库存余量缓存
+     *
+     * @param lotteryActivityId
+     * @param lotteryItemVOList
+     * @return MAP<itemId, stock>
+     */
+    public Map<Long, Integer> refreshLotteryItemStockCache(String lotteryActivityId,
+                                                           List<LotteryItemVO> lotteryItemVOList) {
+        RedisKeyEnum lotteryActivityCacheKey = RedisKeyEnum.LOTTERY_ACTIVITY_ITEM_STOCK_CACHE;
+        String key = lotteryActivityCacheKey.getKey(lotteryActivityId);
+        if (CollectionUtils.isEmpty(lotteryItemVOList)) {
+            redisService.remove(key);
+            return Collections.emptyMap();
+        }
+        Map<Long, Integer> lotteryItemStockMap = new ConcurrentHashMap<>();
+        lotteryItemVOList.stream().forEach(item -> {
+            lotteryItemStockMap.put(item.getId(), item.getStock());
+        });
+        redisService.set(key, JsonUtil.toJsonString(lotteryItemStockMap), lotteryActivityCacheKey.getExpireTime());
+        return lotteryItemStockMap;
+    }
+
+    /**
+     * 刷新抽奖奖品详情缓存
+     *
+     * @param lotteryActivityId
+     * @param lotteryItemVOList
+     */
+    public void refreshLotteryItemDetailCache(String lotteryActivityId, List<LotteryItemVO> lotteryItemVOList) {
+        this.refreshLotteryItemDetailCache(lotteryActivityId, lotteryItemVOList, null);
+    }
+
+    public LotteryItemVO refreshLotteryItemDetailCache(String lotteryActivityId, List<LotteryItemVO> lotteryItemVOList,
+                                                       Long itemId) {
+        RedisKeyEnum lotteryActivityItemDetailKey = RedisKeyEnum.LOTTERY_ACTIVITY_ITEM_DETAIL_CACHE;
+        String key = lotteryActivityItemDetailKey.getKey(lotteryActivityId);
+        if (CollectionUtils.isEmpty(lotteryItemVOList)) {
+            redisService.remove(key);
+            return null;
+        }
+        LotteryItemVO backItem = null;
+        for (LotteryItemVO lotteryItem : lotteryItemVOList) {
+            redisService.hset(key, lotteryItem.getId().toString(),
+                    JsonUtil.toJsonString(lotteryItem), lotteryActivityItemDetailKey.getExpireTime());
+            if (itemId != null && itemId.equals(lotteryItem.getId())) {
+                backItem = lotteryItem;
+            }
+        }
+        return backItem;
+    }
+
+    /**
+     * 查询抽奖商品信息
+     *
+     * @param lotteryActivityId
+     * @param itemId
+     * @return
+     */
+    public LotteryItemVO getLotteryItemVOFromCache(String lotteryActivityId, Long itemId) {
+        RedisKeyEnum lotteryActivityItemDetailKey = RedisKeyEnum.LOTTERY_ACTIVITY_ITEM_DETAIL_CACHE;
+        String key = lotteryActivityItemDetailKey.getKey(lotteryActivityId);
+        String cache = redisService.hget(key, String.valueOf(itemId));
+        if (StringUtils.isNotBlank(cache)) {
+            return JsonUtil.fromJson(cache, LotteryItemVO.class);
+        }
+        List<LotteryItemVO> lotteryItemVOList = this.getLotteryItemVoListFromDB(lotteryActivityId);
+        return refreshLotteryItemDetailCache(lotteryActivityId, lotteryItemVOList, itemId);
+    }
+
+    /**
+     * 查询抽奖活动奖品库存数据
+     *
+     * @param lotteryActivityId
+     * @return
+     */
+    public Map<Long, Integer> getLotteryItemStockFromCache(String lotteryActivityId) {
+        RedisKeyEnum lotteryActivityCacheKey = RedisKeyEnum.LOTTERY_ACTIVITY_ITEM_STOCK_CACHE;
+        String key = lotteryActivityCacheKey.getKey(lotteryActivityId);
+        String stockCache = redisService.get(key);
+        if (StringUtils.isNotBlank(stockCache)) {
+            return JsonUtil.fromJson(stockCache, new TypeReference<ConcurrentHashMap<Long, Integer>>() {
+            });
+        }
+        return Collections.emptyMap();
+    }
+
+    /**
+     * 抽奖出结果后扣减库存
+     *
+     * @param lotteryActivityId
+     * @param itemId
+     */
+    public void deductItemStock(String lotteryActivityId, Long itemId) {
+        RedisKeyEnum lotteryActivityCacheKey = RedisKeyEnum.LOTTERY_ACTIVITY_ITEM_STOCK_CACHE;
+        String key = lotteryActivityCacheKey.getKey(lotteryActivityId);
+        Map<Long, Integer> stockMap = this.getLotteryItemStockFromCache(lotteryActivityId);
+        Assert.isTrue(MapUtils.isNotEmpty(stockMap), "库存为空");
+        stockMap.put(itemId, stockMap.get(itemId) - 1);
+        redisService.set(key, JsonUtil.toJsonString(stockMap), lotteryActivityCacheKey.getExpireTime());
+    }
+
+    /**
+     * 查询活动下的奖品列表
+     *
+     * @param activityId
+     * @return
+     */
+    private List<LotteryItemVO> getLotteryItemVoListFromDB(String activityId) {
+        List<LotteryItemEntity> lotteryItemEntities = lotteryItemDAO.findAllByActivityIdOrderBySlotAsc(activityId);
+        if (CollectionUtils.isEmpty(lotteryItemEntities)) {
+            return Collections.emptyList();
+        }
+        return lotteryItemEntities.stream().map(item -> {
+            LotteryItemVO lotteryItemVO = new LotteryItemVO();
+            BeanUtils.copyProperties(item, lotteryItemVO);
+            lotteryItemVO.setTypeName(LotteryConstants.getLotteryActivityItemTypeName(item.getType()));
+            return lotteryItemVO;
+        }).collect(Collectors.toList());
+    }
+
+    private LotteryActivityVO covertLotteryActivityVO(LotteryActivityEntity entity) {
+        LotteryActivityVO lotteryActivityVO = new LotteryActivityVO();
+        BeanUtils.copyProperties(entity, lotteryActivityVO);
+        lotteryActivityVO.setCreateTime(entity.getCreateDate().getTime());
+        return lotteryActivityVO;
+    }
+
+    /**
+     * 查询最新一期活动
+     *
+     * @return
+     */
+    public LotteryActivityVO getLastLotteryActivityDetailFromCache() {
+        String activityId = this.getLastLotteryActivityIdFromCache();
+        if (StringUtils.isBlank(activityId)) {
+            return null;
+        }
+        return this.getLotteryActivityDetailFromCache(activityId);
+    }
+
+    /**
+     * 查询抽奖活动详情
+     *
+     * @param lotteryActivityId
+     * @return
+     */
+    public LotteryActivityVO getLotteryActivityDetailFromCache(String lotteryActivityId) {
+        RedisKeyEnum lotteryActivityCacheKey = RedisKeyEnum.LOTTERY_ACTIVITY_DETAIL_CACHE;
+        String key = lotteryActivityCacheKey.getKey(lotteryActivityId);
+        String lotteryActivityCache = redisService.get(key);
+        LotteryActivityVO lotteryActivity;
+        if (StringUtils.isNotBlank(lotteryActivityCache)) {
+            lotteryActivity = JsonUtil.fromJson(lotteryActivityCache, LotteryActivityVO.class);
+        } else {
+            lotteryActivity = this.refreshLotteryActivityCacheById(lotteryActivityId);
+        }
+        return lotteryActivity;
+    }
+
+    /**
+     * 刷新抽奖活动缓存
+     */
+    private void refreshLotteryActivityListCache() {
+
+    }
+}

+ 145 - 0
src/main/java/com/webchat/service/lottery/LotteryItemService.java

@@ -0,0 +1,145 @@
+package com.webchat.service.lottery;
+
+import com.webchat.common.bean.APIPageResponseBean;
+import com.webchat.common.constants.LotteryConstants;
+import com.webchat.domain.vo.request.lottery.LotteryItemSaveVO;
+import com.webchat.domain.vo.response.lottery.LotteryItemVO;
+import com.webchat.repository.dao.lottery.ILotteryItemDAO;
+import com.webchat.repository.entity.lottery.LotteryItemEntity;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+
+import javax.persistence.criteria.Predicate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 23:40
+ * @description
+ */
+@Service
+public class LotteryItemService {
+
+    @Autowired
+    private ILotteryItemDAO lotteryItemDAO;
+
+    /**
+     * 保存更新抽奖奖品
+     *
+     * @param lotteryItemSaveVO
+     * @param userId
+     * @return
+     */
+    public Long save(LotteryItemSaveVO lotteryItemSaveVO, String userId) {
+        LotteryItemEntity lotteryItemEntity = this.convert(lotteryItemSaveVO, userId);
+        long count = lotteryItemDAO.countByActivityIdAndSlot(lotteryItemSaveVO.getActivityId(), lotteryItemEntity.getSlot());
+        Assert.isTrue(count == 0, "槽位已经设置了奖品!");
+        return lotteryItemDAO.save(lotteryItemEntity).getId();
+    }
+
+    /**
+     * 分页
+     * @param activityId
+     * @param name
+     * @param slot
+     * @param pageNo
+     * @param pageSize
+     * @return
+     */
+    public APIPageResponseBean<List<LotteryItemVO>> pageList(String activityId, String name, Integer slot, int pageNo, int pageSize) {
+        Pageable pageable = PageRequest.of(pageNo - 1, pageSize, Sort.by(Sort.Order.desc("id")));
+        Specification<LotteryItemEntity> specification = ((root, criteriaQuery, criteriaBuilder) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            if (StringUtils.isNotBlank(activityId)) {
+                predicates.add(criteriaBuilder.equal(root.get("activityId").as(String.class), activityId));
+            }
+            if (StringUtils.isNotBlank(name)) {
+                predicates.add(criteriaBuilder.like(root.get("name").as(String.class), "%" + name + "%"));
+            }
+            if (slot != null) {
+                predicates.add(criteriaBuilder.equal(root.get("slot").as(Integer.class), slot));
+            }
+            Predicate[] pre = new Predicate[predicates.size()];
+            criteriaQuery.where(predicates.toArray(pre));
+            return criteriaQuery.getRestriction();
+        });
+        Page<LotteryItemEntity> lotteryItemEntities = lotteryItemDAO.findAll(specification, pageable);
+        if (CollectionUtils.isEmpty(lotteryItemEntities.getContent())) {
+            return APIPageResponseBean.success(pageNo, pageSize, lotteryItemEntities.getTotalPages(), Collections.emptyList());
+        }
+        List<LotteryItemVO> lotteryItemVOList = lotteryItemEntities.getContent().stream().map(la -> {
+            LotteryItemVO lotteryItemVO = new LotteryItemVO();
+            lotteryItemVO.setId(la.getId());
+            lotteryItemVO.setActivityId(la.getActivityId());
+            lotteryItemVO.setCover(la.getCover());
+            lotteryItemVO.setName(la.getName());
+            lotteryItemVO.setType(la.getType());
+            lotteryItemVO.setIcon(la.getIcon());
+            lotteryItemVO.setCover(la.getCover());
+            lotteryItemVO.setSlot(la.getSlot());
+            lotteryItemVO.setStock(la.getStock());
+            lotteryItemVO.setCreateTime(la.getCreateDate().getTime());
+            return lotteryItemVO;
+        }).collect(Collectors.toList());
+        return APIPageResponseBean.success(pageNo, pageSize, lotteryItemEntities.getTotalElements(), lotteryItemVOList);
+    }
+
+    /**
+     * 校验抽奖活动的奖品数量
+     *
+     * @param lotteryActivityId
+     */
+    public void validateLotteryItemCount(String lotteryActivityId) {
+        Long count = countLotteryItem(lotteryActivityId);
+        Assert.isTrue(LotteryConstants.LOTTERY_ITEM_COUNT == count, "活动奖品数量配置有误");
+    }
+
+    /**
+     * 获取活动奖品数量
+     *
+     * @param lotteryActivityId
+     * @return
+     */
+    public Long countLotteryItem(String lotteryActivityId) {
+        Long count = lotteryItemDAO.countByActivityId(lotteryActivityId);
+        return count == null ? 0L : count;
+    }
+
+    private LotteryItemEntity convert(LotteryItemSaveVO lotteryItemSaveVO, String userId) {
+        Long id = lotteryItemSaveVO.getId();
+        LotteryItemEntity entity;
+        Date now = new Date();
+        if (id != null) {
+            entity = lotteryItemDAO.findById(id).orElse(null);
+            Assert.isTrue(entity != null, "奖品信息更新失败:奖品不存在!");
+        } else {
+            entity = new LotteryItemEntity();
+            entity.setActivityId(lotteryItemSaveVO.getActivityId());
+            entity.setCreateBy(userId);
+            entity.setCreateDate(now);
+        }
+        entity.setType(lotteryItemSaveVO.getType());
+        entity.setName(lotteryItemSaveVO.getName());
+        entity.setIcon(lotteryItemSaveVO.getIcon());
+        entity.setCover(lotteryItemSaveVO.getCover());
+        entity.setSlot(lotteryItemSaveVO.getSlot());
+        entity.setStock(lotteryItemSaveVO.getStock());
+        entity.setUpdateBy(userId);
+        entity.setUpdateDate(now);
+        return entity;
+    }
+
+}

+ 326 - 0
src/main/java/com/webchat/service/lottery/LotteryOrderService.java

@@ -0,0 +1,326 @@
+package com.webchat.service.lottery;
+
+import com.google.common.collect.Lists;
+import com.webchat.common.bean.APIPageResponseBean;
+import com.webchat.common.constants.LotteryConstants;
+import com.webchat.common.constants.WebConstant;
+import com.webchat.common.enums.RedisKeyEnum;
+import com.webchat.common.util.IDGenerateUtil;
+import com.webchat.common.util.JsonUtil;
+import com.webchat.domain.vo.request.lottery.LotteryOrderStatusUpdateVO;
+import com.webchat.domain.vo.response.UserBaseResponseInfoVO;
+import com.webchat.domain.vo.response.lottery.LotteryActivityVO;
+import com.webchat.domain.vo.response.lottery.LotteryItemVO;
+import com.webchat.domain.vo.response.lottery.LotteryUserOrderVO;
+import com.webchat.repository.dao.lottery.ILotteryOrderDAO;
+import com.webchat.repository.entity.lottery.LotteryOrderEntity;
+import com.webchat.service.RedisService;
+import com.webchat.service.UserService;
+import com.webchat.service.queue.dto.LotteryOrderQueueDTO;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+
+import javax.persistence.criteria.Predicate;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 23:40
+ * @description
+ */
+@Slf4j
+@Service
+public class LotteryOrderService {
+
+    @Autowired
+    private RedisService redisService;
+    @Autowired
+    private LotteryCacheService lotteryCacheService;
+    @Autowired
+    private ILotteryOrderDAO lotteryOrderDAO;
+    @Autowired
+    private UserService userService;
+
+
+    /**
+     * 处理中奖订单
+     *
+     * @param lotteryOrderMessage
+     */
+    public void handleLotteryOrder(LotteryOrderQueueDTO lotteryOrderMessage) {
+        LotteryOrderEntity lotteryOrder = new LotteryOrderEntity();
+        lotteryOrder.setOrderId(IDGenerateUtil.createId(LotteryConstants.LOTTERY_ORDER_ID_PREFIX));
+        lotteryOrder.setUserId(lotteryOrderMessage.getUserId());
+        lotteryOrder.setItemId(lotteryOrderMessage.getItemId());
+        lotteryOrder.setActivityId(lotteryOrderMessage.getActivityId());
+        lotteryOrder.setStatus(LotteryConstants.LotteryOrderStatus.UNCHANGED.getStatus());
+        lotteryOrder.setCreateBy(WebConstant.SYSTEM_USER_ID);
+        lotteryOrder.setCreateDate(new Date());
+        /**
+         * 订单入库
+         */
+        lotteryOrder = lotteryOrderDAO.save(lotteryOrder);
+        /**
+         * 添加活动累计中奖名单缓存
+         */
+        this.addLotteryOrderHistoryCache(lotteryOrder);
+        /**
+         * 添加活动下用户累计中奖名单缓存
+         */
+        this.addLotteryUserOrderHistoryCache(lotteryOrder);
+        /**
+         * 处理抽到元气值商品的情况
+         */
+        lotteryOrder = this.handleIntegralItemLuckResult(lotteryOrder);
+        /**
+         * 添加订单详情缓存
+         */
+        this.addLotteryOrderDetailCache(lotteryOrder);
+    }
+
+    /**
+     * 处理抽到虚拟商品的情况
+     *
+     * @param lotteryOrder
+     */
+    public LotteryOrderEntity handleIntegralItemLuckResult(LotteryOrderEntity lotteryOrder) {
+        log.info("抽奖抽中元气值,消息消费内容:{}", JsonUtil.toJsonString(lotteryOrder));
+        String activityId = lotteryOrder.getActivityId();
+        Long itemId = lotteryOrder.getItemId();
+        String userId = lotteryOrder.getUserId();
+        LotteryItemVO lotteryItemVO = lotteryCacheService.getLotteryItemVOFromCache(activityId, itemId);
+        if (lotteryItemVO == null) {
+            return lotteryOrder;
+        }
+        if (!LotteryConstants.LotteryItemType.INTEGRAL.getType().equals(lotteryItemVO.getType())) {
+            // 非猿气值
+            log.info("抽奖未抽中元气值,消息消费内容:{}", JsonUtil.toJsonString(lotteryOrder));
+            return lotteryOrder;
+        }
+        String name = lotteryItemVO.getName();
+        String countStr = name.substring(name.indexOf("X") + 1);
+        /**
+         * 修改订单状态
+         */
+        lotteryOrder.setStatus(LotteryConstants.LotteryOrderStatus.EXCHANGED.getStatus());
+        lotteryOrder.setUpdateBy(LotteryConstants.SYSTEM);
+        lotteryOrder.setUpdateDate(new Date());
+        return lotteryOrderDAO.save(lotteryOrder);
+    }
+
+    /**
+     * 更新订单状态
+     *
+     * @param lotteryOrderStatusUpdate
+     * @param userId
+     */
+    public void updateLotteryOrderStatus(LotteryOrderStatusUpdateVO lotteryOrderStatusUpdate, String userId) {
+        String orderId = lotteryOrderStatusUpdate.getOrderId();
+        Integer status = lotteryOrderStatusUpdate.getStatus();
+        LotteryOrderEntity lotteryOrder = lotteryOrderDAO.findByOrderId(orderId);
+        Assert.isTrue(lotteryOrder != null, "订单不存在");
+        lotteryOrder.setStatus(status);
+        lotteryOrder.setUpdateBy(userId);
+        lotteryOrder.setUpdateDate(new Date());
+        lotteryOrderDAO.save(lotteryOrder);
+        /**
+         * 刷新订单状态缓存
+         */
+        refreshLotteryOrderDetailCache(orderId);
+    }
+
+    /**
+     * 查询用户的中奖订单
+     *
+     * @param activityId
+     * @param userId
+     * @param size
+     * @return
+     */
+    public List<LotteryUserOrderVO> queryActivityUserOrder(String activityId, String userId, int size) {
+        String key = RedisKeyEnum.LOTTERY_ACTIVITY_USER_ORDER_CACHE.getKey(activityId, userId);
+        Set<String> orderIdSet = redisService.zreverseRange(key, 0, size);
+        if (CollectionUtils.isEmpty(orderIdSet)) {
+            return Collections.emptyList();
+        }
+        String orderKey = RedisKeyEnum.LOTTERY_ACTIVITY_ORDER_DETAIL_CACHE.getKey(activityId);
+        List<String> orderCaches = redisService.hmget(orderKey, Lists.newArrayList(orderIdSet));
+        if (CollectionUtils.isEmpty(orderCaches)) {
+            return Collections.emptyList();
+        }
+        List<LotteryOrderEntity> lotteryOrderEntities = orderCaches.stream()
+                .map(cache -> JsonUtil.fromJson(cache, LotteryOrderEntity.class)).collect(Collectors.toList());
+        List<LotteryUserOrderVO> lotteryUserOrderVOList = new ArrayList<>();
+        Map<Long, LotteryItemVO> itemTempMap = new HashMap<>();
+        for (LotteryOrderEntity lotteryOrder : lotteryOrderEntities) {
+            LotteryUserOrderVO lotteryUserOrderVO = new LotteryUserOrderVO();
+            LotteryItemVO item;
+            Long itemId = lotteryOrder.getItemId();
+            if ((item = itemTempMap.get(itemId)) == null) {
+                item = lotteryCacheService.getLotteryItemVOFromCache(activityId, itemId);
+                itemTempMap.put(itemId, item);
+            }
+            lotteryUserOrderVO.setOrderId(lotteryOrder.getOrderId());
+            lotteryUserOrderVO.setItem(item);
+            lotteryUserOrderVO.setOrderStatus(lotteryOrder.getStatus());
+            lotteryUserOrderVO.setOrderStatusName(LotteryConstants.getLotteryOrderStatusName(lotteryOrder.getStatus()));
+            if (lotteryOrder.getCreateDate() != null) {
+                lotteryUserOrderVO.setLuckTime(lotteryOrder.getCreateDate().getTime());
+            }
+            if (lotteryOrder.getUpdateDate() != null) {
+                lotteryUserOrderVO.setExchangeTime(lotteryOrder.getUpdateDate().getTime());
+            }
+            lotteryUserOrderVOList.add(lotteryUserOrderVO);
+        }
+        return lotteryUserOrderVOList;
+    }
+
+    /**
+     * 查询活动下的中奖订单
+     *
+     * @param activityId
+     * @param size
+     * @return
+     */
+    public List<LotteryUserOrderVO> queryActivityOrder(String activityId, int size) {
+        String key = RedisKeyEnum.LOTTERY_ACTIVITY_ORDER_CACHE.getKey(activityId);
+        Set<String> orderIdSet = redisService.zreverseRange(key, 0, size);
+        if (CollectionUtils.isEmpty(orderIdSet)) {
+            return Collections.emptyList();
+        }
+        String orderKey = RedisKeyEnum.LOTTERY_ACTIVITY_ORDER_DETAIL_CACHE.getKey(activityId);
+        List<String> orderCaches = redisService.hmget(orderKey, Lists.newArrayList(orderIdSet));
+        if (CollectionUtils.isEmpty(orderCaches)) {
+            return Collections.emptyList();
+        }
+        List<LotteryOrderEntity> lotteryOrderEntities = orderCaches.stream()
+                .map(cache -> JsonUtil.fromJson(cache, LotteryOrderEntity.class)).collect(Collectors.toList());
+
+        List<String> userIdList = lotteryOrderEntities.stream().map(LotteryOrderEntity::getUserId).collect(Collectors.toList());
+        Map<String, UserBaseResponseInfoVO> luckUserMap = userService.batchGetUserInfoFromCache(userIdList);
+        List<LotteryUserOrderVO> lotteryUserOrderVOList = new ArrayList<>();
+        Map<Long, LotteryItemVO> itemTempMap = new HashMap<>();
+        for (LotteryOrderEntity lotteryOrder : lotteryOrderEntities) {
+            LotteryUserOrderVO lotteryUserOrderVO = new LotteryUserOrderVO();
+            LotteryItemVO item;
+            Long itemId = lotteryOrder.getItemId();
+            if ((item = itemTempMap.get(itemId)) == null) {
+                item = lotteryCacheService.getLotteryItemVOFromCache(activityId, itemId);
+                itemTempMap.put(itemId, item);
+            }
+            lotteryUserOrderVO.setUser(luckUserMap.get(lotteryOrder.getUserId()));
+            lotteryUserOrderVO.setItem(item);
+            lotteryUserOrderVO.setOrderStatus(lotteryOrder.getStatus());
+            lotteryUserOrderVO.setOrderStatusName(LotteryConstants.getLotteryOrderStatusName(lotteryOrder.getStatus()));
+            if (lotteryOrder.getCreateDate() != null) {
+                lotteryUserOrderVO.setLuckTime(lotteryOrder.getCreateDate().getTime());
+            }
+            if (lotteryOrder.getUpdateDate() != null) {
+                lotteryUserOrderVO.setExchangeTime(lotteryOrder.getUpdateDate().getTime());
+            }
+            lotteryUserOrderVOList.add(lotteryUserOrderVO);
+        }
+        return lotteryUserOrderVOList;
+    }
+
+    /**
+     * 订单检索
+     *
+     * @return
+     */
+    public APIPageResponseBean<List<LotteryUserOrderVO>> pageList(String activityId, String orderId, Integer status, int pageNo, int pageSize) {
+        Pageable pageable = PageRequest.of(pageNo - 1, pageSize, Sort.by(Sort.Order.desc("id")));
+        Specification<LotteryOrderEntity> specification = ((root, criteriaQuery, criteriaBuilder) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            if (StringUtils.isNotBlank(activityId)) {
+                predicates.add(criteriaBuilder.equal(root.get("activityId").as(String.class), activityId));
+            }
+            if (StringUtils.isNotBlank(orderId)) {
+                predicates.add(criteriaBuilder.equal(root.get("activityId").as(String.class), orderId));
+            }
+            if (status != null) {
+                predicates.add(criteriaBuilder.equal(root.get("status").as(Integer.class), status));
+            }
+            Predicate[] pre = new Predicate[predicates.size()];
+            criteriaQuery.where(predicates.toArray(pre));
+            return criteriaQuery.getRestriction();
+        });
+        Page<LotteryOrderEntity> lotteryOrderEntityPage = lotteryOrderDAO.findAll(specification, pageable);
+        if (CollectionUtils.isEmpty(lotteryOrderEntityPage.getContent())) {
+            return APIPageResponseBean.success(pageNo, pageSize, lotteryOrderEntityPage.getTotalPages(), Collections.emptyList());
+        }
+        List<LotteryOrderEntity> lotteryOrderEntities = lotteryOrderEntityPage.getContent();
+        List<String> userIdList = lotteryOrderEntities.stream().map(LotteryOrderEntity::getUserId).collect(Collectors.toList());
+        Map<String, UserBaseResponseInfoVO> luckUserMap = userService.batchGetUserInfoFromCache(userIdList);
+        List<LotteryUserOrderVO> lotteryUserOrderVOList = new ArrayList<>();
+        Map<Long, LotteryItemVO> itemTempMap = new HashMap<>();
+        Map<String, LotteryActivityVO> activityTempMap = new HashMap<>();
+        for (LotteryOrderEntity lotteryOrder : lotteryOrderEntities) {
+            LotteryUserOrderVO lotteryUserOrderVO = new LotteryUserOrderVO();
+            LotteryItemVO item;
+            LotteryActivityVO activity;
+            Long itemId = lotteryOrder.getItemId();
+            String activityIdStr = lotteryOrder.getActivityId();
+            if ((item = itemTempMap.get(itemId)) == null) {
+                item = lotteryCacheService.getLotteryItemVOFromCache(activityIdStr, itemId);
+                itemTempMap.put(itemId, item);
+            }
+            if ((activity = activityTempMap.get(activityIdStr)) == null) {
+                activity = lotteryCacheService.getLotteryActivityDetailFromCache(activityIdStr);
+                activityTempMap.put(activityIdStr, activity);
+            }
+            lotteryUserOrderVO.setUser(luckUserMap.get(lotteryOrder.getUserId()));
+            lotteryUserOrderVO.setOrderId(lotteryOrder.getOrderId());
+            lotteryUserOrderVO.setItem(item);
+            lotteryUserOrderVO.setActivity(activity);
+            lotteryUserOrderVO.setOrderStatus(lotteryOrder.getStatus());
+            lotteryUserOrderVO.setOrderStatusName(LotteryConstants.getLotteryOrderStatusName(lotteryOrder.getStatus()));
+            if (lotteryOrder.getCreateDate() != null) {
+                lotteryUserOrderVO.setLuckTime(lotteryOrder.getCreateDate().getTime());
+            }
+            if (lotteryOrder.getUpdateDate() != null) {
+                lotteryUserOrderVO.setExchangeTime(lotteryOrder.getUpdateDate().getTime());
+            }
+            lotteryUserOrderVOList.add(lotteryUserOrderVO);
+        }
+        return APIPageResponseBean.success(pageNo, pageSize, lotteryOrderEntityPage.getTotalElements(), lotteryUserOrderVOList);
+    }
+
+
+    public void addLotteryOrderHistoryCache(LotteryOrderEntity lotteryOrder) {
+        String key = RedisKeyEnum.LOTTERY_ACTIVITY_ORDER_CACHE.getKey(lotteryOrder.getActivityId());
+        redisService.zadd(key, lotteryOrder.getOrderId(), lotteryOrder.getId());
+    }
+
+    public void addLotteryOrderDetailCache(LotteryOrderEntity lotteryOrder) {
+        RedisKeyEnum keyEnum = RedisKeyEnum.LOTTERY_ACTIVITY_ORDER_DETAIL_CACHE;
+        String key = keyEnum.getKey(lotteryOrder.getActivityId());
+        redisService.hset(key, lotteryOrder.getOrderId(), JsonUtil.toJsonString(lotteryOrder), keyEnum.getExpireTime());
+    }
+
+    public void addLotteryUserOrderHistoryCache(LotteryOrderEntity lotteryOrder) {
+        String key = RedisKeyEnum.LOTTERY_ACTIVITY_USER_ORDER_CACHE.getKey(lotteryOrder.getActivityId(), lotteryOrder.getUserId());
+        redisService.zadd(key, lotteryOrder.getOrderId(), lotteryOrder.getId());
+    }
+
+    /**
+     * 刷新订单详情缓存
+     *
+     * @param orderId
+     */
+    public void refreshLotteryOrderDetailCache(String orderId) {
+        LotteryOrderEntity orderEntity = lotteryOrderDAO.findByOrderId(orderId);
+        this.addLotteryOrderDetailCache(orderEntity);
+    }
+}

+ 208 - 0
src/main/java/com/webchat/service/lottery/LotteryService.java

@@ -0,0 +1,208 @@
+package com.webchat.service.lottery;
+
+import com.webchat.common.constants.LotteryConstants;
+import com.webchat.common.exception.BusinessException;
+import com.webchat.domain.vo.request.lottery.LotteryActivityStatusUpdateVO;
+import com.webchat.domain.vo.response.lottery.LotteryActivityVO;
+import com.webchat.service.queue.LotteryOrderQueue;
+import com.webchat.service.queue.dto.LotteryOrderQueueDTO;
+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.util.Assert;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/16 23:40
+ * @description
+ */
+@Slf4j
+@Service
+public class LotteryService {
+
+    @Autowired
+    private LotteryOrderQueue lotteryOrderQueue;
+    @Autowired
+    private LotteryOrderService lotteryOrderService;
+    @Autowired
+    private LotteryActivityService lotteryActivityService;
+    @Autowired
+    private LotteryItemService lotteryItemService;
+    @Autowired
+    private LotteryCacheService lotteryCacheService;
+
+    /**
+     * 幸运抽奖
+     *
+     * @param lotteryActivityId 抽奖活动id
+     * @param userId            当前登录用户id
+     * @return 中奖商品id
+     */
+    public Long luckDraw(String lotteryActivityId, String userId) {
+
+        /**
+         * 1、抽奖
+         */
+        Long lotteryItemId = this.doLotteryDraw(lotteryActivityId);
+
+        /**
+         * 2、处理中奖订单
+         */
+        this.handleLotteryOrder(userId, lotteryActivityId, lotteryItemId);
+
+        return lotteryItemId;
+    }
+
+    /**
+     * 多次抽奖
+     *
+     * @param lotteryActivityId
+     * @param userId
+     * @param times
+     * @return
+     */
+    public synchronized List<Long> luckDraw(String lotteryActivityId, String userId, int times) {
+        List<Long> result = new Vector<>();
+        for (int i = 0; i < times; i++) {
+            Long lotteryItemId = this.doLotteryDraw(lotteryActivityId);
+            this.handleLotteryOrder(userId, lotteryActivityId, lotteryItemId);
+            result.add(lotteryItemId);
+        }
+        return result;
+    }
+
+    /**
+     * 最新一期活动抽奖
+     * @param userId
+     * @return
+     */
+    public Long luckDrawLastActivity(String userId) {
+        String activityId = lotteryCacheService.getLastLotteryActivityIdFromCache();
+        Assert.isTrue(StringUtils.isNotBlank(activityId), "未查询到最新一期的抽奖活动");
+        return this.luckDraw(activityId, userId);
+    }
+
+    /**
+     * 抽奖,返回抽中奖品的id
+     *
+     * @param lotteryActivityId
+     * @return
+     */
+    private Long doLotteryDraw(String lotteryActivityId) {
+
+        /**
+         * 1、抽奖
+         */
+        return this.executeLotteryDraw(lotteryActivityId);
+    }
+
+    /**
+     * 执行抽奖
+     *
+     * @param lotteryActivityId
+     * @return
+     */
+    private synchronized Long executeLotteryDraw(String lotteryActivityId) {
+        /**
+         * 获取奖品库存
+         */
+        Map<Long, Integer> stockMap = lotteryCacheService.getLotteryItemStockFromCache(lotteryActivityId);
+        /**
+         * 构造奖品池
+         */
+        List<Long> itemPool = new ArrayList<>();
+        for (Map.Entry<Long, Integer> entry : stockMap.entrySet()) {
+            Long itemId = entry.getKey();
+            int stock = entry.getValue();
+            if (stock == 0) {
+                continue;
+            }
+            for (int i = 0; i < stock; i++) {
+                itemPool.add(itemId);
+            }
+        }
+
+        /**
+         * 奖品已抽完,自动结束活动
+         */
+        if (itemPool.size() == 0) {
+            LotteryActivityStatusUpdateVO lotteryActivityStatusUpdate = new LotteryActivityStatusUpdateVO();
+            lotteryActivityStatusUpdate.setActivityId(lotteryActivityId);
+            lotteryActivityStatusUpdate.setStatus(LotteryConstants.LotteryActivityStatus.CLOSED.getStatus());
+            lotteryActivityService.updateLotteryActivityStatus(lotteryActivityStatusUpdate, LotteryConstants.SYSTEM);
+            throw new BusinessException("本期奖品抽完。活动已结束!");
+        }
+
+        /**
+         * 随机抽奖
+         */
+        int luckIndex = this.randomLotteryIndex(itemPool.size());
+        Long luckItemId = itemPool.get(luckIndex);
+        /**
+         * 扣减库存
+         */
+        lotteryCacheService.deductItemStock(lotteryActivityId, luckItemId);
+        return luckItemId;
+    }
+
+    /**
+     * 生成抽奖索引
+     *
+     * @param maxIndex
+     * @return
+     */
+    public int randomLotteryIndex(int maxIndex) {
+        ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
+        return threadLocalRandom.nextInt(0, maxIndex);
+    }
+
+    /**
+     * 校验是否满足抽奖条件
+     *
+     * @param lotteryActivityId
+     * @param userId
+     * @return 满足抽奖条件返回抽奖需要消耗的元气值个数
+     */
+    private int validateLotteryDrawCondition(String lotteryActivityId, String userId, int times) {
+
+        // 不需要检验元气
+        return Integer.MAX_VALUE;
+    }
+
+    /**
+     * 校验抽奖活动
+     */
+    private LotteryActivityVO validateLotteryActivity(String lotteryActivityId) {
+        LotteryActivityVO lotteryActivity = lotteryCacheService.getLotteryActivityDetailFromCache(lotteryActivityId);
+        Assert.isTrue(lotteryActivity != null, "活动不存在!");
+        // 校验活动是否进行中
+        lotteryActivity.validateRunningStatus();
+        // 校验活动积分配置
+        lotteryActivity.validateIntegral();
+        return lotteryActivity;
+    }
+
+    /**
+     * 中奖结果加入队列,排队处理
+     *
+     * @param lotteryActivityId
+     * @param userId
+     * @param itemId
+     */
+    private void handleLotteryOrder(String userId, String lotteryActivityId, Long itemId) {
+        LotteryOrderQueueDTO lotteryOrderQueueDTO = new LotteryOrderQueueDTO();
+        lotteryOrderQueueDTO.setActivityId(lotteryActivityId);
+        lotteryOrderQueueDTO.setItemId(itemId);
+        lotteryOrderQueueDTO.setUserId(userId);
+        // 消息队列处理订单
+        lotteryOrderQueue.submit(lotteryOrderQueueDTO);
+    }
+}

+ 97 - 0
src/main/java/com/webchat/service/queue/LotteryOrderQueue.java

@@ -0,0 +1,97 @@
+package com.webchat.service.queue;
+
+import com.webchat.common.enums.RedisKeyEnum;
+import com.webchat.common.util.JsonUtil;
+import com.webchat.service.lottery.LotteryOrderService;
+import com.webchat.service.queue.dto.LotteryOrderQueueDTO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/13 03:57
+ * @description
+ * 消息中心消息队列
+ * 设置@Lazy为false,因为server这边默认bean懒加载,很有可能一开始就执行不了初始化方法
+ */
+@Component
+@Lazy(value = false)
+@Slf4j
+public class LotteryOrderQueue extends AbstractRedisQueue<LotteryOrderQueueDTO> {
+
+    @Autowired
+    private LotteryOrderService lotteryOrderService;
+
+    @PostConstruct
+    public void initBean() {
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                LotteryOrderQueue.this.schedule();
+            }
+        }).start();
+    }
+
+    @Override
+    protected int getPoolSize() {
+        return 5;
+    }
+
+    /**
+     * 从消息队列redis读取数据的超时时间
+     * @return
+     */
+    @Override
+    protected long getTimeout() {
+        return 10000;
+    }
+
+    /**
+     * 一个任务元素重试的次数
+     * @return 默认不重试
+     */
+    @Override
+    protected int getRetryTimes() {
+        return 1;
+    }
+
+    /**
+     * 一个任务最多被重返队列的次数
+     * @return
+     */
+    @Override
+    protected int getBackQueueTimes() {
+        return 0;
+    }
+
+    @Override
+    protected LotteryOrderQueueDTO convert(String s) {
+        return JsonUtil.fromJson(s, LotteryOrderQueueDTO.class);
+    }
+
+    @Override
+    protected String getQueueName() {
+        return RedisKeyEnum.QUEUE_LOTTERY_ORDER_MESSAGE.getKey();
+    }
+
+    @Override
+    protected void receive(LotteryOrderQueueDTO data) {
+        if (data == null) {
+            log.info("******QueueName:{}, 消费到了空消息******", getQueueName());
+            return;
+        }
+        log.info("【幸运抽奖订单队列】开始处理消息, taskId:{}, message:{}", data.getTaskId(), data);
+        lotteryOrderService.handleLotteryOrder(data);
+        log.info("【幸运抽奖订单队列】消息处理成功. taskId:{}", data.getTaskId());
+    }
+
+    @Override
+    protected void error(LotteryOrderQueueDTO data, Exception ex) {
+        log.error("【幸运抽奖订单队列】消费出错 taskId:{}, ex:{}", data.getTaskId(), ex);
+    }
+}

+ 28 - 0
src/main/java/com/webchat/service/queue/dto/LotteryOrderQueueDTO.java

@@ -0,0 +1,28 @@
+package com.webchat.service.queue.dto;
+
+import lombok.Data;
+
+/**
+ * @Author 程序员七七
+ * @webSite https://www.coderutil.com
+ * @Date 2022/11/17 00:41
+ * @description
+ */
+@Data
+public class LotteryOrderQueueDTO extends BaseQueueDTO {
+
+    /**
+     * 中奖用户id
+     */
+    private String userId;
+
+    /**
+     * 中奖商品id
+     */
+    private Long itemId;
+
+    /**
+     * 活动id
+     */
+    private String activityId;
+}

+ 72 - 0
src/main/resources/static/css/admin/console.css

@@ -155,4 +155,76 @@ button:hover {
     border: 0px;
     border-radius: 2px;
     box-shadow: 0px 0px 5px #71adf5;
+}
+.cms-bar-btn {
+    position: relative;
+    padding: 0px 25px;
+    height: 40px;
+    border-radius: 5px;
+    border: none;
+    color: white;
+    background-color: #1d59f1;
+    margin-left: 10px;
+    opacity: 0.9;
+}
+.cms-bar-btn:hover {
+    cursor: pointer;
+    opacity: 1;
+}
+.cms-edit-btn,
+.cms-del-btn,
+.cms-recommend-btn,
+.cms-cancel-recommend-btn,
+.cms-paid-btn,
+.cms-new-btn,
+.cms-publish-btn,
+.cms-cancel-paid-btn {
+    position: relative;
+    padding: 0px 15px;
+    height: 30px;
+    border-radius: 5px;
+    border: none;
+    color: white;
+    background-color: #049406;
+    margin-left: 10px;
+    opacity: 0.9;
+}
+.cms-del-btn {
+    background-color: #de5454;
+}
+.cms-recommend-btn {
+    background-color: #965806;
+}
+.cms-cancel-recommend {
+    background-color: #e19025;
+}
+.cms-paid-btn {
+    background-color: #07569f;
+}
+.cms-cancel-paid-btn {
+    background-color: #0766be;
+}
+.cms-new-btn {
+    background-color: #868681;
+}
+.cms-publish-btn {
+    background-color: #e51b4a;
+}
+.cms-edit-btn:hover,
+.cms-del-btn:hover,
+.cms-recommend-btn:hover,
+.cms-cancel-recommend-btn:hover,
+.cms-paid-btn:hover,
+.cms-new-btn:hover,
+.cms-publish-btn:hover,
+.cms-cancel-paid-btn:hover {
+    cursor: pointer;
+    opacity: 1;
+}
+.search-condition-elem {
+    position: relative;
+    width: 200px;
+    height: 40px;
+    border-radius: 3px;
+    border: 1px solid whitesmoke;
 }

+ 7 - 0
src/main/resources/static/css/common/common.css

@@ -53,4 +53,11 @@ button, input, optgroup, option, select, textarea {
     overflow: hidden;
     z-index: 1999;
     background-color: whitesmoke;
+}
+.div-1190px {
+    position: relative;
+    width: 1190px;
+    min-height: 10px;
+    height: auto;
+    background-color: transparent;
 }

+ 26 - 7
src/main/resources/templates/admin/console.html

@@ -14,10 +14,20 @@
     <link rel="bookmark" href="/favicon.ico" />
     <link href="/css/common/common.css" rel="stylesheet" type="text/css" />
     <link href="/css/admin/console.css" rel="stylesheet" type="text/css"/>
+    <link href="/css/lottery/cms.css" rel="stylesheet" type="text/css"/>
     <link href="/ref/layui-v2.6.8/layui/css/layui.css" rel="stylesheet" type="text/css" />
     <script src="/ref/layui-v2.6.8/layui/layui.js" type="text/javascript"></script>
     <script src="/ref/jquery/jquery-3.4.1.js" type="text/javascript"></script>
     <script src="/js/client/auth.js" type="text/javascript"></script>
+
+    <script>
+        var currentActivityId = '';
+
+        function loadFrame(path) {
+            $("#console-body").load(path);
+        }
+    </script>
+
 </head>
 
 <body style="background-color: whitesmoke">
@@ -33,29 +43,38 @@
         <ul class="layui-nav layui-nav-tree layui-inline" style="margin-top: 20px; width: 240px;border: 0px;
         text-align: left" lay-filter="demo" id="menu-ul">
             <li class="layui-nav-item">
-                <a href="/admin/user-manage" target="_body">用户管理</a>
+                <a onclick="loadFrame('/admin/user-manage')" target="_body">用户管理</a>
             </li>
             <li class="layui-nav-item">
-                <a href="/admin/user-wallet-manage" target="_body">钱包管理</a>
+                <a onclick="loadFrame('/admin/user-wallet-manage')"  target="_body">钱包管理</a>
             </li>
             <li class="layui-nav-item">
-                <a href="/admin/public-account-article" target="_body">公众号文章管理</a>
+                <a onclick="loadFrame('/admin/public-account-article')"  target="_body">公众号文章管理</a>
             </li>
             <li class="layui-nav-item">
-                <a href="/admin/slide-manage" target="_body">滑块验证</a>
+                <a onclick="loadFrame('/admin/slide-manage')" target="_body">滑块验证</a>
             </li>
             <li class="layui-nav-item">
-                <a href="/admin/mess-manage" target="_body">消息审核</a>
+                <a onclick="loadFrame('/admin/mess-manage')" target="_body">消息审核</a>
+            </li>
+            <li class="console-menu-li-l1">
+                <p class="menu1 menu" id="menu-activity"><i class="layui-icon">&#xe66f;</i>      抽奖活动 <i class="layui-icon menu-more-icon">&#xe61a;</i></p>
+                <ul class="console-menu-ul-l2" style="padding-left: 40px; line-height:30px; margin-top: 15px;">
+                    <a onclick="loadFrame('/admin/lottery-activity')"><li class="console-menu-li-l2" id="cms-activity">活动管理</li></a>
+                    <a onclick="loadFrame('/admin/lottery-order')"><li class="console-menu-li-l2" id="cms-order">抽奖订单</li></a>
+                </ul>
             </li>
         </ul>
     </div>
     <!-- 控制台主体 -->
-    <iframe id="console-body" name="_body" src="/admin/default"
-            style="background-color: white; padding: 20px; border-radius: 10px 10px 0px 0px; overflow: hidden"></iframe>
+    <div id="console-body" style="background-color: white; padding: 20px; border-radius: 10px 10px 0px 0px; overflow: hidden"></div>
 </center>
 </body>
 
 <script>
+
+    loadFrame("/admin/default");
+
     var userId;
     $.ajax({
         url:"/api/user/getCurrentAdminUserInfo",

+ 0 - 1
src/main/resources/templates/admin/default.html

@@ -2,7 +2,6 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
-    <link href="/css/ref/common.css" rel="stylesheet" type="text/css" />
     <link href="/css/admin/console.css" rel="stylesheet" type="text/css"/>
 
     <style>

+ 291 - 0
src/main/resources/templates/admin/lottery-activity.html

@@ -0,0 +1,291 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="referrer" content="no-referrer"/>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
+    <meta http-equiv="pragma" content="no-cache">
+    <meta http-equiv="cache-control" content="no-cache">
+    <meta http-equiv="expires" content="0">
+
+    <link rel="shortcut icon" href="/favicon.ico"/>
+    <link rel="bookmark" href="/favicon.ico"/>
+
+
+    <link href="/css/common/common.css" rel="stylesheet" type="text/css" />
+    <link href="/css/admin/console.css" rel="stylesheet" type="text/css"/>
+    <link href="/ref/layui-v2.6.8/layui/css/layui.css" rel="stylesheet" type="text/css" />
+    <script src="/ref/layui-v2.6.8/layui/layui.js" type="text/javascript"></script>
+    <script src="/ref/jquery/jquery-3.4.1.js" type="text/javascript"></script>
+    <script src="/js/common/common.js" type="text/javascript"></script>
+</head>
+<body style="overflow-x: hidden">
+<center>
+    <div class="aiqr-container" style="margin-bottom: 0px; padding-top: 40px; width: calc(100% - 100px); margin-bottom: 20px">
+        <div class="cms-manage-title">
+                <input class="search-condition-elem" style="margin-top: 0px" id="search-name" placeholder="活动名称">
+                <select class="search-condition-elem" style="margin-top: 0px"  id="search-status">
+                    <option value="">全部状态</option>
+                    <option value="1">筹备中</option>
+                    <option value="2">进行中</option>
+                    <option value="3">已结束</option>
+                    <option value="4">已删除</option>
+                </select>
+                <button class="cms-bar-btn" id="search-btn">检索</button>
+                <button class="cms-bar-btn" id="add-lottery-activity-btn" style="background-color: orangered; box-shadow: 0px 0px 5px #e56535;">创建抽奖活动</button>
+        </div>
+        <table class="account-apply-tab layui-table" id="search-result-tab">
+            <thead>
+            <tr>
+                <th style="width: 150px">活动业务编号</th>
+                <th style="width: 100px">活动名称</th>
+                <th style="width: 80px">封面</th>
+                <th style="width: 200px">活动描述</th>
+                <th style="width: 200px">基础配置</th>
+                <th style="width: 80px">状态</th>
+                <th>创建时间</th>
+                <th>操作</th>
+            </tr>
+            </thead>
+            <tbody>
+
+            </tbody>
+        </table>
+        <div id="page"></div>
+        <div style="clear: both"></div>
+    </div>
+</center>
+</body>
+
+<script>
+    var firstSearch = true;
+    var curr = 0, size = 10, total = 0;
+    $("#add-lottery-activity-btn").on('click', function() {
+        layer.closeAll();
+        layer.open({
+            type: 1,
+            title: false,
+            skin: 'layui-layer-demo', //样式类名
+            closeBtn: 0, //不显示关闭按钮
+            anim: 2,
+            area: ['380px', '560px'],
+            shadeClose: true, //开启遮罩关闭
+            content: '<div class=\"popup-module-header-div\" style="height: 70px;">' +
+                '<p class=\"popup-module-title\" style="padding-top: 20px">创建抽奖活动</p>' +
+                '</div>' +
+                '<div class=\"popup-module-core-div\" style="margin-top: -40px">' +
+                '<div class="popup-module-core-div-item">' +
+                '<input class="popup-input-number" id="create-activity-name" placeholder="活动名称(30字以内)">' +
+                '</div>' +
+                '<div class="popup-module-core-div-item" style="height: 100px; overflow: hidden">' +
+                '<textarea class="allDiv" id="create-activity-description" style="border: 0px; padding: 5px; line-height: 23px; font-size: 14px; height: 100px; width: 260px; resize: none" placeholder="活动描述"></textarea>' +
+                '</div>' +
+                '<div class="popup-module-core-div-item layui-upload" style="width: 160px; height: 100px" id="upload-activity-cover">' +
+                '<div style="width: 160px; border: 1px solid #f5f5f5; height: 100px; text-align: center; color: #c7c7c7; font-size: 20px; font-weight: 700; line-height: 100px">' +
+                '<img style="position: absolute; left:0px; top:0px; width: 100%; height: 100%; border: 0px" id="activity-cover">' +
+                '活动主图 <i class="layui-icon" style="font-size: 25px; margin-right: 20px;">&#xe60d;</i>   ' +
+                '</div>'+
+                '</div>' +
+                '<div class="popup-module-core-div-item" style=\"border: 0px; margin-top: 20px;\">' +
+                '<button class=\"popup-btn\" onclick=\"createLotteryActivity($(\'#create-activity-name\').val(), $(\'#create-activity-description\').val(), $(\'#activity-cover\').attr(\'src\'))\">创建抽奖活动</button>' +
+                '</div>' +
+                '</div>' +
+                '</div>'
+        });
+        layui.use(['upload'], function(){
+            var $ = layui.jquery
+                ,upload = layui.upload
+            upload.render({
+                elem: '#upload-activity-cover'
+                ,url: '/api/file/moment/upload' //改成您自己的上传接口
+                ,accept: 'images'
+                ,acceptMime: '*',
+                done: function(res){
+                    if(!res.success){
+                        return layer.msg('上传失败');
+                    } else {
+                        $('#activity-cover').attr('src', res.data.url);
+                    }
+                }
+            });
+        });
+    })
+
+
+    function createLotteryActivity(name, description, cover) {
+        if (name == '') {
+            layer.msg("活动名不能为空");
+            return;
+        }
+        if (description == '') {
+            layer.msg("描述不能为空");
+            return;
+        }
+        if (cover == '') {
+            layer.msg("封面不能为空");
+            return;
+        }
+        $.ajax({
+            url: "/api/lottery/activity/save",
+            type: "post",
+            dataType: "json",
+            contentType: "application/json;charset=utf-8",
+            data: JSON.stringify({
+                name: name,
+                description: description,
+                cover: cover,
+            }),
+            success:function (data) {
+                data = eval(data);
+                if (data.success){
+                    layer.msg("创建成功");
+                    pageNo = 1;
+                    searchList();
+                    layer.closeAll();
+                } else {
+                    layer.msg(data.msg);
+                }
+            },error:function () {
+                layer.msg("服务端异常");
+            }
+        })
+    }
+
+    $("#search-btn").on('click', function() {
+        curr = 1;
+        searchList();
+    })
+
+    searchList();
+    function searchList() {
+        $("#search-result-tab").children("tbody").empty();
+        $.ajax({
+            url:"/api/lottery/activity/page?pageNo="+(curr == 0 ? 1 : curr)+"&pageSize="+size+"&name="+$("#search-name").val()+"&status="+$("#search-status").val(),
+            type:"get",
+            success:function (data) {
+                data = eval(data);
+                if (data.success){
+                    $.each(data.data, function (index, value) {
+                        var tr = "<tr>"+
+                            "<td>"+value.activityId+"</td>"+
+                            "<td>"+value.name+"</td>"+
+                            "<td><img style='height: 40px' src='"+value.cover+"'></td>"+
+                            "<td>"+value.description+"</td>"+
+                            "<td>" +
+                            "   <a  onclick=\"currentActivityId = '"+value.activityId+"'; loadFrame('/admin/lottery-cms-item');\" style='margin-right: 10px; color: #0b5eaf'>奖品配置</a>" +
+                            "</td>"+
+                            "<td>"+value.statusName+"</td>"+
+                            "<td>"+formatMsgTime(value.createTime)+"</td>"+
+                            "<td style='width: 200px;'>";
+                        if (value.status == 1){
+                            tr+="<button class=\"cms-publish-btn\"  onclick=\"updateStatus('"+value.activityId+"', 2)\">发布</button>";
+                            tr+="<button class=\"cms-recommend-btn\" onclick=\"updateStatus('"+value.activityId+"', 4)\">删除</button>";
+                        } else if (value.status == 2){
+                            tr+="<button class=\"cms-new-btn\" onclick=\"updateStatus('"+value.activityId+"', 3)\">暂停</button>";
+                        } else if (value.status == 3){
+                            tr+="<button class=\"cms-recommend-btn\" onclick=\"updateStatus('"+value.activityId+"', 4)\">删除</button>";
+                        } else if (value.status == 4){}
+                        tr+="</td></tr>";
+                        if (!firstSearch) {
+                            $("#search-result-tab").children("tbody").append(tr);
+                        }
+                    });
+                    if (curr == 0 || data.total != total) {
+                        curr = 1;
+                        firstSearch = false;
+                        total = data.total;
+                        initPage();
+                    }
+                }
+            },
+            error:function () {
+                layer.msg("error");
+            }
+        })
+    }
+
+    function initPage() {
+        layui.use('laypage', function(){
+            var laypage = layui.laypage;
+            laypage.render({
+                elem: 'page',
+                count: total,
+                limit: size,
+                curr: curr,
+                jump: function(obj){
+                    curr = obj.curr;
+                    searchList();
+                }
+            });
+        })
+    }
+
+    function updateStatus(activityId, status) {
+        if (activityId == '') {
+            layer.msg("活动ID为空");
+            return;
+        }
+        if (status == '') {
+            layer.msg("变更状态为空");
+            return;
+        }
+        $.ajax({
+            url: "/api/lottery/activity/updateStatus",
+            type: "post",
+            dataType: "json",
+            contentType: "application/json;charset=utf-8",
+            data: "{\"activityId\":\""+activityId+"\", \"status\": "+status+"}",
+            success:function (data) {
+                data = eval(data);
+                if (data.success){
+                    searchList();
+                } else {
+                    layer.msg(data.msg);
+                }
+            },error:function () {
+                layer.msg("服务端异常");
+            }
+        })
+    }
+</script>
+<style>
+    .layui-laypage a, .layui-laypage span {
+        display: inline-block;
+        *display: inline;
+        *zoom: 1;
+        vertical-align: middle;
+        padding: 3px 13px;
+        height: 28px;
+        line-height: 28px;
+        margin: 0px 5px;
+        border-radius: 4px;
+        background-color: #fff;
+        color: #4e6ef2;
+        font-size: 14px;
+        border: none;
+    }
+    .layui-laypage a:hover, .layui-laypage span:hover {
+        background: #4e6ef2;
+        color: #fff;
+    }
+    .layui-laypage .layui-laypage-curr .layui-laypage-em {
+        position: absolute;
+        left: -1px;
+        top: -1px;
+        padding: 1px;
+        width: 100%;
+        height: 100%;
+        border-radius: 4px;
+        background: #4e6ef2;
+        color: #fff;
+        font-weight: normal;
+    }
+    .layui-laypage-next, .layui-laypage-prev {
+        border-radius: 4px;
+    }
+    .layui-laypage-default {
+        float: right;
+    }
+</style>
+</html>

+ 316 - 0
src/main/resources/templates/admin/lottery-cms-item.html

@@ -0,0 +1,316 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="referrer" content="no-referrer"/>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
+    <meta http-equiv="pragma" content="no-cache">
+    <meta http-equiv="cache-control" content="no-cache">
+    <meta http-equiv="expires" content="0">
+
+    <link rel="shortcut icon" href="/favicon.ico"/>
+    <link rel="bookmark" href="/favicon.ico"/>
+
+
+    <link href="/css/common/common.css" rel="stylesheet" type="text/css" />
+    <link href="/css/admin/console.css" rel="stylesheet" type="text/css"/>
+    <link href="/ref/layui-v2.6.8/layui/css/layui.css" rel="stylesheet" type="text/css" />
+    <script src="/ref/layui-v2.6.8/layui/layui.js" type="text/javascript"></script>
+    <script src="/ref/jquery/jquery-3.4.1.js" type="text/javascript"></script>
+    <script src="/js/common/common.js" type="text/javascript"></script>
+</head>
+<body style="overflow-x: hidden">
+<center>
+    <div class="aiqr-container" style="margin-bottom: 0px; padding-top: 40px; width: calc(100% - 100px); margin-bottom: 20px">
+        <div class="cms-manage-title">
+            <div style="float: right">
+                <input class="search-condition-elem" style="margin-top: 0px" id="search-name" placeholder="奖品名">
+                <input class="search-condition-elem" style="margin-top: 0px" id="search-activityId" placeholder="活动id">
+                <select class="search-condition-elem" style="margin-top: 0px" id="search-type">
+                    <option value="">全部状态</option>
+                    <option value="1">积分</option>
+                    <option value="2">实体商品</option>
+                    <option value="3">现金红包</option>
+                </select>
+                <button class="cms-bar-btn" id="add-lottery-item-btn" style="background-color: orangered; box-shadow: 0px 0px 5px #e56535;">新增奖品</button>
+            </div>
+        </div>
+        <table class="account-apply-tab layui-table" id="search-result-tab">
+            <thead>
+            <tr>
+                <th style="width: 50px">奖品编号</th>
+                <th style="width: 120px">活动编号</th>
+                <th style="width: 100px">奖品名</th>
+                <th style="width: 100px">类型</th>
+                <th style="width: 100px">icon</th>
+                <th style="width: 100px">封面图</th>
+                <th style="width: 70px">槽位</th>
+                <th style="width: 70px">总库存</th>
+                <th style="width: 95px">时间</th>
+            </tr>
+            </thead>
+            <tbody>
+
+            </tbody>
+        </table>
+        <div id="page"></div>
+        <div style="clear: both"></div>
+    </div>
+</center>
+</body>
+
+<script>
+    var activityId = currentActivityId;
+    if (activityId != '') {
+        $("#search-activityId").val(activityId);
+    }
+
+    var firstSearch = true;
+    var curr = 0, size = 10, total = 0;
+
+
+    $("#add-lottery-item-btn").on('click', function () {
+        addLotteryItem();
+    })
+
+    function addLotteryItem() {
+        layer.open({
+            type: 1,
+            title: false,
+            skin: 'layui-layer-demo', //样式类名
+            closeBtn: 0, //不显示关闭按钮
+            anim: 2,
+            area: ['380px', '716px'],
+            shadeClose: true, //开启遮罩关闭
+            content: '<div class=\"popup-module-header-div\">' +
+                '<p class=\"popup-module-title\">创建抽奖奖品</p>' +
+                '<p class=\"popup-module-desc\">抽奖奖品必须8个额</p>' +
+                '</div>' +
+                '<div class=\"popup-module-core-div\">' +
+                '<div class="popup-module-core-div-item">' +
+                '<input class="popup-input-number" id="activity-id" placeholder="活动id" value="'+activityId+'">' +
+                '</div>' +
+                '<div class="popup-module-core-div-item">' +
+                '<select class="popup-input-number" style="width: 100%; height: 100%; border: 0px" id="item-type">' +
+                '   <option value="">类型</option>' +
+                // '   <option value="1">猿气值</option>' +
+                '   <option value="2">实体商品</option>' +
+                '   <option value="3">现金红包</option>' +
+                '</select>' +
+                '</div>' +
+                '<div class="popup-module-core-div-item">' +
+                '<input class="popup-input-number" id="item-name" placeholder="奖品名称,如: 猿气值 X1">' +
+                '</div>' +
+                '<div class="popup-module-core-div-item">' +
+                '<input class="popup-input-number" id="item-slot" placeholder="卡槽, 支持1 ~ 8">' +
+                '</div>' +
+                '<div class="popup-module-core-div-item">' +
+                '<input class="popup-input-number" id="item-stock" placeholder="库存总量,大于0">' +
+                '</div>' +
+                '<div class="popup-module-core-div-item layui-upload" style="width: 50px; height: 50px" id="upload-item-icon">' +
+                '<div style="width: 40px; border: 1px solid #f5f5f5; height: 50px; text-align: center; color: #c7c7c7; font-size: 20px; font-weight: 700; line-height: 100px">' +
+                '<img style="position: absolute; left:0px; top:0px; width: 50px; height: 100%; border: 0px" id="item-icon">' +
+                '</div>' +
+                '</div>' +
+                '<div class="popup-module-core-div-item layui-upload" style="width: 160px; height: 100px" id="upload-item-cover">' +
+                '<div style="width: 160px; border: 1px solid #f5f5f5; height: 100px; text-align: center; color: #c7c7c7; font-size: 20px; font-weight: 700; line-height: 100px">' +
+                '<img style="position: absolute; left:0px; top:0px; width: 100%; height: 100%; border: 0px" id="item-cover">' +
+                '主图 <i class="layui-icon" style="font-size: 25px; margin-right: 20px;">&#xe60d;</i>   ' +
+                '</div>' +
+                '</div>' +
+                '<div class="popup-module-core-div-item" style=\"border: 0px\">' +
+                '<button class=\"popup-btn\" onclick=\"updateLotteryItem($(\'#activity-id\').val(), $(\'#item-type\').val(), ' +
+                '$(\'#item-name\').val(), $(\'#item-slot\').val(), $(\'#item-stock\').val(), $(\'#item-icon\').attr(\'src\'), $(\'#item-cover\').attr(\'src\'))\">创建奖品</button>' +
+                '</div>' +
+                '</div>' +
+                '</div>'
+        });
+        layui.use(['upload'], function(){
+            var $ = layui.jquery
+                ,upload = layui.upload
+            //常规使用 - 普通图片上传
+
+            upload.render({
+                elem: '#upload-item-cover'
+                ,url: '/api/file/moment/upload' //改成您自己的上传接口
+                ,accept: 'images'
+                ,acceptMime: '*'
+                ,done: function(res){
+                    if(!res.success){
+                        return layer.msg('上传失败');
+                    } else {
+                        $('#item-cover').attr('src', res.data.url);
+                    }
+                }
+            });
+        });
+        layui.use(['upload'], function(){
+            var $ = layui.jquery
+                ,upload = layui.upload
+            //常规使用 - 普通图片上传
+            upload.render({
+                elem: '#upload-item-icon'
+                ,url: '/api/file/chat/upload' //改成您自己的上传接口
+                ,headers: {upload_path: "lottery"}
+                ,accept: 'images'
+                ,acceptMime: '*'
+                ,done: function(res){
+                    if(!res.success){
+                        return layer.msg('上传失败');
+                    } else {
+                        $('#item-icon').attr('src', res.data.url);
+                    }
+                }
+            });
+        });
+    }
+
+    function updateLotteryItem(activityId, type, name, slot, stock, icon, cover) {
+        if (activityId == '') {
+            layer.msg("活动id不能为空");
+            return;
+        }
+        if (type == '') {
+            layer.msg("类型不能为空");
+            return;
+        }
+        if (name == '') {
+            layer.msg("名称不能为空");
+            return;
+        }
+        if (slot == '') {
+            layer.msg("卡槽设置不能为空");
+            return;
+        }
+        if (stock == '') {
+            layer.msg("库存不能为空");
+            return;
+        }
+        if (icon == '') {
+            layer.msg("icon不能为空");
+            return;
+        }
+        if (cover == '') {
+            layer.msg("封面不能为空");
+            return;
+        }
+        $.ajax({
+            url: "/api/lottery/item/save",
+            type: "post",
+            dataType: "json",
+            contentType: "application/json;charset=utf-8",
+            data: "{\"activityId\":\""+activityId+"\",\"name\":\""+name+"\",\"icon\":\""+icon+"\",\"cover\":\""+cover+"\", \"stock\": "+stock+",\"slot\": "+slot+",\"type\": "+type+"}",
+            success:function (data) {
+                data = eval(data);
+                if (data.success){
+                    layer.closeAll();
+                    layer.msg("操作成功");
+                    pageNo = 1;
+                    searchList();
+                } else {
+                    layer.msg(data.msg);
+                }
+            },error:function () {
+                layer.msg("服务端异常");
+            }
+        })
+    }
+
+    $("#search-list-btn").on('click', function() {
+        curr = 1;
+        searchList();
+    })
+
+    searchList();
+    function searchList() {
+        $("#search-result-tab").children("tbody").empty();
+        $.ajax({
+            url:"/api/lottery/item/page?name="+$("#search-name").val()+"&type="+$("#search-type").val()+"&pageNo="+(curr == 0 ? 1 : curr)+"&pageSize="+size+"&activityId="+$("#search-activityId").val(),
+            type:"get",
+            success:function (data) {
+                data = eval(data);
+                if (data.success){
+                    $.each(data.data, function (index, value) {
+                        var tr = "<tr>"+
+                            "<td>"+value.id+"</td>"+
+                            "<td>"+value.activityId+"</td>"+
+                            "<td>"+value.name+"</td>"+
+                            "<td>"+value.typeName+"</td>"+
+                            "<td><img style='height: 40px' src='"+value.icon+"'></td>"+
+                            "<td><img style='height: 40px' src='"+value.cover+"'></td>"+
+                            "<td>"+value.slot+"</td>"+
+                            "<td>"+value.stock+"</td>"+
+                            "<td>"+formatMsgTime(value.createTime)+"</td>"+
+                            "</tr>";
+                        if (!firstSearch) {
+                            $("#search-result-tab").children("tbody").append(tr);
+                        }
+                    })
+                    if (curr == 0 || data.total != total) {
+                        curr = 1;
+                        firstSearch = false;
+                        total = data.total;
+                        initPage();
+                    }
+                } else {}
+            },error:function () {}
+        })
+    }
+
+    function initPage() {
+        layui.use('laypage', function(){
+            var laypage = layui.laypage;
+            laypage.render({
+                elem: 'page',
+                count: total,
+                limit: size,
+                curr: curr,
+                jump: function(obj){
+                    curr = obj.curr;
+                    searchList();
+                }
+            });
+        })
+    }
+</script>
+<style>
+    .layui-laypage a, .layui-laypage span {
+        display: inline-block;
+        *display: inline;
+        *zoom: 1;
+        vertical-align: middle;
+        padding: 3px 13px;
+        height: 28px;
+        line-height: 28px;
+        margin: 0px 5px;
+        border-radius: 4px;
+        background-color: #fff;
+        color: #4e6ef2;
+        font-size: 14px;
+        border: none;
+    }
+    .layui-laypage a:hover, .layui-laypage span:hover {
+        background: #4e6ef2;
+        color: #fff;
+    }
+    .layui-laypage .layui-laypage-curr .layui-laypage-em {
+        position: absolute;
+        left: -1px;
+        top: -1px;
+        padding: 1px;
+        width: 100%;
+        height: 100%;
+        border-radius: 4px;
+        background: #4e6ef2;
+        color: #fff;
+        font-weight: normal;
+    }
+    .layui-laypage-next, .layui-laypage-prev {
+        border-radius: 4px;
+    }
+    .layui-laypage-default {
+        float: right;
+    }
+</style>
+</html>

+ 274 - 0
src/main/resources/templates/admin/lottery-order.html

@@ -0,0 +1,274 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="referrer" content="no-referrer"/>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
+    <meta http-equiv="pragma" content="no-cache">
+    <meta http-equiv="cache-control" content="no-cache">
+    <meta http-equiv="expires" content="0">
+
+    <link rel="shortcut icon" href="/favicon.ico"/>
+    <link rel="bookmark" href="/favicon.ico"/>
+
+
+    <link href="/css/common/common.css" rel="stylesheet" type="text/css" />
+    <link href="/css/admin/console.css" rel="stylesheet" type="text/css"/>
+    <link href="/ref/layui-v2.6.8/layui/css/layui.css" rel="stylesheet" type="text/css" />
+    <script src="/ref/layui-v2.6.8/layui/layui.js" type="text/javascript"></script>
+    <script src="/ref/jquery/jquery-3.4.1.js" type="text/javascript"></script>
+    <script src="/js/common/common.js" type="text/javascript"></script>
+
+</head>
+<body style="overflow-x: hidden">
+<center>
+    <div class="aiqr-container" style="margin-bottom: 0px; padding-top: 40px; width: calc(100% - 100px); margin-bottom: 20px">
+        <div class="cms-manage-title">
+            <div style="float: right">
+                <input class="search-condition-elem" style="margin-top: 0px" id="search-orderId" placeholder="订单id">
+                <input class="search-condition-elem"  style="margin-top: 0px" id="search-activityId" placeholder="活动id">
+                <select class="search-condition-elem" style="margin-top: 0px" id="search-status">
+                    <option value="">全部状态</option>
+                    <option value="1">未兑换</option>
+                    <option value="2">未兑换</option>
+                    <option value="3">已删除</option>
+                </select>
+                <button class="cms-bar-btn" id="search-list-btn">检索</button>
+            </div>
+        </div>
+        <table class="account-apply-tab layui-table" id="search-result-tab">
+            <thead>
+            <tr>
+                <th style="width: 150px">订单号</th>
+                <th style="width: 150px">活动信息</th>
+                <th style="width: 200px">商品信息</th>
+                <th style="width: 200px">中奖用户</th>
+                <th style="width: 70px">中奖时间</th>
+                <th style="width: 65px">兑换时间</th>
+                <th style="width: 65px">状态</th>
+                <th style="width: 65px">操作</th>
+            </tr>
+            </thead>
+            <tbody>
+
+            </tbody>
+        </table>
+        <div id="page"></div>
+        <div style="clear: both"></div>
+    </div>
+</center>
+</body>
+
+<script>
+    var activityId = getUserParamByName("activityId");
+    if (activityId != '') {
+        $("#search-activityId").val(activityId);
+    }
+
+    var firstSearch = true;
+    var curr = 0, size = 10, total = 0;
+
+    function updateIntegral() {
+
+        layer.open({
+            type: 1,
+            title: false,
+            skin: 'layui-layer-demo', //样式类名
+            closeBtn: 0, //不显示关闭按钮
+            anim: 2,
+            area: ['380px', '506px'],
+            shadeClose: true, //开启遮罩关闭
+            content: '<div class=\"login-module-header-div\">' +
+                '<p class=\"login-module-title\">系统积分管理</p>' +
+                '<p class=\"login-module-desc\">积分操作需谨慎</p>' +
+                '</div>' +
+                '<div class=\"login-module-core-div\">' +
+                '<div class="login-module-core-div-item">' +
+                '<select class="login-input-number" style="width: 100%; height: 100%; border: 0px" id="integral-reason"><option value="">类型</option>' +
+                '<option value="SYSTEM_REWARD">系统奖励</option>' +
+                '<option value="SYSTEM_COMPENSATE">系统补偿</option>' +
+                '<option value="SYSTEM_DEDUCTION">系统扣除</option>' +
+                '</select>' +
+                '</div>' +
+                '<div class="login-module-core-div-item">' +
+                '<input class="login-input-number" id="integral-user-id" placeholder="用户ID">' +
+                '</div>' +
+                '<div class="login-module-core-div-item">' +
+                '<input class="login-input-number" id="integral-count" placeholder="积分值" value="0">' +
+                '</div>' +
+                '<div class="login-module-core-div-item" style=\"border: 0px\">' +
+                '<button class=\"login-btn\" onclick=\"updateIntegralBySystem($(\'#integral-reason\').val(), $(\'#integral-user-id\').val(), $(\'#integral-count\').val())\">积分变更</button>' +
+                '</div>' +
+                '</div>' +
+                '</div>'
+        });
+    }
+
+    function updateIntegralBySystem(integralReason, integralUserId, integralCount) {
+        if (integralReason == '') {
+            layer.msg("积分操作类型不能为空");
+            return;
+        }
+        if (integralUserId == '') {
+            layer.msg("操作目标用户不能为空");
+            return;
+        }
+        if (integralCount == '') {
+            layer.msg("积分值不能为空");
+            return;
+        }
+        $.ajax({
+            url: "/api/integral/trans",
+            type: "post",
+            dataType: "json",
+            contentType: "application/json;charset=utf-8",
+            data: "{\"userId\":\""+integralUserId+"\", \"reasonCode\": \""+integralReason+"\", \"count\": "+integralCount+"}",
+            success:function (data) {
+                data = eval(data);
+                if (data.success){
+                    layer.closeAll();
+                    layer.msg("操作成功");
+                    pageNo = 1;
+                    searchList();
+                } else {
+                    layer.msg(data.msg);
+                }
+            },error:function () {
+                layer.msg("服务端异常");
+            }
+        })
+    }
+
+    $("#search-list-btn").on('click', function() {
+        curr = 1;
+        searchList();
+    })
+
+    searchList();
+    function searchList() {
+        $("#search-result-tab").children("tbody").empty();
+        $.ajax({
+            url:"/api/lottery/order/page?orderId="+$("#search-orderId").val()+"&activityId="+$("#search-activityId").val()+"&pageNo="+(curr == 0 ? 1 : curr)+"&pageSize="+size+"&status="+$("#search-status").val(),
+            type:"get",
+            success:function (data) {
+                data = eval(data);
+                if (data.success){
+                    $.each(data.data, function (index, value) {
+                        var tr = "<tr>"+
+                            "<td>"+value.orderId+"</td>"+
+                            "<td>"+value.activity.name+"<br/>"+value.activity.activityId+"</td>"+
+                            "<td><img style='height: 25px; margin-right: 10px;' src='"+value.item.icon+"'>"+value.item.name+"</td>"+
+                            "<td><a href='/coder?userid="+value.user.userId+"'><img style='height: 25px; border-radius: 100px; margin-right: 10px;' src='"+value.user.photo+"'>"+value.user.userName+"</a></td>"+
+                            "<td>"+formatMsgTime(value.luckTime)+"</td>"+
+                            "<td>"+(value.exchangeTime == 0 ? "/" : formatMsgTime(value.exchangeTime))+"</td>"+
+                            "<td>"+value.orderStatusName+"</td>"+
+                            "<td style='width: 200px;'>";
+                        if (value.orderStatus == 1){
+                            tr+="<button class=\"layui-btn layui-btn-sm layui-btn-warm\"  onclick=\"updateStatus('"+value.orderId+"', 2)\">改为已兑换</button>";
+                            tr+="<button class=\"layui-btn layui-btn-sm layui-btn-norma\" onclick=\"updateStatus('"+value.orderId+"', 3)\">删除</button>";
+                        } else if (value.orderStatus == 2){
+                            tr+="<button class=\"layui-btn layui-btn-sm layui-btn-norma\" onclick=\"updateStatus('"+value.orderId+"', 1)\">改为未兑换</button>";
+                            tr+="<button class=\"layui-btn layui-btn-sm layui-btn-norma\" onclick=\"updateStatus('"+value.orderId+"', 3)\">删除</button>";
+                        }
+                        tr+="</td></tr>";
+                        if (!firstSearch) {
+                            $("#search-result-tab").children("tbody").append(tr);
+                        }
+                    })
+                    if (curr == 0 || data.total != total) {
+                        curr = 1;
+                        firstSearch = false;
+                        total = data.total;
+                        initPage();
+                    }
+                } else {}
+            },error:function () {}
+        })
+    }
+
+    function updateStatus(orderId, status) {
+        if (orderId == '') {
+            layer.msg("订单ID为空");
+            return;
+        }
+        if (status == '') {
+            layer.msg("变更状态为空");
+            return;
+        }
+        $.ajax({
+            url: "/api/lottery/order/updateStatus",
+            type: "post",
+            dataType: "json",
+            contentType: "application/json;charset=utf-8",
+            data: "{\"orderId\":\""+orderId+"\", \"status\": "+status+"}",
+            success:function (data) {
+                data = eval(data);
+                if (data.success){
+                    searchList();
+                } else {
+                    layer.msg(data.msg);
+                }
+            },error:function () {
+                layer.msg("服务端异常");
+            }
+        })
+    }
+
+    function initPage() {
+        layui.use('laypage', function(){
+            var laypage = layui.laypage;
+            laypage.render({
+                elem: 'page',
+                count: total,
+                limit: size,
+                curr: curr,
+                jump: function(obj){
+                    curr = obj.curr;
+                    searchList();
+                }
+            });
+        })
+    }
+
+</script>
+<style>
+    .layui-laypage a, .layui-laypage span {
+        display: inline-block;
+        *display: inline;
+        *zoom: 1;
+        vertical-align: middle;
+        padding: 3px 13px;
+        height: 28px;
+        line-height: 28px;
+        margin: 0px 5px;
+        border-radius: 4px;
+        background-color: #fff;
+        color: #4e6ef2;
+        font-size: 14px;
+        border: none;
+    }
+    .layui-laypage a:hover, .layui-laypage span:hover {
+        background: #4e6ef2;
+        color: #fff;
+    }
+    .layui-laypage .layui-laypage-curr .layui-laypage-em {
+        position: absolute;
+        left: -1px;
+        top: -1px;
+        padding: 1px;
+        width: 100%;
+        height: 100%;
+        border-radius: 4px;
+        background: #4e6ef2;
+        color: #fff;
+        font-weight: normal;
+    }
+    .layui-laypage-next, .layui-laypage-prev {
+        border-radius: 4px;
+    }
+    .layui-laypage-default {
+        float: right;
+    }
+</style>
+</html>

+ 99 - 0
src/main/resources/templates/client/header.html

@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="x-ua-compatible" content="IE=edge" >
+    <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"/>
+    <title>coderutil出品幸运抽奖系统</title>
+    <link rel="shortcut icon" href="/favicon.ico" />
+    <link rel="bookmark" href="/favicon.ico" />
+
+
+    <link rel="stylesheet" href="/css/common/common.css">
+    <link href="/ref/layui-v2.6.8/layui/css/layui.css" rel="stylesheet" type="text/css" />
+    <script src="/js/ref/jquery-3.4.1.js" type="text/javascript"></script>
+    <script src="/ref/layui-v2.6.8/layui/layui.js" type="text/javascript"></script>
+    <script src="/js/common/common.js" type="text/javascript"></script>
+
+    <style>
+        .logo {
+            position: absolute;
+            left: 0px;
+            top: 15px;
+            height: 30px;
+        }
+    </style>
+
+    <script>
+        function login() {
+            location.href = "/auth";
+        }
+    </script>
+</head>
+<body>
+<center>
+
+    <div style="height: 60px; width: 100%; background-color: white; border-bottom: 1px solid #eae9e9">
+        <div class="div-1190px">
+            
+            <img src="/image/icon/logo_title.png" class="logo">
+
+            <a class="header-level1-link float-right" id="login-menu" style="margin-right: 0px;">
+                <button style="padding: 7px 10px; border-radius: 100px; margin-top: 13px; font-size: 12px; border: 1px solid #2b73f6; background-color: transparent; color: #2b73f6"
+                onclick="login()">登录/注册</button>
+            </a>
+            <span class="header-level1-link float-right layui-btn-container" id="login-user" style="float: right; margin-right: 0px; display: none; height: 100%">
+                <img src="" id="login-user-photo" class="common-login-user down-user-center" style="width: 30px; margin-top: 13px;">
+            </span>
+
+        </div>
+    </div>
+
+</center>
+
+</body>
+<script>
+    refreshUserInfo();
+    function refreshUserInfo() {
+        $.ajax({
+            url:"/api/user/getCurrentUserInfo",
+            type:"get",
+            success:function (data) {
+                data = eval(data);
+                if (data.success){
+                    data = data.data;
+                    $("#login-menu").hide();
+                    $("#login-user").show();
+                    $("#login-user-photo").attr("src", data.photo);
+                    var roleCode = data.roleCode;
+                    /***
+                     * 登录用户设置
+                     */
+                    layui.use(['dropdown', 'util', 'layer', 'table'], function() {
+                        var dropdown = layui.dropdown;
+                        //用户
+                        dropdown.render({
+                            elem: '#login-user-photo'
+                            , data: [ {
+                                title: roleCode == 2 ? '后台管理' : ''
+                                , id: roleCode == 2 ? 1 : -1
+                            }, {
+                                title: '退出登录'
+                                , id: 2
+                            }]
+                            , click: function (obj) {
+                                if (obj.id == 1) {
+                                    location.href = "/admin/cms";
+                                } else if (obj.id == 2) {
+                                    loginOut();
+                                }
+                            }
+                        });
+                    })
+
+                }
+            },error:function () {}
+        })
+    }
+</script>
+</html>

+ 541 - 0
src/main/resources/templates/client/lottery.html

@@ -0,0 +1,541 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="x-ua-compatible" content="IE=edge" >
+    <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"/>
+    <title>coderutil出品幸运抽奖系统</title>
+    <link rel="shortcut icon" href="/favicon.ico" />
+    <link rel="bookmark" href="/favicon.ico" />
+
+
+    <link rel="stylesheet" href="/css/common/common.css">
+    <link href="/css/lottery/luck.css" rel="stylesheet" type="text/css" />
+
+    <link href="/ref/layui-v2.6.8/layui/css/layui.css" rel="stylesheet" type="text/css" />
+    <script src="/ref/jquery/jquery-3.4.1.js" type="text/javascript"></script>
+    <script src="/ref/layui-v2.6.8/layui/layui.js" type="text/javascript"></script>
+    <script src="/js/common/common.js" type="text/javascript"></script>
+
+    <style>
+        .popup {
+            background-color: transparent;/*背景透明*/
+            box-shadow: 0 0 0 rgba(0,0,0,0);/*前景无阴影*/
+        }
+    </style>
+</head>
+<body>
+<center>
+
+    <div id="header" style="position: fixed; top: 0px; left: 0px; width: 100%; height: 60px; z-index: 1999"></div>
+
+    <div class="luck-body"><div class="div-1190px">
+        <!-- 抽奖器模块 -->
+        <div class="luck-player-module" style="background-image: url('http://coderutil.oss-cn-beijing.aliyuncs.com/bbs-image/file_7ce6e36d2bf44e689a6d078e690e8202.png')">
+            <div class="luck-player-top">
+                <span class="luck-title"><span class="layui-badge" id="activityStatus">进行中</span> <span id="activityName"></span></span>
+            </div>
+            <!-- ali player -->
+            <div class="luck-box">
+                <div class="luck-container">
+                    <div id="luck-container"></div>
+                    <button class="luck-border"></button>
+                    <button class="luck-btn" id="luck-btn">抽奖</button>
+                </div>
+                <div class="luck-reduce-integral-rule">本期每抽奖一次需消耗 <b id="reduce-count">-</b> 猿气值</div>
+                <div class="luck-item-count-container">
+                    <div class="luck-item-count-container-line" style="color: white; font-weight: 700">
+                        <span class="luck-item-rule-left">奖品名称</span>
+                        <span class="luck-item-rule-right">奖品数量</span>
+                    </div>
+                    <div id="luck-item-count-container">
+
+                    </div>
+                </div>
+            </div>
+        </div>
+        <!-- 抽奖结果 -->
+        <div class="luck-recommend-module">
+            <span class="luck-recommend-title">最近中奖 TOP5</span>
+            <div class="winPrizeListContainer">
+                <div class="winPrizeListContainerList" id="winPrizeListContainerList">
+
+                </div>
+
+                <div class="ww-recommend">
+                    <div class="wwads-cn wwads-horizontal" data-id="181" style="max-width: 300px; margin-top:0px;"></div>
+                </div>
+            </div>
+
+        </div>
+
+    </div>
+        <div class="unlogin-luck-mode" id="unlogin-mode">未登录, 没有查看权限!</div>
+        <div class="unlogin-luck-mode" id="end-mode" style="display: none">活动已经结束</div>
+        <div class="unlogin-luck-mode" id="prepare-mode" style="display: none">活动筹备中</div>
+    </div>
+
+    <!-- 奖品清单 -->
+    <div class="luck-list-module"><div class="div-1190px">
+        <div class="luck-list-box">
+            <div class="luck-list-title-box">
+                <span class="luck-list-title" data-v-04c4730d="">本期奖品清单</span>
+            </div>
+
+            <div id="luck-lists">
+
+            </div>
+            <div style="clear: both"></div>
+        </div>
+    </div>
+    </div>
+
+    <!-- 我的本期中奖订单 -->
+    <div class="luck-list-module"><div class="div-1190px">
+        <div class="luck-list-box">
+            <div class="luck-list-title-box">
+                <span class="luck-list-title" data-v-04c4730d="">我的中奖记录</span>
+                <a style="float: right" onclick="masterWeixin()">兑换 <i class="layui-icon">&#xe602;</i> </a>
+            </div>
+            <div id="curr-user-order-lists" class="userOrderMaxHeight">
+
+            </div>
+            <div style="clear: both"></div>
+            <a id="showAllOrderBtn" style="display: none" onclick="$('#curr-user-order-lists').removeClass('userOrderMaxHeight'); $(this).hide();">
+                <p style="height: 50px; line-height: 50px;">
+                    展开查看全部 <i class="layui-icon">&#xe61a;</i>
+                </p>
+            </a>
+        </div>
+    </div></div>
+
+    <!-- 往期活动 -->
+    <div class="luck-list-module"><div class="div-1190px">
+        <div class="luck-list-box">
+            <div class="luck-list-title-box">
+                <span class="luck-list-title" data-v-04c4730d="">往期活动</span>
+            </div>
+            <div id="history-luck-lists">
+
+            </div>
+            <div style="clear: both"></div>
+        </div>
+    </div></div>
+
+    <div style="width: 100%; line-height: 50px; margin-top: 50px;">
+        © 2023 https://www.coderutil.com
+    </div>
+</center>
+
+</body>
+<script>
+
+    $("#header").load("/client/header");
+
+    var index = 1;
+    var redu = 4;
+    var time = 85;
+    var reduceCount = 0;
+    var luckRandomCount = 300;
+    var luckId;
+    var luckIdArr;
+    var luckTimes = 1;
+    var enableLuck = false;
+    var running = false;
+    var itemMap = new HashMap();
+    var activityId = getUserParamByName("id");
+    var historyLastId = '';
+
+    if (activityId == '') {
+        $.ajax({
+            url: "/api/lottery/activity/lastId",
+            type: "get",
+            success: function (data) {
+                data = eval(data);
+                if (data.success) {
+                    activityId = data.data;
+                    initLotteryInfo();
+                    loadActivityOrderList();
+                    loadCurrentUserActivityOrderList();
+                }
+            }
+        })
+    } else {
+        initLotteryInfo();
+        loadActivityOrderList();
+        loadCurrentUserActivityOrderList();
+    }
+
+    function initLotteryInfo() {
+        $.ajax({
+            url: "/api/lottery/activity/detail/" + activityId,
+            type: "get",
+            success: function (data) {
+                data = eval(data);
+                if (data.success) {
+                    data = data.data;
+                    if (data == null) {
+                        layer.msg("活动未开启");
+                        return;
+                    }
+                    var status = data.status;
+                    $("#activityStatus").text(data.statusName);
+                    $("#activityName").text(data.name);
+                    setDisableLuckStateByActivityStatus();
+                    if (status == 1) {
+                        $("#prepare-mode").show();
+                        $("#activityStatus").addClass("layui-bg-orange");
+                    } else if (status == 2) {
+                        setEnableLuckState();
+                    } else if (status == 3) {
+                        $("#end-mode").show();
+                        $("#activityStatus").addClass("layui-bg-cyan");
+                    } else if (status == 4) {
+                        $("#activityStatus").addClass("layui-bg-gray");
+                    }
+                    $.each(data.items, function (index, item) {
+                        var itemElem = "<div class=\"luck-item luck-item-"+item.slot+"\" id=\"luck-item-"+item.slot+"\">\n" +
+                            "                        <img src=\""+item.icon+"\">\n" +
+                            "                        <p class=\"line1ppp\">"+item.name+"</p>\n" +
+                            "                    </div>";
+                        var itemStock = "<div class=\"luck-item-count-container-line\">\n" +
+                            "                        <span class=\"luck-item-rule-left line1ppp\">"+item.name+"</span>\n" +
+                            "                        <span class=\"luck-item-rule-right\">"+item.stock+"</span>\n" +
+                            "                    </div>";
+                        var itemGood = "<div class=\"luck-list\">\n" +
+                            "                    <img class=\"recommend-luck-cover\"\n" +
+                            "                         src=\""+item.cover+"?x-oss-process=image/resize,m_fill,w_250,h_150\">\n" +
+                            "                    <div class=\"title-container\"><span class=\"title\">"+item.name+"</span></div>\n" +
+                            "                    <span class=\"luck-item-count\"><span class=\"layui-badge\">X "+item.stock+"</span></span>\n" +
+                            "                </div>";
+                        $("#luck-container").append(itemElem);
+                        $("#luck-item-count-container").append(itemStock);
+                        $("#luck-lists").append(itemGood);
+                        // 维护奖品信息到map
+                        itemMap.put(item.id, item);
+                    })
+                    $.each(data.sponsors, function (index, sponsor) {
+                        var hrefAttr = "";
+                        if (sponsor.url != null && sponsor.url != '') {
+                            hrefAttr = "href='"+sponsor.url+"'"
+                        }
+                        var sponsorElem = "<a "+hrefAttr+" target=\"_blank\">\n" +
+                            "                    <div class=\"sponsor-container\">\n" +
+                            "                        <img src=\""+sponsor.logo+"?x-oss-process=image/resize,m_fill,w_50,h_50\" class=\"sponsor-logo\">\n" +
+                            "                        <p class=\"sponsor-content line1ppp\">"+sponsor.content+"</p>\n" +
+                            "                    </div>\n" +
+                            "                </a>";
+                        $("#sponsor-lists").append(sponsorElem);
+                    })
+
+                    // 点击开始抽奖
+                    $("#luck-btn").on('click', function () {
+                        var popupContent =
+                            "<div style='width: 540px; height: 130px; background-color: white; border-radius: 10px;'>" +
+                            "<button id='time1LuckBtn'>抽一次 <img src='http://coderutil.oss-cn-beijing.aliyuncs.com/bbs-image/file_75493b4c9d2a40be8b96b3ea00b13793.png' style='height: 30px;'><b style='color: #ee954d'>"+reduceCount+"</b></button>" +
+                            "<button id='time10LuckBtn'>十连抽 <img src='http://coderutil.oss-cn-beijing.aliyuncs.com/bbs-image/file_75493b4c9d2a40be8b96b3ea00b13793.png' style='height: 30px;'><b style='color: #ee954d'>"+(reduceCount * 10)+"</b></button>"+
+                            "</div>";
+                        layer.open({
+                            type: 1,
+                            title: false,
+                            closeBtn: 0,
+                            shadeClose: true,
+                            area:  ['540px', '130px'],
+                            content: popupContent
+                        });
+                        $("#time1LuckBtn").on('click', function () {
+                            layer.closeAll();
+                            startLuckDraw(1);
+                        })
+                        $("#time10LuckBtn").on('click', function () {
+                            layer.closeAll();
+                            startLuckDraw(10);
+                        })
+                    })
+
+                }
+            }, error: function () {
+            }
+        })
+    }
+
+    function startLuckDraw(times) {
+        if (!isEnableLuck()) {
+            layer.msg("活动处于非抽奖状态");
+            return;
+        }
+        if (this.isRunning()) {
+            return;
+        }
+        setDisableLuckState();
+        // 抽奖api
+        var luckApi = "/api/lottery/luckDraw/" + activityId;
+        if (times == 10) {
+            luckApi = "/api/lottery/luckDraw/" + activityId + "/" + times;
+        }
+        luckTimes = times;
+        // 抽奖之前先重置抽奖结果
+        resetLuckResult();
+        $.ajax({
+            url: luckApi,
+            type: "post",
+            success: function (data) {
+                data = eval(data);
+                if (data.success) {
+                    // 获取到抽奖结果
+                    if (times == 1) {
+                        luckId = data.data;
+                    } else if (times == 10) {
+                        luckIdArr = data.data;
+                    } else {
+                        layer.msg("不支持的抽奖个数!");
+                    }
+                }
+            }
+        })
+        initLuckItemSelectedState();
+        initLuckParam();
+        luckAnimation();
+    }
+
+    function resetLuckResult() {
+        luckId="";
+        luckIdArr="";
+    }
+
+    function luckResultSelected(id) {
+        $("#luck-item-"+id).css("background-color", "#93e3ad");
+    }
+
+    function initLuckParam() {
+        luckRandomCount = 300;
+        index = ((++index) % 8 + 1);
+    }
+
+    function initLuckItemSelectedState() {
+        $(".luck-item").css("background-color", "#ffffff");
+    }
+
+    /**
+     * 设置不允许抽奖
+     */
+    function setDisableLuckState() {
+        running = true;
+        $(".luck-btn").text("抽奖中");
+        $(".luck-btn").css("background-color", "#265732");
+    }
+
+    function setDisableLuckStateByActivityStatus() {
+        enableLuck = false;
+        $(".luck-btn").text("暂停");
+        $(".luck-btn").css("background-color", "#265732");
+    }
+
+    /**
+     * 设置允许抽奖
+     */
+    function setEnableLuckState() {
+        running = false;
+        enableLuck = true;
+        $(".luck-btn").text("抽奖");
+        $(".luck-btn").css("background-color", "#58d576");
+    }
+
+    function isRunning() {
+        return running;
+    }
+
+    function isEnableLuck() {
+        return enableLuck;
+    }
+
+    function luckAnimation() {
+        initLuckItemSelectedState();
+        if (luckRandomCount <= 0) {
+            showLuckResult();
+            // luckResultSelected(luckId);
+            setEnableLuckState();
+            // 重新加载活动下的中奖名单
+            loadActivityOrderList();
+            // 重新加载我的中奖记录
+            loadCurrentUserActivityOrderList();
+        } else {
+            var tempId = ((++index) % 8 + 1);
+            luckResultSelected(tempId);
+            luckRandomCount -= redu;
+            setTimeout("luckAnimation()",  time);
+        }
+    }
+
+    function showLuckResult() {
+        if (luckTimes == 1) {
+            var luckItem = itemMap.get(luckId);
+            var popupContent =
+                "<div style='width: 500px; height: 300px; text-align: center; overflow: hidden'>" +
+                "<p style='line-height: 80px; font-size: 30px; color: white; font-weight: 700'>恭喜你,本次抽中</p>" +
+                "<img src=\""+luckItem.icon+"\" style='height: 150px;'>" +
+                "<p style='line-height: 80px; font-size: 20px; color: white; font-weight: 500'>"+luckItem.name+"</p>" +
+                "</div>"+
+                "<p style='width: 500px; height: 30px; margin-top: 20px; text-align: center'>" +
+                "<button onclick='layer.closeAll()' class='close-popup-btn'>" +
+                "<i class=\"layui-icon\" style='font-size: 20px; color: white'>&#x1006;</i>" +
+                "</button></p>";
+            layer.open({
+                type: 1,
+                title: false,
+                closeBtn: 0,
+                shadeClose: false,
+                area:  ['500px', '450px'],
+                skin: 'popup',
+                content: popupContent
+            });
+        } else if (luckTimes == 10) {
+            var popupContent =
+                "<div style='width: 600px; height: 400px; text-align: center; overflow: hidden'>" +
+                "<p style='line-height: 80px; font-size: 25px; color: white; font-weight: 700'>恭喜你,十连抽中</p>";
+            for (var i = 0; i < luckIdArr.length; i++) {
+                var luckItem = itemMap.get(luckIdArr[i]);
+                popupContent +=  "<div class='time10result-container'><div style='height: 80px; width: 120px; text-align: center'><img src=\""+luckItem.icon+"\" style='height: 80px;'></div><p style='line-height: 40px; font-size: 17px; color: white' class='line1ppp'>"+luckItem.name+"</p></div>";
+            }
+            popupContent += "</div>"+
+                "<p style='width: 600px; height: 30px; margin-top: 20px; text-align: center'>" +
+                "<button onclick='layer.closeAll()' class='close-popup-btn'>" +
+                "<i class=\"layui-icon\" style='font-size: 20px; color: white'>&#x1006;</i>" +
+                "</button></p>";
+            layer.open({
+                type: 1,
+                title: false,
+                closeBtn: 0,
+                shadeClose: false,
+                area:  ['600px', '500px'],
+                skin: 'popup',
+                content: popupContent
+            });
+        } else {
+            layer.msg("抽奖参数异常!");
+        }
+    }
+
+    /**
+     * 查询当前用户本期活动所有中奖订单记录
+     */
+    function loadCurrentUserActivityOrderList() {
+        $("#curr-user-order-lists").empty();
+        $.ajax({
+            url: "/api/lottery/order/byCurrUser/"+activityId,
+            type: "get",
+            success: function (data) {
+                data = eval(data);
+                if (data.success) {
+                    data = data.data;
+                    if (data.legend == 0) {
+                        $("#curr-user-order-lists").append("<div style='margin: 50px 0px 100px 0px;'><img src=\"http://coderutil.oss-cn-beijing.aliyuncs.com/bbs-image/file_9983ba5b80bf4b5b9e7c7ea25dee139f.png\" style=\"height: 300px\">\n" +
+                            "            <p style=\"line-height: 60px; color: gray; font-size: 15px;\">暂无中奖记录 ~ </p></div>");
+                        return;
+                    }
+                    $.each(data, function (index, order) {
+                        var meOrder = "<div class=\"curr-user-order\" style=\"text-align: left; width: 100%; line-height: 40px; border-bottom: 1px dashed whitesmoke\">\n" +
+                            "                    <span style=\"width: 100px; margin-right: 20px;\"><span class=\"layui-badge layui-bg-green\">已兑换</span></span>\n" +
+                            "                    <span style=\"width: 200px; margin-right: 30px; font-family: Courier\">"+order.orderId+"</span>\n" +
+                            "                    <span class=\"line1ppp\" style=\"width: 400px;\">"+order.item.name+"</span>\n"+
+                            "                    <span style=\"float: right; color: gray\">中奖时间: "+formatMsgTime(order.luckTime)+"</span>\n";
+                        if (order.exchangeTime != 0) {
+                            meOrder += "                    <span style=\"float: right; margin-right: 30px; width: 200px; color: gray\">兑换时间: "+formatMsgTime(order.exchangeTime)+"</span>\n";
+                        }
+                        meOrder += "                </div>";
+                        $("#curr-user-order-lists").append(meOrder);
+                    })
+                    if (data.length > 10) {
+                        $("#showAllOrderBtn").show();
+                    }
+                }
+            }, error: function () {
+            }
+        })
+    }
+
+    function loadActivityOrderList() {
+        $("#winPrizeListContainerList").empty();
+        $.ajax({
+            url: "/api/lottery/order/byActivity/"+activityId,
+            type: "get",
+            success: function (data) {
+                data = eval(data);
+                if (data.success) {
+                    data = data.data;
+                    if (data.legend == 0) {
+                        $("#winPrizeListContainerList").append("<p style='line-height: 200px'>暂无中奖记录 ~ </p>");
+                        return;
+                    }
+                    $.each(data, function (index, order) {
+                        var order = "<div class=\"winPrize\">\n" +
+                            "                        <p class=\"prize-header\">\n" +
+                            "                            <span class=\"prize-username\"><img src=\""+order.user.photo+"?x-oss-process=image/resize,m_fill,w_20,h_20\">"+order.user.userName+"</span>\n" +
+                            "                            <span class=\"prize-time\">"+formatMsgTime(order.luckTime)+"</span>\n" +
+                            "                        </p>\n" +
+                            "                        <p class=\"prize-detail line1ppp\">\n" +
+                            "                            抽中 <span style=\"color: orangered\">"+order.item.name+"</span>\n" +
+                            "                        </p>\n" +
+                            "                    </div>";
+                        $("#winPrizeListContainerList").append(order);
+                    })
+                }
+            }, error: function () {
+            }
+        })
+    }
+
+    /**
+     * 加载历史活动列表
+     */
+    loadHistoryActivityList();
+    function loadHistoryActivityList() {
+        $.ajax({
+            url: "/api/lottery/activity/history/list",
+            type: "get",
+            success: function (data) {
+                data = eval(data);
+                if (data.success) {
+                    data = data.data;
+                    $.each(data, function (index, activity) {
+                        var statusTag;
+                        var status = activity.status;
+                        if (status == 1) {
+                            statusTag = "<span class=\"layui-badge layui-bg-orange\">"+activity.statusName+"</span>";
+                        } else if (status == 2) {
+                            statusTag = "<span class=\"layui-badge\">"+activity.statusName+"</span>";
+                        } else if (status == 3) {
+                            statusTag = "<span class=\"layui-badge layui-bg-cyan\">"+activity.statusName+"</span>";
+                        } else if (status == 4) {
+                            statusTag = "<span class=\"layui-badge layui-bg-gray\">"+activity.statusName+"</span>";
+                        }
+                        var historyActivity = "<a href='/client/index?id="+activity.activityId+"'><div class=\"luck-list\">\n" +
+                            "                    <img class=\"recommend-luck-cover\"\n" +
+                            "                         src=\""+activity.cover+"?x-oss-process=image/resize,m_fill,w_250,h_150\">\n" +
+                            "                    <div class=\"title-container\"><span class=\"title\">"+activity.name+"</span></div>\n" +
+                            "                    <span class=\"luck-item-count\">"+statusTag+"</span>\n" +
+                            "                </div></a>";
+                        historyLastId = activity.id;
+                        $("#history-luck-lists").append(historyActivity);
+                    })
+                }
+            }, error: function () {
+            }
+        })
+    }
+
+    refreshUserInfo();
+    function refreshUserInfo() {
+        $.ajax({
+            url:"/api/user/getCurrentUserInfo",
+            type:"get",
+            success:function (data) {
+                data = eval(data);
+                if (data.success){
+                    $("#unlogin-mode").hide();
+                }
+            },error:function () {}
+        })
+    }
+</script>
+</html>

+ 5 - 0
src/main/resources/templates/client/video-chat.html

@@ -143,6 +143,11 @@
             "iceServers": [
                 {
                     "urls": "stun:stun.l.google.com:19302"
+                },
+                {
+                    "urls": "turn:8.140.184.12:3478",
+                    "username": "webchat",
+                    "credential": "webchat666",
                 }
             ]
         };