data = new HashMap<>(4);
+ data.put("record", record);
+ data.put("winners", record == null ? null : drawService.winnersOfDraw(record.getId()));
+ return new ResultUtil<>().setData(data);
+ } catch (HiverException e) {
+ return ResultUtil.error(e.getMessage());
+ }
+ }
+
+ @ApiOperation(value = "鎴戠殑涓璁板綍")
+ @RequestMapping(value = "/draw/myWinning", method = RequestMethod.POST)
+ public Result myWinning(@RequestBody PlanetQuery query) {
+ if (StringUtils.isEmpty(query.getRegionId())) {
+ return ResultUtil.error("缂哄皯鍟嗗湀鍙傛暟锛屼粎鍙煡鐪嬫湰鍖哄煙涓璁板綍");
+ }
+ try {
+ return new ResultUtil<>().setData(drawService.myWinning(query.getUserId(), query.getRegionId()));
+ } catch (HiverException e) {
+ return ResultUtil.error(e.getMessage());
+ }
+ }
+
+ @ApiOperation(value = "棰嗗彇涓濂栧姳")
+ @RequestMapping(value = "/draw/receive", method = RequestMethod.POST)
+ public Result receive(@RequestBody PlanetQuery query) {
+ if (StringUtils.isEmpty(query.getRegionId())) {
+ return ResultUtil.error("缂哄皯鍟嗗湀鍙傛暟锛屼粎鍙鍙栨湰鍖哄煙涓");
+ }
+ try {
+ drawService.receive(query.getUserId(), query.getRegionId(), query.getWinnerId());
+ return ResultUtil.success("棰嗗彇鎴愬姛");
+ } catch (HiverException e) {
+ return ResultUtil.error(e.getMessage());
+ }
+ }
+
+ @ApiOperation(value = "鎴戠殑鏄熺悆鍒告槑缁(鍒嗛〉)")
+ @RequestMapping(value = "/ticket/log", method = RequestMethod.POST)
+ public Result ticketLog(@RequestBody PlanetQuery query) {
+ if (StringUtils.isEmpty(query.getUserId())) {
+ return ResultUtil.error("鐢ㄦ埛id涓嶈兘涓虹┖");
+ }
+ if (StringUtils.isEmpty(query.getRegionId())) {
+ return ResultUtil.error(REGION_REQUIRED);
+ }
+ return new ResultUtil<>().setData(
+ ticketService.pageLog(query.getUserId(), query.getRegionId(), query.getPageNumber(), query.getPageSize()));
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetBuff.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetBuff.java
new file mode 100644
index 00000000..fc223b06
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetBuff.java
@@ -0,0 +1,67 @@
+package cc.hiver.mall.planet.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.persistence.Id;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 鐧藉珫鏄熺悆-BUFF閰嶇疆
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-BUFF閰嶇疆")
+@TableName(value = "t_planet_buff", autoResultMap = true)
+public class PlanetBuff implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @TableId
+ @ApiModelProperty(value = "涓婚敭")
+ private String id;
+
+ @ApiModelProperty(value = "鍟嗗湀id 绌轰负鍏ㄥ眬榛樿")
+ private String regionId;
+
+ @ApiModelProperty(value = "绫诲瀷 double/shield/lucky/hunt/stealth")
+ private String type;
+
+ @ApiModelProperty(value = "鍚嶇О")
+ private String name;
+
+ @ApiModelProperty(value = "鎻忚堪")
+ private String description;
+
+ @ApiModelProperty(value = "鍥炬爣")
+ private String icon;
+
+ @ApiModelProperty(value = "娑堣楀埜鏁伴噺")
+ private Integer costTickets;
+
+ @ApiModelProperty(value = "鎸佺画灏忔椂")
+ private Integer durationHours;
+
+ @ApiModelProperty(value = "鏁堟灉鍊")
+ private BigDecimal effectValue;
+
+ @ApiModelProperty(value = "鎺掑簭")
+ private Integer sort;
+
+ @ApiModelProperty(value = "鏄惁鍚敤 1鏄 0鍚")
+ private Integer enabled;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetBuffRecord.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetBuffRecord.java
new file mode 100644
index 00000000..cd5397c6
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetBuffRecord.java
@@ -0,0 +1,68 @@
+package cc.hiver.mall.planet.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.persistence.Id;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 鐧藉珫鏄熺悆-鐢ㄦ埛BUFF璁板綍
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-鐢ㄦ埛BUFF璁板綍")
+@TableName(value = "t_planet_buff_record", autoResultMap = true)
+public class PlanetBuffRecord implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @TableId
+ @ApiModelProperty(value = "涓婚敭")
+ private String id;
+
+ @ApiModelProperty(value = "鐢ㄦ埛id")
+ private String userId;
+
+ @ApiModelProperty(value = "鍟嗗湀id")
+ private String regionId;
+
+ @ApiModelProperty(value = "BUFF閰嶇疆id")
+ private String buffId;
+
+ @ApiModelProperty(value = "绫诲瀷")
+ private String type;
+
+ @ApiModelProperty(value = "鏁堟灉鍊")
+ private BigDecimal effectValue;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "鐢熸晥鏃堕棿")
+ private Date startTime;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "澶辨晥鏃堕棿")
+ private Date endTime;
+
+ @ApiModelProperty(value = "鐘舵 0鐢熸晥 1宸茶繃鏈")
+ private Integer status;
+
+ @ApiModelProperty(value = "鏉ユ簮 buy璐拱 box瀹濈 hunt闃叉姢")
+ private String source;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetDrawRecord.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetDrawRecord.java
new file mode 100644
index 00000000..ff23a5ae
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetDrawRecord.java
@@ -0,0 +1,66 @@
+package cc.hiver.mall.planet.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.persistence.Id;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 鐧藉珫鏄熺悆-寮濂栬褰
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-寮濂栬褰")
+@TableName(value = "t_planet_draw_record", autoResultMap = true)
+public class PlanetDrawRecord implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @TableId
+ @ApiModelProperty(value = "涓婚敭")
+ private String id;
+
+ @ApiModelProperty(value = "濂栨睜id")
+ private String poolId;
+
+ @ApiModelProperty(value = "鍟嗗湀id")
+ private String regionId;
+
+ @ApiModelProperty(value = "鏈熷彿")
+ private String periodNo;
+
+ @ApiModelProperty(value = "濂栨睜閲戦")
+ private BigDecimal poolAmount;
+
+ @ApiModelProperty(value = "鍙備笌浜烘暟")
+ private Integer joinCount;
+
+ @ApiModelProperty(value = "鍒告绘暟")
+ private Integer ticketTotal;
+
+ @ApiModelProperty(value = "涓浜烘暟")
+ private Integer winnerCount;
+
+ @ApiModelProperty(value = "鐘舵 1宸插紑濂")
+ private Integer status;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "寮濂栨椂闂")
+ private Date drawTime;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetDrawWinner.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetDrawWinner.java
new file mode 100644
index 00000000..6ccb8669
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetDrawWinner.java
@@ -0,0 +1,78 @@
+package cc.hiver.mall.planet.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.persistence.Id;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 鐧藉珫鏄熺悆-涓璁板綍
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-涓璁板綍")
+@TableName(value = "t_planet_draw_winner", autoResultMap = true)
+public class PlanetDrawWinner implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @TableId
+ @ApiModelProperty(value = "涓婚敭")
+ private String id;
+
+ @ApiModelProperty(value = "寮濂栬褰昳d")
+ private String drawId;
+
+ @ApiModelProperty(value = "濂栨睜id")
+ private String poolId;
+
+ @ApiModelProperty(value = "鍟嗗湀id")
+ private String regionId;
+
+ @ApiModelProperty(value = "鏈熷彿")
+ private String periodNo;
+
+ @ApiModelProperty(value = "涓鐢ㄦ埛id")
+ private String userId;
+
+ @ApiModelProperty(value = "涓鐢ㄦ埛鏄电О")
+ private String userName;
+
+ @ApiModelProperty(value = "濂栭」灞傜骇")
+ private Integer rewardLevel;
+
+ @ApiModelProperty(value = "濂栭」鍚嶇О")
+ private String levelName;
+
+ @ApiModelProperty(value = "濂栧姳绫诲瀷 0鐜伴噾 1浼樻儬鍒")
+ private Integer rewardType;
+
+ @ApiModelProperty(value = "涓閲戦")
+ private BigDecimal amount;
+
+ @ApiModelProperty(value = "浼樻儬鍒竔d")
+ private String couponId;
+
+ @ApiModelProperty(value = "鏄惁宸查鍙 0鍚 1鏄")
+ private Integer isReceived;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "棰嗗彇鏃堕棿")
+ private Date receivedTime;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetHuntRecord.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetHuntRecord.java
new file mode 100644
index 00000000..552c3bf9
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetHuntRecord.java
@@ -0,0 +1,66 @@
+package cc.hiver.mall.planet.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.persistence.Id;
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.Date;
+
+/**
+ * 鐧藉珫鏄熺悆-杩芥崟璁板綍
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-杩芥崟璁板綍")
+@TableName(value = "t_planet_hunt_record", autoResultMap = true)
+public class PlanetHuntRecord implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @TableId
+ @ApiModelProperty(value = "涓婚敭")
+ private String id;
+
+ @ApiModelProperty(value = "鍙戣捣浜篿d")
+ private String fromUserId;
+
+ @ApiModelProperty(value = "鍙戣捣浜烘樀绉")
+ private String fromUserName;
+
+ @ApiModelProperty(value = "鐩爣鐢ㄦ埛id")
+ private String toUserId;
+
+ @ApiModelProperty(value = "鐩爣鐢ㄦ埛鏄电О")
+ private String toUserName;
+
+ @ApiModelProperty(value = "鍟嗗湀id")
+ private String regionId;
+
+ @ApiModelProperty(value = "缁撴灉 success/shield/fail")
+ private String result;
+
+ @ApiModelProperty(value = "缂磋幏鍒")
+ private Integer gainTickets;
+
+ @ApiModelProperty(value = "鎮祻棰濆鍒")
+ private Integer bountyTickets;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
+ @DateTimeFormat(pattern = "yyyy-MM-dd")
+ @ApiModelProperty(value = "杩芥崟鏃ユ湡")
+ private LocalDate huntDate;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetNews.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetNews.java
new file mode 100644
index 00000000..59af2099
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetNews.java
@@ -0,0 +1,57 @@
+package cc.hiver.mall.planet.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.persistence.Id;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 鐧藉珫鏄熺悆-鏄熺悆蹇
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-鏄熺悆蹇")
+@TableName(value = "t_planet_news", autoResultMap = true)
+public class PlanetNews implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @TableId
+ @ApiModelProperty(value = "涓婚敭")
+ private String id;
+
+ @ApiModelProperty(value = "鍟嗗湀id")
+ private String regionId;
+
+ @ApiModelProperty(value = "绫诲瀷 rank/hunt/box/draw/sys")
+ private String type;
+
+ @ApiModelProperty(value = "蹇鍐呭")
+ private String content;
+
+ @ApiModelProperty(value = "鍏宠仈鐢ㄦ埛id")
+ private String userId;
+
+ @ApiModelProperty(value = "鍏宠仈鐢ㄦ埛鏄电О")
+ private String userName;
+
+ @ApiModelProperty(value = "鏄惁缃《 0鍚 1鏄")
+ private Integer isTop;
+
+ @ApiModelProperty(value = "鏄惁灞曠ず 1鏄 0鍚")
+ private Integer enabled;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetPool.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetPool.java
new file mode 100644
index 00000000..5079fca9
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetPool.java
@@ -0,0 +1,74 @@
+package cc.hiver.mall.planet.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.persistence.Id;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 鐧藉珫鏄熺悆-濂栨睜鏈熸
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-濂栨睜鏈熸")
+@TableName(value = "t_planet_pool", autoResultMap = true)
+public class PlanetPool implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @TableId
+ @ApiModelProperty(value = "涓婚敭")
+ private String id;
+
+ @ApiModelProperty(value = "鍟嗗湀/鏍″尯id")
+ private String regionId;
+
+ @ApiModelProperty(value = "鏈熷彿")
+ private String periodNo;
+
+ @ApiModelProperty(value = "鏈湡鏍囬")
+ private String title;
+
+ @ApiModelProperty(value = "濂栨睜鎬婚噾棰")
+ private BigDecimal poolAmount;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "寮濂栨椂闂")
+ private Date drawTime;
+
+ @ApiModelProperty(value = "鍙備笌浜烘暟")
+ private Integer joinCount;
+
+ @ApiModelProperty(value = "鏈湡鎶曞叆鍒告绘暟")
+ private Integer ticketTotal;
+
+ @ApiModelProperty(value = "涓浜烘暟")
+ private Integer winnerCount;
+
+ @ApiModelProperty(value = "鐘舵 0杩涜涓 1宸插紑濂")
+ private Integer status;
+
+ @ApiModelProperty(value = "澶囨敞/鍏憡")
+ private String remark;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "鏇存柊鏃堕棿")
+ private Date updateTime;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetReward.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetReward.java
new file mode 100644
index 00000000..002ac750
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetReward.java
@@ -0,0 +1,64 @@
+package cc.hiver.mall.planet.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.persistence.Id;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 鐧藉珫鏄熺悆-濂栭」閰嶇疆
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-濂栭」閰嶇疆")
+@TableName(value = "t_planet_reward", autoResultMap = true)
+public class PlanetReward implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @TableId
+ @ApiModelProperty(value = "涓婚敭")
+ private String id;
+
+ @ApiModelProperty(value = "濂栨睜id")
+ private String poolId;
+
+ @ApiModelProperty(value = "鍟嗗湀id")
+ private String regionId;
+
+ @ApiModelProperty(value = "濂栭」灞傜骇 1涓绛 2浜岀瓑 3涓夌瓑 4骞歌繍")
+ private Integer level;
+
+ @ApiModelProperty(value = "濂栭」鍚嶇О")
+ private String levelName;
+
+ @ApiModelProperty(value = "濂栧姳绫诲瀷 0鐜伴噾 1浼樻儬鍒")
+ private Integer rewardType;
+
+ @ApiModelProperty(value = "鍗曚唤閲戦")
+ private BigDecimal amount;
+
+ @ApiModelProperty(value = "浼樻儬鍒竔d")
+ private String couponId;
+
+ @ApiModelProperty(value = "鍚嶉鏁伴噺")
+ private Integer quota;
+
+ @ApiModelProperty(value = "鎺掑簭")
+ private Integer sort;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetTask.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetTask.java
new file mode 100644
index 00000000..dd70ff7f
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetTask.java
@@ -0,0 +1,63 @@
+package cc.hiver.mall.planet.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.persistence.Id;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 鐧藉珫鏄熺悆-浠诲姟瑙勫垯
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-浠诲姟瑙勫垯")
+@TableName(value = "t_planet_task", autoResultMap = true)
+public class PlanetTask implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @TableId
+ @ApiModelProperty(value = "涓婚敭")
+ private String id;
+
+ @ApiModelProperty(value = "鍟嗗湀id 绌轰负鍏ㄥ眬榛樿")
+ private String regionId;
+
+ @ApiModelProperty(value = "浠诲姟缂栫爜 waimai/group/invite/sign")
+ private String code;
+
+ @ApiModelProperty(value = "浠诲姟鍚嶇О")
+ private String name;
+
+ @ApiModelProperty(value = "鎻忚堪")
+ private String description;
+
+ @ApiModelProperty(value = "鍥炬爣")
+ private String icon;
+
+ @ApiModelProperty(value = "濂栧姳鍒告暟閲")
+ private Integer rewardTickets;
+
+ @ApiModelProperty(value = "姣忔棩涓婇檺 0涓嶉檺")
+ private Integer dailyLimit;
+
+ @ApiModelProperty(value = "鎺掑簭")
+ private Integer sort;
+
+ @ApiModelProperty(value = "鏄惁鍚敤 1鏄 0鍚")
+ private Integer enabled;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetTicket.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetTicket.java
new file mode 100644
index 00000000..4ee7048c
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetTicket.java
@@ -0,0 +1,91 @@
+package cc.hiver.mall.planet.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.persistence.Id;
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.util.Date;
+
+/**
+ * 鐧藉珫鏄熺悆-鐢ㄦ埛鍒告眹鎬
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-鐢ㄦ埛鍒告眹鎬")
+@TableName(value = "t_planet_ticket", autoResultMap = true)
+public class PlanetTicket implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @TableId
+ @ApiModelProperty(value = "涓婚敭")
+ private String id;
+
+ @ApiModelProperty(value = "鐢ㄦ埛id")
+ private String userId;
+
+ @ApiModelProperty(value = "鍟嗗湀id")
+ private String regionId;
+
+ @ApiModelProperty(value = "鏄电О")
+ private String nickname;
+
+ @ApiModelProperty(value = "澶村儚")
+ private String avatar;
+
+ @ApiModelProperty(value = "瀛﹂櫌")
+ private String college;
+
+ @ApiModelProperty(value = "褰撳墠鍙敤鍒告暟閲")
+ private Integer ticketCount;
+
+ @ApiModelProperty(value = "鍘嗗彶绱鑾峰緱鍒")
+ private Integer totalTicket;
+
+ @ApiModelProperty(value = "杩炵画绛惧埌澶╂暟")
+ private Integer consecutiveSignDays;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
+ @DateTimeFormat(pattern = "yyyy-MM-dd")
+ @ApiModelProperty(value = "鏈杩戠鍒版棩鏈")
+ private LocalDate lastSignDate;
+
+ @ApiModelProperty(value = "杩炵画闇告澶╂暟")
+ private Integer rankKeepDays;
+
+ @ApiModelProperty(value = "涓婃鎺掑悕")
+ private Integer lastRankNo;
+
+ @ApiModelProperty(value = "浠婃棩宸茶拷鎹曟鏁")
+ private Integer huntCountToday;
+
+ @ApiModelProperty(value = "浠婃棩琚拷鎹曟垚鍔熸鏁")
+ private Integer huntedSuccessToday;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
+ @DateTimeFormat(pattern = "yyyy-MM-dd")
+ @ApiModelProperty(value = "鏈杩戝紑瀹濈鏃ユ湡")
+ private LocalDate boxOpenedDate;
+
+ @ApiModelProperty(value = "鎵灞炴湡鍙")
+ private String periodNo;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "鏇存柊鏃堕棿")
+ private Date updateTime;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetTicketLog.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetTicketLog.java
new file mode 100644
index 00000000..ada05b41
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/entity/PlanetTicketLog.java
@@ -0,0 +1,60 @@
+package cc.hiver.mall.planet.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.persistence.Id;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 鐧藉珫鏄熺悆-鍒告祦姘
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-鍒告祦姘")
+@TableName(value = "t_planet_ticket_log", autoResultMap = true)
+public class PlanetTicketLog implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @TableId
+ @ApiModelProperty(value = "涓婚敭")
+ private String id;
+
+ @ApiModelProperty(value = "鐢ㄦ埛id")
+ private String userId;
+
+ @ApiModelProperty(value = "鍟嗗湀id")
+ private String regionId;
+
+ @ApiModelProperty(value = "鍙樻洿鏁伴噺(姝e緱璐熸墸)")
+ private Integer changeCount;
+
+ @ApiModelProperty(value = "鍙樻洿鍚庝綑棰")
+ private Integer balance;
+
+ @ApiModelProperty(value = "鏉ユ簮 order/group/invite/sign/box/hunt/buff/draw")
+ private String type;
+
+ @ApiModelProperty(value = "鏉ユ簮涓氬姟id(骞傜瓑)")
+ private String sourceId;
+
+ @ApiModelProperty(value = "鏈熷彿")
+ private String periodNo;
+
+ @ApiModelProperty(value = "璇存槑")
+ private String remark;
+
+ @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+ private Date createTime;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/hook/PlanetRewardHook.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/hook/PlanetRewardHook.java
new file mode 100644
index 00000000..4188ff04
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/hook/PlanetRewardHook.java
@@ -0,0 +1,56 @@
+package cc.hiver.mall.planet.hook;
+
+import cc.hiver.mall.planet.constant.PlanetConstant;
+import cc.hiver.mall.planet.mq.PlanetTicketProducer;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 鐧藉珫鏄熺悆-涓氬姟鍙戝埜鍥炶皟
+ *
+ * 浠呭仛杞婚噺 MQ 鎶曢掞紝鎵鏈 DB 鎿嶄綔鏀惧埌娑堣垂绔紓姝ユ墽琛岋紝淇濊瘉涓嶅奖鍝嶄富涓氬姟閫熷害鍜屾祦绋嬶紱
+ * 浠绘剰寮傚父閮借鍚炴帀锛岀粷涓嶅悜涓婃姏銆
+ *
+ * @author hiver
+ */
+@Slf4j
+@Component
+public class PlanetRewardHook {
+
+ @Autowired
+ private PlanetTicketProducer ticketProducer;
+
+ /**
+ * 澶栧崠/鏅氳鍗曞畬鎴愬彂鍒
+ */
+ public void onOrderFinish(String userId, String regionId, String orderId) {
+ rewardByTask(userId, regionId, PlanetConstant.TASK_WAIMAI, PlanetConstant.TICKET_TYPE_ORDER, orderId, "澶栧崠璁㈠崟瀹屾垚");
+ }
+
+ /**
+ * 鍥㈣喘璁㈠崟瀹屾垚鍙戝埜
+ */
+ public void onGroupFinish(String userId, String regionId, String groupOrderId) {
+ rewardByTask(userId, regionId, PlanetConstant.TASK_GROUP, PlanetConstant.TICKET_TYPE_GROUP, groupOrderId, "鍥㈣喘璁㈠崟瀹屾垚");
+ }
+
+ /**
+ * 閭璇峰ソ鍙嬫敞鍐屽彂鍒
+ */
+ public void onInviteRegister(String inviterUserId, String regionId, String sourceId) {
+ rewardByTask(inviterUserId, regionId, PlanetConstant.TASK_INVITE, PlanetConstant.TICKET_TYPE_INVITE, sourceId, "閭璇峰ソ鍙嬫敞鍐");
+ }
+
+ private void rewardByTask(String userId, String regionId, String taskCode, String type, String sourceId, String remark) {
+ if (StringUtils.isEmpty(userId)) {
+ return;
+ }
+ try {
+ ticketProducer.sendTaskReward(userId, regionId, taskCode, type, sourceId, remark);
+ } catch (Exception e) {
+ log.warn("[鐧藉珫鏄熺悆] 鍙戝埜鎶曢掑け璐 userId={}, task={}, {}", userId, taskCode, e.getMessage());
+ }
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetBuffMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetBuffMapper.java
new file mode 100644
index 00000000..3b5fecd3
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetBuffMapper.java
@@ -0,0 +1,9 @@
+package cc.hiver.mall.planet.mapper;
+
+import cc.hiver.mall.planet.entity.PlanetBuff;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PlanetBuffMapper extends BaseMapper {
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetBuffRecordMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetBuffRecordMapper.java
new file mode 100644
index 00000000..86de4251
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetBuffRecordMapper.java
@@ -0,0 +1,9 @@
+package cc.hiver.mall.planet.mapper;
+
+import cc.hiver.mall.planet.entity.PlanetBuffRecord;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PlanetBuffRecordMapper extends BaseMapper {
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetDrawRecordMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetDrawRecordMapper.java
new file mode 100644
index 00000000..f325abc1
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetDrawRecordMapper.java
@@ -0,0 +1,9 @@
+package cc.hiver.mall.planet.mapper;
+
+import cc.hiver.mall.planet.entity.PlanetDrawRecord;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PlanetDrawRecordMapper extends BaseMapper {
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetDrawWinnerMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetDrawWinnerMapper.java
new file mode 100644
index 00000000..ae39dcc8
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetDrawWinnerMapper.java
@@ -0,0 +1,9 @@
+package cc.hiver.mall.planet.mapper;
+
+import cc.hiver.mall.planet.entity.PlanetDrawWinner;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PlanetDrawWinnerMapper extends BaseMapper {
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetHuntRecordMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetHuntRecordMapper.java
new file mode 100644
index 00000000..1063589e
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetHuntRecordMapper.java
@@ -0,0 +1,9 @@
+package cc.hiver.mall.planet.mapper;
+
+import cc.hiver.mall.planet.entity.PlanetHuntRecord;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PlanetHuntRecordMapper extends BaseMapper {
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetNewsMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetNewsMapper.java
new file mode 100644
index 00000000..09709196
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetNewsMapper.java
@@ -0,0 +1,9 @@
+package cc.hiver.mall.planet.mapper;
+
+import cc.hiver.mall.planet.entity.PlanetNews;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PlanetNewsMapper extends BaseMapper {
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetPoolMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetPoolMapper.java
new file mode 100644
index 00000000..f0f63ff0
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetPoolMapper.java
@@ -0,0 +1,9 @@
+package cc.hiver.mall.planet.mapper;
+
+import cc.hiver.mall.planet.entity.PlanetPool;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PlanetPoolMapper extends BaseMapper {
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetRewardMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetRewardMapper.java
new file mode 100644
index 00000000..88aa8166
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetRewardMapper.java
@@ -0,0 +1,9 @@
+package cc.hiver.mall.planet.mapper;
+
+import cc.hiver.mall.planet.entity.PlanetReward;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PlanetRewardMapper extends BaseMapper {
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetTaskMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetTaskMapper.java
new file mode 100644
index 00000000..f8cd8a42
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetTaskMapper.java
@@ -0,0 +1,9 @@
+package cc.hiver.mall.planet.mapper;
+
+import cc.hiver.mall.planet.entity.PlanetTask;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PlanetTaskMapper extends BaseMapper {
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetTicketLogMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetTicketLogMapper.java
new file mode 100644
index 00000000..8fbf547f
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetTicketLogMapper.java
@@ -0,0 +1,9 @@
+package cc.hiver.mall.planet.mapper;
+
+import cc.hiver.mall.planet.entity.PlanetTicketLog;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PlanetTicketLogMapper extends BaseMapper {
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetTicketMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetTicketMapper.java
new file mode 100644
index 00000000..8539a127
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mapper/PlanetTicketMapper.java
@@ -0,0 +1,9 @@
+package cc.hiver.mall.planet.mapper;
+
+import cc.hiver.mall.planet.entity.PlanetTicket;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PlanetTicketMapper extends BaseMapper {
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mq/PlanetMqConfig.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mq/PlanetMqConfig.java
new file mode 100644
index 00000000..bdc4354a
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mq/PlanetMqConfig.java
@@ -0,0 +1,54 @@
+package cc.hiver.mall.planet.mq;
+
+import org.springframework.amqp.core.Binding;
+import org.springframework.amqp.core.BindingBuilder;
+import org.springframework.amqp.core.DirectExchange;
+import org.springframework.amqp.core.Queue;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 鐧藉珫鏄熺悆 MQ 閰嶇疆
+ *
+ * 鍙戝埜銆佺敓鎴愬揩璁瓑闈炴牳蹇冨姩浣滃紓姝ュ寲锛岄伩鍏嶉樆濉炰笅鍗/娉ㄥ唽绛変富涓氬姟娴佺▼銆
+ *
+ * @author hiver
+ */
+@Configuration
+public class PlanetMqConfig {
+
+ public static final String PLANET_EXCHANGE = "planet.direct.exchange";
+
+ /** 鍙戝埜闃熷垪 */
+ public static final String TICKET_GRANT_QUEUE = "planet.ticket.grant.queue";
+ public static final String TICKET_GRANT_ROUTING = "planet.ticket.grant.routing.key";
+
+ /** 蹇鐢熸垚闃熷垪 */
+ public static final String NEWS_QUEUE = "planet.news.queue";
+ public static final String NEWS_ROUTING = "planet.news.routing.key";
+
+ @Bean
+ public DirectExchange planetDirectExchange() {
+ return new DirectExchange(PLANET_EXCHANGE, true, false);
+ }
+
+ @Bean
+ public Queue planetTicketGrantQueue() {
+ return new Queue(TICKET_GRANT_QUEUE, true);
+ }
+
+ @Bean
+ public Binding bindingPlanetTicketGrantQueue() {
+ return BindingBuilder.bind(planetTicketGrantQueue()).to(planetDirectExchange()).with(TICKET_GRANT_ROUTING);
+ }
+
+ @Bean
+ public Queue planetNewsQueue() {
+ return new Queue(NEWS_QUEUE, true);
+ }
+
+ @Bean
+ public Binding bindingPlanetNewsQueue() {
+ return BindingBuilder.bind(planetNewsQueue()).to(planetDirectExchange()).with(NEWS_ROUTING);
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mq/PlanetTicketConsumer.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mq/PlanetTicketConsumer.java
new file mode 100644
index 00000000..295e18a3
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mq/PlanetTicketConsumer.java
@@ -0,0 +1,109 @@
+package cc.hiver.mall.planet.mq;
+
+import cc.hiver.mall.entity.MallOrder;
+import cc.hiver.mall.planet.entity.PlanetTask;
+import cc.hiver.mall.planet.service.PlanetNewsService;
+import cc.hiver.mall.planet.service.PlanetTaskService;
+import cc.hiver.mall.planet.service.PlanetTicketService;
+import cc.hiver.mall.service.mybatis.MallOrderService;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.amqp.AmqpRejectAndDontRequeueException;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * 鐧藉珫鏄熺悆-鍙戝埜/蹇寮傛娑堣垂鑰
+ *
+ * 鐪熸鐨勫彂鍒 DB 鎿嶄綔鍦ㄦ瀹屾垚锛沘ddTickets 閫氳繃 sourceId 骞傜瓑锛屽彲瀹夊叏搴斿 MQ 鑷冲皯涓娆℃姇閫掋
+ *
+ * @author hiver
+ */
+@Slf4j
+@Component
+public class PlanetTicketConsumer {
+
+ @Autowired
+ private PlanetTaskService taskService;
+
+ @Autowired
+ private PlanetTicketService ticketService;
+
+ @Autowired
+ private PlanetNewsService newsService;
+
+ @Autowired
+ private MallOrderService mallOrderService;
+
+ @RabbitListener(queues = PlanetMqConfig.TICKET_GRANT_QUEUE)
+ @Transactional(rollbackFor = Exception.class)
+ public void handleTicketGrant(String message) {
+ try {
+ final JSONObject json = JSONObject.parseObject(message);
+ final String userId = json.getString("userId");
+ String regionId = json.getString("regionId");
+ final String taskCode = json.getString("taskCode");
+ final String type = json.getString("type");
+ final String sourceId = json.getString("sourceId");
+ final String remark = json.getString("remark");
+ if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(taskCode)) {
+ log.warn("[鐧藉珫鏄熺悆MQ] 鍙戝埜娑堟伅鍙傛暟寮傚父: {}", message);
+ return;
+ }
+ // 鍟嗗湀寮洪殧绂伙細娑堟伅缂 regionId 鏃讹紙濡傞個璇锋敞鍐岋級锛屼粠鐢ㄦ埛鏈杩戜竴绗旇鍗曞厹搴曡В鏋愶紝纭繚鍒稿綊灞炴纭晢鍦
+ if (StringUtils.isEmpty(regionId)) {
+ regionId = resolveRegionByUserOrder(userId);
+ }
+ if (StringUtils.isEmpty(regionId)) {
+ log.warn("[鐧藉珫鏄熺悆MQ] 鏃犳硶纭畾鐢ㄦ埛鍟嗗湀锛岃烦杩囧彂鍒 userId={}, task={}", userId, taskCode);
+ return;
+ }
+ final PlanetTask task = taskService.getTaskConfig(regionId, taskCode);
+ if (task == null || task.getEnabled() == null || task.getEnabled() != 1) {
+ log.info("[鐧藉珫鏄熺悆MQ] 浠诲姟鏈惎鐢紝璺宠繃鍙戝埜 task={}", taskCode);
+ return;
+ }
+ final int reward = task.getRewardTickets() == null ? 1 : task.getRewardTickets();
+ final int added = ticketService.addTickets(userId, regionId, reward, type, sourceId, remark);
+ log.info("[鐧藉珫鏄熺悆MQ] 鍙戝埜瀹屾垚 userId={}, task={}, 瀹炲彂={}", userId, taskCode, added);
+ } catch (Exception e) {
+ // 涓氬姟寮傚父鏃犻渶閲嶆姇锛堥噸鎶曚篃浼氬啀娆″け璐ワ級锛屼粎璁板綍鏃ュ織锛岃繘鍏ユ淇/涓㈠純
+ log.error("[鐧藉珫鏄熺悆MQ] 鍙戝埜娑堟伅澶勭悊寮傚父: {}, msg={}", e.getMessage(), message, e);
+ throw new AmqpRejectAndDontRequeueException(e);
+ }
+ }
+
+ /**
+ * 浠庣敤鎴锋渶杩戜竴绗斿甫鍟嗗湀鐨勮鍗曡В鏋 regionId锛堢敤浜庨個璇锋敞鍐岀瓑鏃犲晢鍦堜笂涓嬫枃鐨勫彂鍒革級銆
+ */
+ private String resolveRegionByUserOrder(String userId) {
+ try {
+ final MallOrder order = mallOrderService.getOne(new LambdaQueryWrapper()
+ .select(MallOrder::getRegionId)
+ .eq(MallOrder::getUserId, userId)
+ .isNotNull(MallOrder::getRegionId)
+ .ne(MallOrder::getRegionId, "")
+ .orderByDesc(MallOrder::getCreateTime)
+ .last("limit 1"));
+ return order == null ? null : order.getRegionId();
+ } catch (Exception e) {
+ log.warn("[鐧藉珫鏄熺悆MQ] 瑙f瀽鐢ㄦ埛鍟嗗湀澶辫触 userId={}, {}", userId, e.getMessage());
+ return null;
+ }
+ }
+
+ @RabbitListener(queues = PlanetMqConfig.NEWS_QUEUE)
+ public void handleNews(String message) {
+ try {
+ final JSONObject json = JSONObject.parseObject(message);
+ newsService.addNews(json.getString("regionId"), json.getString("type"), json.getString("content"), null, null);
+ } catch (Exception e) {
+ log.error("[鐧藉珫鏄熺悆MQ] 蹇娑堟伅澶勭悊寮傚父: {}, msg={}", e.getMessage(), message, e);
+ throw new AmqpRejectAndDontRequeueException(e);
+ }
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mq/PlanetTicketProducer.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mq/PlanetTicketProducer.java
new file mode 100644
index 00000000..167f6cad
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/mq/PlanetTicketProducer.java
@@ -0,0 +1,60 @@
+package cc.hiver.mall.planet.mq;
+
+import com.alibaba.fastjson.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 鐧藉珫鏄熺悆-鍙戝埜/蹇寮傛娑堟伅鐢熶骇鑰
+ *
+ * 浠呭仛杞婚噺鎶曢掞紝涓嶅仛浠讳綍 DB 鎿嶄綔锛屼繚璇佷富涓氬姟娴佺▼涓嶈鍙戝埜閫昏緫鎷栨參銆
+ *
+ * @author hiver
+ */
+@Slf4j
+@Component
+public class PlanetTicketProducer {
+
+ @Autowired
+ private RabbitTemplate rabbitTemplate;
+
+ /**
+ * 鎶曢掍竴鏉°屾寜浠诲姟鍙戝埜銆嶆秷鎭傛秷璐圭璐熻矗瑙f瀽浠诲姟閰嶇疆骞跺箓绛夊彂鍒搞
+ */
+ public void sendTaskReward(String userId, String regionId, String taskCode, String type, String sourceId, String remark) {
+ Map msg = new HashMap<>(8);
+ msg.put("userId", userId);
+ msg.put("regionId", regionId);
+ msg.put("taskCode", taskCode);
+ msg.put("type", type);
+ msg.put("sourceId", sourceId);
+ msg.put("remark", remark);
+ try {
+ rabbitTemplate.convertAndSend(PlanetMqConfig.PLANET_EXCHANGE, PlanetMqConfig.TICKET_GRANT_ROUTING, JSON.toJSONString(msg));
+ log.info("[鐧藉珫鏄熺悆MQ] 宸叉姇閫掑彂鍒告秷鎭 userId={}, task={}, sourceId={}", userId, taskCode, sourceId);
+ } catch (Exception e) {
+ // 鎶曢掑け璐ヤ粎璁板綍锛岀粷涓嶅奖鍝嶄富涓氬姟
+ log.warn("[鐧藉珫鏄熺悆MQ] 鍙戝埜娑堟伅鎶曢掑け璐 userId={}, task={}, {}", userId, taskCode, e.getMessage());
+ }
+ }
+
+ /**
+ * 鎶曢掍竴鏉″揩璁敓鎴愭秷鎭
+ */
+ public void sendNews(String regionId, String type, String content) {
+ Map msg = new HashMap<>(4);
+ msg.put("regionId", regionId);
+ msg.put("type", type);
+ msg.put("content", content);
+ try {
+ rabbitTemplate.convertAndSend(PlanetMqConfig.PLANET_EXCHANGE, PlanetMqConfig.NEWS_ROUTING, JSON.toJSONString(msg));
+ } catch (Exception e) {
+ log.warn("[鐧藉珫鏄熺悆MQ] 蹇娑堟伅鎶曢掑け璐 regionId={}, {}", regionId, e.getMessage());
+ }
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetBoxResultVo.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetBoxResultVo.java
new file mode 100644
index 00000000..9ab3428d
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetBoxResultVo.java
@@ -0,0 +1,34 @@
+package cc.hiver.mall.planet.pojo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 鐧藉珫鏄熺悆-瀹濈寮鍚粨鏋
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-瀹濈寮鍚粨鏋")
+public class PlanetBoxResultVo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty(value = "濂栧姳绫诲瀷 ticket鍒/buff澧炵泭")
+ private String rewardType;
+
+ @ApiModelProperty(value = "鑾峰緱鍒告暟閲(ticket鏃)")
+ private Integer ticketCount;
+
+ @ApiModelProperty(value = "BUFF绫诲瀷(buff鏃)")
+ private String buffType;
+
+ @ApiModelProperty(value = "濂栧姳鍚嶇О")
+ private String rewardName;
+
+ @ApiModelProperty(value = "鎻愮ず鏂囨")
+ private String message;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetBuffVo.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetBuffVo.java
new file mode 100644
index 00000000..70579876
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetBuffVo.java
@@ -0,0 +1,51 @@
+package cc.hiver.mall.planet.pojo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 鐧藉珫鏄熺悆-BUFF椤(鍚綋鍓嶆槸鍚︾敓鏁)
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-BUFF椤")
+public class PlanetBuffVo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty(value = "BUFF閰嶇疆id")
+ private String id;
+
+ @ApiModelProperty(value = "绫诲瀷")
+ private String type;
+
+ @ApiModelProperty(value = "鍚嶇О")
+ private String name;
+
+ @ApiModelProperty(value = "鎻忚堪")
+ private String description;
+
+ @ApiModelProperty(value = "鍥炬爣")
+ private String icon;
+
+ @ApiModelProperty(value = "娑堣楀埜鏁伴噺")
+ private Integer costTickets;
+
+ @ApiModelProperty(value = "鎸佺画灏忔椂")
+ private Integer durationHours;
+
+ @ApiModelProperty(value = "鏁堟灉鍊")
+ private BigDecimal effectValue;
+
+ @ApiModelProperty(value = "褰撳墠鏄惁鐢熸晥")
+ private Boolean active;
+
+ @ApiModelProperty(value = "褰撳墠鐢熸晥鍒版湡鏃堕棿")
+ private Date activeEndTime;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetHomeVo.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetHomeVo.java
new file mode 100644
index 00000000..cf781c52
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetHomeVo.java
@@ -0,0 +1,83 @@
+package cc.hiver.mall.planet.pojo;
+
+import cc.hiver.mall.planet.entity.PlanetNews;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 鐧藉珫鏄熺悆-棣栭〉鑱氬悎鏁版嵁
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-棣栭〉鑱氬悎鏁版嵁")
+public class PlanetHomeVo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ // ---------- 瀹囧畽Header ----------
+ @ApiModelProperty(value = "鏈湡濂栭噾姹")
+ private BigDecimal poolAmount;
+
+ @ApiModelProperty(value = "寮濂栨椂闂")
+ private Date drawTime;
+
+ @ApiModelProperty(value = "璺濈寮濂栧墿浣欐绉")
+ private Long countdownMillis;
+
+ @ApiModelProperty(value = "鏈湡鍙備笌浜烘暟")
+ private Integer joinCount;
+
+ @ApiModelProperty(value = "鏈熷彿")
+ private String periodNo;
+
+ // ---------- 鎴戠殑鏄熺悆涓績 ----------
+ @ApiModelProperty(value = "鎴戠殑鍒告暟閲")
+ private Integer myTicketCount;
+
+ @ApiModelProperty(value = "鎴戠殑鎺掑悕(0鏈笂姒)")
+ private Integer myRankNo;
+
+ @ApiModelProperty(value = "鏄电О")
+ private String nickname;
+
+ @ApiModelProperty(value = "澶村儚")
+ private String avatar;
+
+ @ApiModelProperty(value = "杩炵画绛惧埌澶╂暟")
+ private Integer consecutiveSignDays;
+
+ @ApiModelProperty(value = "浠婃棩鏄惁宸茬鍒")
+ private Boolean signedToday;
+
+ @ApiModelProperty(value = "浠婃棩鍏嶈垂瀹濈鏄惁鍙紑")
+ private Boolean boxAvailable;
+
+ @ApiModelProperty(value = "浠婃棩鍓╀綑杩芥崟娆℃暟")
+ private Integer remainHunt;
+
+ @ApiModelProperty(value = "绛夌骇(鍗遍櫓绛夌骇)")
+ private String level;
+
+ @ApiModelProperty(value = "鎴戝綋鍓嶇敓鏁堢殑BUFF")
+ private List myBuffs;
+
+ // ---------- 鍚勬澘鍧 ----------
+ @ApiModelProperty(value = "浠诲姟鍒楄〃")
+ private List tasks;
+
+ @ApiModelProperty(value = "閫氱級姒淭OP10")
+ private List rankList;
+
+ @ApiModelProperty(value = "BUFF鍟嗗簵")
+ private List buffShop;
+
+ @ApiModelProperty(value = "鏄熺悆蹇")
+ private List newsList;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetHuntResultVo.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetHuntResultVo.java
new file mode 100644
index 00000000..a8ede5e9
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetHuntResultVo.java
@@ -0,0 +1,40 @@
+package cc.hiver.mall.planet.pojo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 鐧藉珫鏄熺悆-杩芥崟缁撴灉
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-杩芥崟缁撴灉")
+public class PlanetHuntResultVo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty(value = "缁撴灉 success缂磋幏/shield瀵规柟鎶ょ浘/fail澶辫触")
+ private String result;
+
+ @ApiModelProperty(value = "缂磋幏鍒")
+ private Integer gainTickets;
+
+ @ApiModelProperty(value = "鎮祻棰濆鍒")
+ private Integer bountyTickets;
+
+ @ApiModelProperty(value = "鏈鍚堣鑾峰緱鍒")
+ private Integer totalGain;
+
+ @ApiModelProperty(value = "鐩爣鏄电О")
+ private String targetName;
+
+ @ApiModelProperty(value = "浠婃棩鍓╀綑杩芥崟娆℃暟")
+ private Integer remainHunt;
+
+ @ApiModelProperty(value = "鎻愮ず鏂囨")
+ private String message;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetQuery.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetQuery.java
new file mode 100644
index 00000000..fa4df10b
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetQuery.java
@@ -0,0 +1,52 @@
+package cc.hiver.mall.planet.pojo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 鐧藉珫鏄熺悆-App绔氱敤璇锋眰鍙傛暟
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-App绔氱敤璇锋眰鍙傛暟")
+public class PlanetQuery implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty(value = "鐢ㄦ埛id")
+ private String userId;
+
+ @ApiModelProperty(value = "鍟嗗湀/鏍″尯id")
+ private String regionId;
+
+ @ApiModelProperty(value = "鏄电О(棣栨杩涘叆鍐椾綑)")
+ private String nickname;
+
+ @ApiModelProperty(value = "澶村儚(棣栨杩涘叆鍐椾綑)")
+ private String avatar;
+
+ @ApiModelProperty(value = "瀛﹂櫌")
+ private String college;
+
+ @ApiModelProperty(value = "鐩爣鐢ㄦ埛id(杩芥崟鐢)")
+ private String toUserId;
+
+ @ApiModelProperty(value = "浠诲姟缂栫爜")
+ private String taskCode;
+
+ @ApiModelProperty(value = "BUFF閰嶇疆id")
+ private String buffId;
+
+ @ApiModelProperty(value = "涓璁板綍id")
+ private String winnerId;
+
+ @ApiModelProperty(value = "椤电爜(浠1寮濮)")
+ private Integer pageNumber;
+
+ @ApiModelProperty(value = "姣忛〉鏁伴噺")
+ private Integer pageSize;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetRankItemVo.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetRankItemVo.java
new file mode 100644
index 00000000..d79cde61
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetRankItemVo.java
@@ -0,0 +1,55 @@
+package cc.hiver.mall.planet.pojo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 鐧藉珫鏄熺悆-閫氱級姒滃崟椤
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-閫氱級姒滃崟椤")
+public class PlanetRankItemVo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty(value = "鎺掑悕")
+ private Integer rankNo;
+
+ @ApiModelProperty(value = "鐢ㄦ埛id")
+ private String userId;
+
+ @ApiModelProperty(value = "鏄电О")
+ private String nickname;
+
+ @ApiModelProperty(value = "澶村儚")
+ private String avatar;
+
+ @ApiModelProperty(value = "瀛﹂櫌")
+ private String college;
+
+ @ApiModelProperty(value = "褰撳墠鍒告暟閲")
+ private Integer ticketCount;
+
+ @ApiModelProperty(value = "鍗遍櫓绛夌骇 C/B/A/S/SSS")
+ private String dangerLevel;
+
+ @ApiModelProperty(value = "鍗遍櫓绛夌骇鍚嶇О")
+ private String dangerLevelName;
+
+ @ApiModelProperty(value = "杩炵画闇告澶╂暟")
+ private Integer rankKeepDays;
+
+ @ApiModelProperty(value = "鎮祻棰濆鍒")
+ private Integer bountyTickets;
+
+ @ApiModelProperty(value = "鏄惁寮鍚槻鎶ょ僵")
+ private Boolean shielded;
+
+ @ApiModelProperty(value = "鏄惁鏄嚜宸")
+ private Boolean self;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetTaskVo.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetTaskVo.java
new file mode 100644
index 00000000..a4caebb6
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/pojo/PlanetTaskVo.java
@@ -0,0 +1,46 @@
+package cc.hiver.mall.planet.pojo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 鐧藉珫鏄熺悆-浠诲姟椤(鍚粖鏃ュ畬鎴愮姸鎬)
+ *
+ * @author hiver
+ */
+@Data
+@ApiModel(value = "鐧藉珫鏄熺悆-浠诲姟椤")
+public class PlanetTaskVo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty(value = "浠诲姟缂栫爜")
+ private String code;
+
+ @ApiModelProperty(value = "浠诲姟鍚嶇О")
+ private String name;
+
+ @ApiModelProperty(value = "鎻忚堪")
+ private String description;
+
+ @ApiModelProperty(value = "鍥炬爣")
+ private String icon;
+
+ @ApiModelProperty(value = "濂栧姳鍒告暟閲")
+ private Integer rewardTickets;
+
+ @ApiModelProperty(value = "姣忔棩涓婇檺 0涓嶉檺")
+ private Integer dailyLimit;
+
+ @ApiModelProperty(value = "浠婃棩宸插畬鎴愭鏁")
+ private Integer todayCount;
+
+ @ApiModelProperty(value = "浠婃棩鏄惁鍙鍙(绛惧埌绫)")
+ private Boolean canClaim;
+
+ @ApiModelProperty(value = "鍔ㄤ綔绫诲瀷 claim鍙鍙/auto璁㈠崟鑷姩")
+ private String actionType;
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetAdminService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetAdminService.java
new file mode 100644
index 00000000..91d359bd
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetAdminService.java
@@ -0,0 +1,65 @@
+package cc.hiver.mall.planet.service;
+
+import cc.hiver.core.common.vo.PageVo;
+import cc.hiver.mall.planet.entity.*;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import java.util.List;
+
+/**
+ * 鐧藉珫鏄熺悆-鍚庡彴绠$悊鏈嶅姟
+ *
+ * @author hiver
+ */
+public interface PlanetAdminService {
+
+ // 濂栨睜
+ Page pagePools(PageVo pageVo, String regionId, Integer status);
+
+ PlanetPool savePool(PlanetPool pool);
+
+ void deletePool(String id);
+
+ // 濂栭」
+ List rewardsOfPool(String poolId);
+
+ PlanetReward saveReward(PlanetReward reward);
+
+ void deleteReward(String id);
+
+ // 浠诲姟
+ Page pageTasks(PageVo pageVo, String regionId);
+
+ PlanetTask saveTask(PlanetTask task);
+
+ void deleteTask(String id);
+
+ // BUFF
+ Page pageBuffs(PageVo pageVo, String regionId);
+
+ PlanetBuff saveBuff(PlanetBuff buff);
+
+ void deleteBuff(String id);
+
+ // 蹇
+ Page pageNews(PageVo pageVo, String regionId);
+
+ PlanetNews saveNews(PlanetNews news);
+
+ void deleteNews(String id);
+
+ // 鎺掕姒(鏌ョ湅)
+ Page pageRank(PageVo pageVo, String regionId);
+
+ // 杩芥崟璁板綍
+ Page pageHunts(PageVo pageVo, String regionId);
+
+ // 寮濂栬褰
+ Page pageDraws(PageVo pageVo, String regionId);
+
+ // 涓璁板綍
+ Page pageWinners(PageVo pageVo, String regionId, String periodNo);
+
+ // 鎵嬪姩寮濂
+ PlanetDrawRecord manualDraw(String poolId, String regionId);
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetBoxService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetBoxService.java
new file mode 100644
index 00000000..e7056e74
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetBoxService.java
@@ -0,0 +1,21 @@
+package cc.hiver.mall.planet.service;
+
+import cc.hiver.mall.planet.pojo.PlanetBoxResultVo;
+
+/**
+ * 鐧藉珫鏄熺悆-骞歌繍瀹濈鏈嶅姟
+ *
+ * @author hiver
+ */
+public interface PlanetBoxService {
+
+ /**
+ * 寮鍚瘡鏃ュ厤璐瑰疂绠
+ */
+ PlanetBoxResultVo openDailyBox(String userId, String regionId);
+
+ /**
+ * 浠婃棩鏄惁杩樺彲寮瀹濈
+ */
+ boolean boxAvailable(String userId, String regionId);
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetBuffService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetBuffService.java
new file mode 100644
index 00000000..686a6716
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetBuffService.java
@@ -0,0 +1,63 @@
+package cc.hiver.mall.planet.service;
+
+import cc.hiver.mall.planet.entity.PlanetBuffRecord;
+import cc.hiver.mall.planet.pojo.PlanetBuffVo;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 鐧藉珫鏄熺悆-BUFF鏈嶅姟
+ *
+ * @author hiver
+ */
+public interface PlanetBuffService {
+
+ /**
+ * BUFF鍟嗗簵鍒楄〃(鍚綋鍓嶇敤鎴锋槸鍚︾敓鏁)
+ */
+ List shopList(String userId, String regionId);
+
+ /**
+ * 璐拱BUFF(娑堣楀埜骞剁敓鏁)
+ */
+ PlanetBuffVo buyBuff(String userId, String regionId, String buffId);
+
+ /**
+ * 褰撳墠鐢ㄦ埛鐢熸晥涓殑BUFF鍒楄〃
+ */
+ List activeBuffs(String userId);
+
+ /**
+ * 鍒ゆ柇鏌愮被鍨婤UFF鏄惁鐢熸晥
+ */
+ boolean hasActiveBuff(String userId, String type);
+
+ /**
+ * 缁欑敤鎴锋巿浜堜竴涓狟UFF(瀹濈/鑷姩闃叉姢浣跨敤)
+ */
+ PlanetBuffRecord grantBuff(String userId, String regionId, String type, String source);
+
+ /**
+ * 骞歌繍BUFF寮濂栨潈閲嶅姞鎴愮郴鏁(1+effectValue)锛屾棤鍒1
+ */
+ double luckyWeightFactor(String userId);
+
+ /**
+ * 鎵归噺鏌ヨ澶氫釜鐢ㄦ埛褰撳墠鐢熸晥涓殑 BUFF 绫诲瀷闆嗗悎(涓娆℃煡搴擄紝鍐呭瓨缁勮)
+ *
+ * @param userIds 鐢ㄦ埛id闆嗗悎
+ * @return Map<userId, Set<buffType>>锛屾棤鐢熸晥BUFF鐨勭敤鎴蜂笉鍦╩ap涓
+ */
+ Map> activeBuffTypes(Collection userIds);
+
+ /**
+ * 鎵归噺鏌ヨ澶氫釜鐢ㄦ埛鐨勫垢杩怋UFF鏉冮噸绯绘暟(涓娆℃煡搴擄紝鍐呭瓨缁勮)
+ *
+ * @param userIds 鐢ㄦ埛id闆嗗悎
+ * @return Map<userId, 绯绘暟(1+effectValue)>锛屾棤骞歌繍BUFF鐨勭敤鎴蜂笉鍦╩ap涓
+ */
+ Map luckyWeightFactors(Collection userIds);
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetDrawService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetDrawService.java
new file mode 100644
index 00000000..d463ded4
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetDrawService.java
@@ -0,0 +1,52 @@
+package cc.hiver.mall.planet.service;
+
+import cc.hiver.mall.planet.entity.PlanetDrawRecord;
+import cc.hiver.mall.planet.entity.PlanetDrawWinner;
+import cc.hiver.mall.planet.entity.PlanetPool;
+
+import java.util.List;
+
+/**
+ * 鐧藉珫鏄熺悆-寮濂栨湇鍔
+ *
+ * @author hiver
+ */
+public interface PlanetDrawService {
+
+ /**
+ * 鑾峰彇褰撳墠杩涜涓殑濂栨睜(鏃犲垯鑷姩鍒涘缓榛樿濂栨睜)
+ */
+ PlanetPool getCurrentPool(String regionId);
+
+ /**
+ * 鎵ц寮濂(鍔犳潈鎶藉彇锛屽繀涓悕鍗)
+ *
+ * @return 寮濂栬褰
+ */
+ PlanetDrawRecord drawPool(String poolId);
+
+ /**
+ * 鎵弿鎵鏈夊埌鐐规湭寮濂栫殑濂栨睜骞跺紑濂(瀹氭椂浠诲姟鐢)
+ */
+ int drawDuePools();
+
+ /**
+ * 鏈杩戜竴鏈熷紑濂栬褰
+ */
+ PlanetDrawRecord latestDrawRecord(String regionId);
+
+ /**
+ * 鏌愭寮濂栫殑涓鍚嶅崟
+ */
+ List winnersOfDraw(String drawId);
+
+ /**
+ * 鎴戠殑涓璁板綍(浠呴檺鏈晢鍦)
+ */
+ List myWinning(String userId, String regionId);
+
+ /**
+ * 棰嗗彇涓濂栧姳(鏍¢獙鍟嗗湀涓鑷)
+ */
+ void receive(String userId, String regionId, String winnerId);
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetHuntService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetHuntService.java
new file mode 100644
index 00000000..f874a317
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetHuntService.java
@@ -0,0 +1,21 @@
+package cc.hiver.mall.planet.service;
+
+import cc.hiver.mall.planet.pojo.PlanetHuntResultVo;
+
+/**
+ * 鐧藉珫鏄熺悆-杩芥崟鏈嶅姟
+ *
+ * @author hiver
+ */
+public interface PlanetHuntService {
+
+ /**
+ * 鍙戣捣杩芥崟
+ */
+ PlanetHuntResultVo hunt(String fromUserId, String regionId, String toUserId);
+
+ /**
+ * 浠婃棩鍓╀綑杩芥崟娆℃暟
+ */
+ int remainHunt(String userId);
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetNewsService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetNewsService.java
new file mode 100644
index 00000000..091d9a59
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetNewsService.java
@@ -0,0 +1,23 @@
+package cc.hiver.mall.planet.service;
+
+import cc.hiver.mall.planet.entity.PlanetNews;
+
+import java.util.List;
+
+/**
+ * 鐧藉珫鏄熺悆-鏄熺悆蹇鏈嶅姟
+ *
+ * @author hiver
+ */
+public interface PlanetNewsService {
+
+ /**
+ * 鏂板蹇
+ */
+ void addNews(String regionId, String type, String content, String userId, String userName);
+
+ /**
+ * 鑾峰彇鏈鏂板揩璁
+ */
+ List latest(String regionId, int limit);
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetRankService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetRankService.java
new file mode 100644
index 00000000..78d05260
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetRankService.java
@@ -0,0 +1,29 @@
+package cc.hiver.mall.planet.service;
+
+import cc.hiver.mall.planet.entity.PlanetTicket;
+import cc.hiver.mall.planet.pojo.PlanetRankItemVo;
+
+import java.util.List;
+
+/**
+ * 鐧藉珫鏄熺悆-閫氱級姒滄湇鍔
+ *
+ * @author hiver
+ */
+public interface PlanetRankService {
+
+ /**
+ * 鑾峰彇閫氱級姒淭OP N(鎺掗櫎闅愯韩BUFF鐢ㄦ埛)
+ */
+ List rankTop(String regionId, int topN, String currentUserId);
+
+ /**
+ * 鑾峰彇姒滃崟鍐呮煇鐢ㄦ埛鐨勫師濮嬪埜璁板綍(鐢ㄤ簬杩芥崟鎮祻璁$畻)锛屼笉鍦ㄦ杩斿洖null
+ */
+ PlanetTicket getRankTarget(String regionId, String userId);
+
+ /**
+ * 鐢ㄦ埛褰撳墠鎺掑悕(0鏈笂姒)
+ */
+ int userRankNo(String regionId, String userId);
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetService.java
new file mode 100644
index 00000000..d9293ee3
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetService.java
@@ -0,0 +1,17 @@
+package cc.hiver.mall.planet.service;
+
+import cc.hiver.mall.planet.pojo.PlanetHomeVo;
+import cc.hiver.mall.planet.pojo.PlanetQuery;
+
+/**
+ * 鐧藉珫鏄熺悆-棣栭〉鑱氬悎鏈嶅姟
+ *
+ * @author hiver
+ */
+public interface PlanetService {
+
+ /**
+ * 棣栭〉鑱氬悎鏁版嵁
+ */
+ PlanetHomeVo home(PlanetQuery query);
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetTaskService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetTaskService.java
new file mode 100644
index 00000000..d50e2bef
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetTaskService.java
@@ -0,0 +1,41 @@
+package cc.hiver.mall.planet.service;
+
+import cc.hiver.mall.planet.entity.PlanetTask;
+import cc.hiver.mall.planet.pojo.PlanetTaskVo;
+
+import java.util.List;
+
+/**
+ * 鐧藉珫鏄熺悆-浠诲姟鏈嶅姟
+ *
+ * @author hiver
+ */
+public interface PlanetTaskService {
+
+ /**
+ * 浠诲姟鍒楄〃(鍚粖鏃ュ畬鎴愮姸鎬)
+ */
+ List listTasks(String userId, String regionId);
+
+ /**
+ * 姣忔棩绛惧埌
+ *
+ * @return 鑾峰緱鍒告暟閲
+ */
+ int sign(String userId, String regionId);
+
+ /**
+ * 棰嗗彇浠诲姟濂栧姳(鐩墠浠呯鍒板彲涓诲姩棰嗗彇)
+ */
+ int claim(String userId, String regionId, String taskCode);
+
+ /**
+ * 鏍规嵁缂栫爜鑾峰彇鐢熸晥浠诲姟閰嶇疆
+ */
+ PlanetTask getTaskConfig(String regionId, String code);
+
+ /**
+ * 浠婃棩鏄惁宸茬鍒
+ */
+ boolean signedToday(String userId, String regionId);
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetTicketService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetTicketService.java
new file mode 100644
index 00000000..989b4342
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/PlanetTicketService.java
@@ -0,0 +1,50 @@
+package cc.hiver.mall.planet.service;
+
+import cc.hiver.mall.planet.entity.PlanetTicket;
+import cc.hiver.mall.planet.entity.PlanetTicketLog;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+/**
+ * 鐧藉珫鏄熺悆-鏄熺悆鍒告湇鍔
+ *
+ * @author hiver
+ */
+public interface PlanetTicketService {
+
+ /**
+ * 鑾峰彇鎴栧垱寤虹敤鎴峰埜璁板綍(鑷姩琛ュ叏鏄电О澶村儚)
+ */
+ PlanetTicket getOrCreate(String userId, String regionId, String nickname, String avatar, String college);
+
+ /**
+ * 鏌ヨ鐢ㄦ埛鍒歌褰(涓嶅瓨鍦ㄨ繑鍥瀗ull)
+ */
+ PlanetTicket getByUser(String userId, String regionId);
+
+ /**
+ * 澧炲姞鏄熺悆鍒(鏀寔鍙屽岯UFF涓庡箓绛)
+ *
+ * @param baseCount 鍩虹鏁伴噺(BUFF鍓)
+ * @param type 鏉ユ簮绫诲瀷
+ * @param sourceId 鏉ユ簮涓氬姟id(闈炵┖鏃跺仛骞傜瓑鍘婚噸)
+ * @return 瀹為檯澧炲姞鏁伴噺(0琛ㄧず琚箓绛夋嫤鎴)
+ */
+ int addTickets(String userId, String regionId, int baseCount, String type, String sourceId, String remark);
+
+ /**
+ * 鎵e噺鏄熺悆鍒(浣欓涓嶈冻鎶涘紓甯)
+ *
+ * @return 鎵e噺鍚庝綑棰
+ */
+ int deductTickets(String userId, String regionId, int count, String type, String sourceId, String remark);
+
+ /**
+ * 褰撳墠鐢ㄦ埛鍦ㄥ晢鍦堝唴鐨勬帓鍚(0琛ㄧず鏈笂姒)
+ */
+ int getRankNo(String regionId, String userId);
+
+ /**
+ * 鍒嗛〉鏌ヨ鐢ㄦ埛鏄熺悆鍒告槑缁嗘祦姘(鎸夋椂闂村掑簭)
+ */
+ Page pageLog(String userId, String regionId, Integer pageNumber, Integer pageSize);
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetAdminServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetAdminServiceImpl.java
new file mode 100644
index 00000000..3d46e0fa
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetAdminServiceImpl.java
@@ -0,0 +1,288 @@
+package cc.hiver.mall.planet.service.impl;
+
+import cc.hiver.core.common.exception.HiverException;
+import cc.hiver.core.common.vo.PageVo;
+import cc.hiver.mall.planet.entity.*;
+import cc.hiver.mall.planet.mapper.*;
+import cc.hiver.mall.planet.service.PlanetAdminService;
+import cc.hiver.mall.planet.service.PlanetDrawService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * 鐧藉珫鏄熺悆-鍚庡彴绠$悊鏈嶅姟瀹炵幇
+ *
+ * @author hiver
+ */
+@Service
+public class PlanetAdminServiceImpl implements PlanetAdminService {
+
+ @Autowired
+ private PlanetPoolMapper poolMapper;
+
+ @Autowired
+ private PlanetRewardMapper rewardMapper;
+
+ @Autowired
+ private PlanetTaskMapper taskMapper;
+
+ @Autowired
+ private PlanetBuffMapper buffMapper;
+
+ @Autowired
+ private PlanetNewsMapper newsMapper;
+
+ @Autowired
+ private PlanetTicketMapper ticketMapper;
+
+ @Autowired
+ private PlanetHuntRecordMapper huntRecordMapper;
+
+ @Autowired
+ private PlanetDrawRecordMapper drawRecordMapper;
+
+ @Autowired
+ private PlanetDrawWinnerMapper drawWinnerMapper;
+
+ @Autowired
+ private PlanetDrawService drawService;
+
+ private static String genId() {
+ return UUID.randomUUID().toString().replace("-", "");
+ }
+
+ private Page page(PageVo pageVo) {
+ final int num = pageVo == null || pageVo.getPageNumber() <= 0 ? 1 : pageVo.getPageNumber();
+ final int size = pageVo == null || pageVo.getPageSize() <= 0 ? 10 : pageVo.getPageSize();
+ return new Page<>(num, size);
+ }
+
+ @Override
+ public Page pagePools(PageVo pageVo, String regionId, Integer status) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ if (StringUtils.isNotEmpty(regionId)) {
+ qw.eq(PlanetPool::getRegionId, regionId);
+ }
+ if (status != null) {
+ qw.eq(PlanetPool::getStatus, status);
+ }
+ qw.orderByDesc(PlanetPool::getCreateTime);
+ return poolMapper.selectPage(page(pageVo), qw);
+ }
+
+ @Override
+ public PlanetPool savePool(PlanetPool pool) {
+ if (StringUtils.isEmpty(pool.getId())) {
+ pool.setId(genId());
+ if (pool.getStatus() == null) {
+ pool.setStatus(0);
+ }
+ if (pool.getJoinCount() == null) {
+ pool.setJoinCount(0);
+ }
+ if (pool.getTicketTotal() == null) {
+ pool.setTicketTotal(0);
+ }
+ if (pool.getWinnerCount() == null) {
+ pool.setWinnerCount(0);
+ }
+ pool.setCreateTime(new Date());
+ pool.setUpdateTime(new Date());
+ poolMapper.insert(pool);
+ } else {
+ pool.setUpdateTime(new Date());
+ poolMapper.updateById(pool);
+ }
+ return pool;
+ }
+
+ @Override
+ public void deletePool(String id) {
+ poolMapper.deleteById(id);
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetReward::getPoolId, id);
+ rewardMapper.delete(qw);
+ }
+
+ @Override
+ public List rewardsOfPool(String poolId) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetReward::getPoolId, poolId).orderByAsc(PlanetReward::getSort);
+ return rewardMapper.selectList(qw);
+ }
+
+ @Override
+ public PlanetReward saveReward(PlanetReward reward) {
+ if (StringUtils.isEmpty(reward.getId())) {
+ reward.setId(genId());
+ reward.setCreateTime(new Date());
+ rewardMapper.insert(reward);
+ } else {
+ rewardMapper.updateById(reward);
+ }
+ return reward;
+ }
+
+ @Override
+ public void deleteReward(String id) {
+ rewardMapper.deleteById(id);
+ }
+
+ @Override
+ public Page pageTasks(PageVo pageVo, String regionId) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ if (StringUtils.isNotEmpty(regionId)) {
+ qw.eq(PlanetTask::getRegionId, regionId);
+ }
+ qw.orderByAsc(PlanetTask::getSort);
+ return taskMapper.selectPage(page(pageVo), qw);
+ }
+
+ @Override
+ public PlanetTask saveTask(PlanetTask task) {
+ if (StringUtils.isEmpty(task.getId())) {
+ task.setId(genId());
+ if (task.getEnabled() == null) {
+ task.setEnabled(1);
+ }
+ task.setCreateTime(new Date());
+ taskMapper.insert(task);
+ } else {
+ taskMapper.updateById(task);
+ }
+ return task;
+ }
+
+ @Override
+ public void deleteTask(String id) {
+ taskMapper.deleteById(id);
+ }
+
+ @Override
+ public Page pageBuffs(PageVo pageVo, String regionId) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ if (StringUtils.isNotEmpty(regionId)) {
+ qw.eq(PlanetBuff::getRegionId, regionId);
+ }
+ qw.orderByAsc(PlanetBuff::getSort);
+ return buffMapper.selectPage(page(pageVo), qw);
+ }
+
+ @Override
+ public PlanetBuff saveBuff(PlanetBuff buff) {
+ if (StringUtils.isEmpty(buff.getId())) {
+ buff.setId(genId());
+ if (buff.getEnabled() == null) {
+ buff.setEnabled(1);
+ }
+ buff.setCreateTime(new Date());
+ buffMapper.insert(buff);
+ } else {
+ buffMapper.updateById(buff);
+ }
+ return buff;
+ }
+
+ @Override
+ public void deleteBuff(String id) {
+ buffMapper.deleteById(id);
+ }
+
+ @Override
+ public Page pageNews(PageVo pageVo, String regionId) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ if (StringUtils.isNotEmpty(regionId)) {
+ qw.eq(PlanetNews::getRegionId, regionId);
+ }
+ qw.orderByDesc(PlanetNews::getCreateTime);
+ return newsMapper.selectPage(page(pageVo), qw);
+ }
+
+ @Override
+ public PlanetNews saveNews(PlanetNews news) {
+ if (StringUtils.isEmpty(news.getId())) {
+ news.setId(genId());
+ if (news.getEnabled() == null) {
+ news.setEnabled(1);
+ }
+ if (news.getIsTop() == null) {
+ news.setIsTop(0);
+ }
+ news.setCreateTime(new Date());
+ newsMapper.insert(news);
+ } else {
+ newsMapper.updateById(news);
+ }
+ return news;
+ }
+
+ @Override
+ public void deleteNews(String id) {
+ newsMapper.deleteById(id);
+ }
+
+ @Override
+ public Page pageRank(PageVo pageVo, String regionId) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ if (StringUtils.isNotEmpty(regionId)) {
+ qw.eq(PlanetTicket::getRegionId, regionId);
+ }
+ qw.orderByDesc(PlanetTicket::getTicketCount);
+ return ticketMapper.selectPage(page(pageVo), qw);
+ }
+
+ @Override
+ public Page pageHunts(PageVo pageVo, String regionId) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ if (StringUtils.isNotEmpty(regionId)) {
+ qw.eq(PlanetHuntRecord::getRegionId, regionId);
+ }
+ qw.orderByDesc(PlanetHuntRecord::getCreateTime);
+ return huntRecordMapper.selectPage(page(pageVo), qw);
+ }
+
+ @Override
+ public Page pageDraws(PageVo pageVo, String regionId) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ if (StringUtils.isNotEmpty(regionId)) {
+ qw.eq(PlanetDrawRecord::getRegionId, regionId);
+ }
+ qw.orderByDesc(PlanetDrawRecord::getDrawTime);
+ return drawRecordMapper.selectPage(page(pageVo), qw);
+ }
+
+ @Override
+ public Page pageWinners(PageVo pageVo, String regionId, String periodNo) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ if (StringUtils.isNotEmpty(regionId)) {
+ qw.eq(PlanetDrawWinner::getRegionId, regionId);
+ }
+ if (StringUtils.isNotEmpty(periodNo)) {
+ qw.eq(PlanetDrawWinner::getPeriodNo, periodNo);
+ }
+ qw.orderByDesc(PlanetDrawWinner::getCreateTime);
+ return drawWinnerMapper.selectPage(page(pageVo), qw);
+ }
+
+ @Override
+ public PlanetDrawRecord manualDraw(String poolId, String regionId) {
+ if (StringUtils.isEmpty(regionId)) {
+ throw new HiverException("缂哄皯鍟嗗湀鍙傛暟锛屽紑濂栦粎闄愭湰鍖哄煙鍐呰繘琛");
+ }
+ final PlanetPool pool = poolMapper.selectById(poolId);
+ if (pool == null) {
+ throw new HiverException("濂栨睜涓嶅瓨鍦");
+ }
+ if (!regionId.equals(pool.getRegionId())) {
+ throw new HiverException("鏃犳潈瀵瑰叾瀹冨晢鍦堢殑濂栨睜寮濂");
+ }
+ return drawService.drawPool(poolId);
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetBoxServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetBoxServiceImpl.java
new file mode 100644
index 00000000..0114885d
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetBoxServiceImpl.java
@@ -0,0 +1,107 @@
+package cc.hiver.mall.planet.service.impl;
+
+import cc.hiver.core.common.exception.HiverException;
+import cc.hiver.mall.planet.constant.PlanetConstant;
+import cc.hiver.mall.planet.entity.PlanetTicket;
+import cc.hiver.mall.planet.mapper.PlanetTicketMapper;
+import cc.hiver.mall.planet.pojo.PlanetBoxResultVo;
+import cc.hiver.mall.planet.service.PlanetBoxService;
+import cc.hiver.mall.planet.service.PlanetBuffService;
+import cc.hiver.mall.planet.service.PlanetNewsService;
+import cc.hiver.mall.planet.service.PlanetTicketService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.util.Date;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * 鐧藉珫鏄熺悆-骞歌繍瀹濈鏈嶅姟瀹炵幇
+ *
+ * @author hiver
+ */
+@Service
+public class PlanetBoxServiceImpl implements PlanetBoxService {
+
+ @Autowired
+ private PlanetTicketService ticketService;
+
+ @Autowired
+ private PlanetTicketMapper ticketMapper;
+
+ @Autowired
+ private PlanetBuffService buffService;
+
+ @Autowired
+ private PlanetNewsService newsService;
+
+ @Override
+ public PlanetBoxResultVo openDailyBox(String userId, String regionId) {
+ final PlanetTicket ticket = ticketService.getOrCreate(userId, regionId, null, null, null);
+ final LocalDate today = LocalDate.now();
+ if (ticket.getBoxOpenedDate() != null && ticket.getBoxOpenedDate().isEqual(today)) {
+ throw new HiverException("浠婃棩瀹濈宸插紑鍚紝鏄庡ぉ鍐嶆潵");
+ }
+ // 鏍囪宸插紑
+ ticket.setBoxOpenedDate(today);
+ ticket.setUpdateTime(new Date());
+ ticketMapper.updateById(ticket);
+
+ // 鎶藉彇濂栧姳锛氬姞鏉冮殢鏈
+ // 1鍒50% / 2鍒22% / 5鍒8% / 骞歌繍BUFF6% / 闃叉姢BUFF6% / 鍙屽岯UFF5% / 鎺犲ずBUFF3%
+ final int roll = ThreadLocalRandom.current().nextInt(100);
+ final PlanetBoxResultVo vo = new PlanetBoxResultVo();
+ if (roll < 50) {
+ grantTicket(userId, regionId, 1, vo, "骞歌繍瀹濈寮鍑 1 寮犳槦鐞冨埜");
+ } else if (roll < 72) {
+ grantTicket(userId, regionId, 2, vo, "骞歌繍瀹濈寮鍑 2 寮犳槦鐞冨埜");
+ } else if (roll < 80) {
+ grantTicket(userId, regionId, 5, vo, "骞歌繍瀹濈寮鍑 5 寮犳槦鐞冨埜");
+ } else if (roll < 86) {
+ grantBuff(userId, regionId, PlanetConstant.BUFF_LUCKY, "骞歌繍鏄熻景", vo);
+ } else if (roll < 92) {
+ grantBuff(userId, regionId, PlanetConstant.BUFF_SHIELD, "鏄熼檯闃叉姢缃", vo);
+ } else if (roll < 97) {
+ grantBuff(userId, regionId, PlanetConstant.BUFF_DOUBLE, "鍙屽嶈兘閲", vo);
+ } else {
+ grantBuff(userId, regionId, PlanetConstant.BUFF_HUNT, "杩界寧闆疯揪", vo);
+ }
+
+ newsService.addNews(regionId, PlanetConstant.NEWS_BOX,
+ safeName(ticket.getNickname()) + " 骞歌繍寮鍚疂绠憋紝" + vo.getRewardName(),
+ userId, ticket.getNickname());
+ return vo;
+ }
+
+ @Override
+ public boolean boxAvailable(String userId, String regionId) {
+ final PlanetTicket ticket = ticketService.getByUser(userId, regionId);
+ if (ticket == null || ticket.getBoxOpenedDate() == null) {
+ return true;
+ }
+ return !ticket.getBoxOpenedDate().isEqual(LocalDate.now());
+ }
+
+ private void grantTicket(String userId, String regionId, int count, PlanetBoxResultVo vo, String name) {
+ ticketService.addTickets(userId, regionId, count, PlanetConstant.TICKET_TYPE_BOX,
+ UUID.randomUUID().toString().replace("-", ""), "瀹濈濂栧姳");
+ vo.setRewardType("ticket");
+ vo.setTicketCount(count);
+ vo.setRewardName("鑾峰緱 " + count + " 寮犳槦鐞冨埜");
+ vo.setMessage(name);
+ }
+
+ private void grantBuff(String userId, String regionId, String type, String name, PlanetBoxResultVo vo) {
+ buffService.grantBuff(userId, regionId, type, "box");
+ vo.setRewardType("buff");
+ vo.setBuffType(type);
+ vo.setRewardName("鑾峰緱銆" + name + "銆嶅鐩");
+ vo.setMessage("骞歌繍瀹濈寮鍑恒" + name + "銆嶅鐩");
+ }
+
+ private String safeName(String name) {
+ return name == null || name.isEmpty() ? "绁炵鍚屽" : name;
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetBuffServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetBuffServiceImpl.java
new file mode 100644
index 00000000..b7706291
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetBuffServiceImpl.java
@@ -0,0 +1,276 @@
+package cc.hiver.mall.planet.service.impl;
+
+import cc.hiver.core.common.exception.HiverException;
+import cc.hiver.mall.planet.constant.PlanetConstant;
+import cc.hiver.mall.planet.entity.PlanetBuff;
+import cc.hiver.mall.planet.entity.PlanetBuffRecord;
+import cc.hiver.mall.planet.mapper.PlanetBuffMapper;
+import cc.hiver.mall.planet.mapper.PlanetBuffRecordMapper;
+import cc.hiver.mall.planet.pojo.PlanetBuffVo;
+import cc.hiver.mall.planet.service.PlanetBuffService;
+import cc.hiver.mall.planet.service.PlanetTicketService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+/**
+ * 鐧藉珫鏄熺悆-BUFF鏈嶅姟瀹炵幇
+ *
+ * @author hiver
+ */
+@Service
+public class PlanetBuffServiceImpl implements PlanetBuffService {
+
+ @Autowired
+ private PlanetBuffMapper buffMapper;
+
+ @Autowired
+ private PlanetBuffRecordMapper buffRecordMapper;
+
+ @Autowired
+ private PlanetTicketService ticketService;
+
+ @Override
+ public List shopList(String userId, String regionId) {
+ final List buffs = listEnabledBuffs(regionId);
+ final List actives = activeBuffs(userId);
+ final List list = new ArrayList<>();
+ for (PlanetBuff buff : buffs) {
+ final PlanetBuffVo vo = toVo(buff);
+ for (PlanetBuffRecord r : actives) {
+ if (r.getType() != null && r.getType().equals(buff.getType())) {
+ vo.setActive(true);
+ vo.setActiveEndTime(r.getEndTime());
+ break;
+ }
+ }
+ list.add(vo);
+ }
+ return list;
+ }
+
+ @Override
+ public PlanetBuffVo buyBuff(String userId, String regionId, String buffId) {
+ final PlanetBuff buff = buffMapper.selectById(buffId);
+ if (buff == null || buff.getEnabled() == null || buff.getEnabled() != 1) {
+ throw new HiverException("璇UFF涓嶅瓨鍦ㄦ垨宸蹭笅鏋");
+ }
+ if (hasActiveBuff(userId, buff.getType())) {
+ throw new HiverException("璇ュ鐩婃鍦ㄧ敓鏁堜腑锛岃鍕块噸澶嶈喘涔");
+ }
+ if (buff.getCostTickets() != null && buff.getCostTickets() > 0) {
+ ticketService.deductTickets(userId, regionId, buff.getCostTickets(),
+ PlanetConstant.TICKET_TYPE_BUFF, buff.getId() + ":" + System.currentTimeMillis(),
+ "璐拱BUFF-" + buff.getName());
+ }
+ final PlanetBuffRecord record = grantBuffInner(userId, regionId, buff, "buy");
+ final PlanetBuffVo vo = toVo(buff);
+ vo.setActive(true);
+ vo.setActiveEndTime(record.getEndTime());
+ return vo;
+ }
+
+ @Override
+ public List activeBuffs(String userId) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetBuffRecord::getUserId, userId)
+ .eq(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_ACTIVE)
+ .gt(PlanetBuffRecord::getEndTime, new Date())
+ .orderByDesc(PlanetBuffRecord::getEndTime);
+ return buffRecordMapper.selectList(qw);
+ }
+
+ @Override
+ public boolean hasActiveBuff(String userId, String type) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetBuffRecord::getUserId, userId)
+ .eq(PlanetBuffRecord::getType, type)
+ .eq(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_ACTIVE)
+ .gt(PlanetBuffRecord::getEndTime, new Date());
+ return buffRecordMapper.selectCount(qw) > 0;
+ }
+
+ @Override
+ public PlanetBuffRecord grantBuff(String userId, String regionId, String type, String source) {
+ final PlanetBuff buff = findBuffByType(regionId, type);
+ if (buff == null) {
+ final PlanetBuff temp = new PlanetBuff();
+ temp.setType(type);
+ temp.setDurationHours(PlanetConstant.AUTO_SHIELD_HOURS);
+ temp.setEffectValue(BigDecimal.ZERO);
+ return grantBuffInner(userId, regionId, temp, source);
+ }
+ return grantBuffInner(userId, regionId, buff, source);
+ }
+
+ @Override
+ public double luckyWeightFactor(String userId) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetBuffRecord::getUserId, userId)
+ .eq(PlanetBuffRecord::getType, PlanetConstant.BUFF_LUCKY)
+ .eq(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_ACTIVE)
+ .gt(PlanetBuffRecord::getEndTime, new Date())
+ .orderByDesc(PlanetBuffRecord::getEffectValue)
+ .last("limit 1");
+ final List list = buffRecordMapper.selectList(qw);
+ if (list != null && !list.isEmpty() && list.get(0).getEffectValue() != null) {
+ return 1.0 + list.get(0).getEffectValue().doubleValue();
+ }
+ return 1.0;
+ }
+
+ @Override
+ public Map> activeBuffTypes(Collection userIds) {
+ final Map> map = new HashMap<>();
+ if (userIds == null || userIds.isEmpty()) {
+ return map;
+ }
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.in(PlanetBuffRecord::getUserId, userIds)
+ .eq(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_ACTIVE)
+ .gt(PlanetBuffRecord::getEndTime, new Date())
+ .select(PlanetBuffRecord::getUserId, PlanetBuffRecord::getType);
+ final List records = buffRecordMapper.selectList(qw);
+ for (PlanetBuffRecord r : records) {
+ map.computeIfAbsent(r.getUserId(), k -> new HashSet<>()).add(r.getType());
+ }
+ return map;
+ }
+
+ @Override
+ public Map luckyWeightFactors(Collection userIds) {
+ final Map map = new HashMap<>();
+ if (userIds == null || userIds.isEmpty()) {
+ return map;
+ }
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.in(PlanetBuffRecord::getUserId, userIds)
+ .eq(PlanetBuffRecord::getType, PlanetConstant.BUFF_LUCKY)
+ .eq(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_ACTIVE)
+ .gt(PlanetBuffRecord::getEndTime, new Date())
+ .select(PlanetBuffRecord::getUserId, PlanetBuffRecord::getEffectValue);
+ final List records = buffRecordMapper.selectList(qw);
+ for (PlanetBuffRecord r : records) {
+ if (r.getEffectValue() == null) {
+ continue;
+ }
+ final double factor = 1.0 + r.getEffectValue().doubleValue();
+ // 鍚岀被鍨嬪彲鑳藉鏉★紝鍙栨渶澶у姞鎴
+ map.merge(r.getUserId(), factor, Math::max);
+ }
+ return map;
+ }
+
+ private PlanetBuffVo toVo(PlanetBuff buff) {
+ final PlanetBuffVo vo = new PlanetBuffVo();
+ vo.setId(buff.getId());
+ vo.setType(buff.getType());
+ vo.setName(buff.getName());
+ vo.setDescription(buff.getDescription());
+ vo.setIcon(buff.getIcon());
+ vo.setCostTickets(buff.getCostTickets());
+ vo.setDurationHours(buff.getDurationHours());
+ vo.setEffectValue(buff.getEffectValue());
+ vo.setActive(false);
+ return vo;
+ }
+
+ private PlanetBuffRecord grantBuffInner(String userId, String regionId, PlanetBuff buff, String source) {
+ final PlanetBuffRecord record = new PlanetBuffRecord();
+ record.setId(UUID.randomUUID().toString().replace("-", ""));
+ record.setUserId(userId);
+ record.setRegionId(regionId);
+ record.setBuffId(buff.getId());
+ record.setType(buff.getType());
+ record.setEffectValue(buff.getEffectValue() == null ? BigDecimal.ZERO : buff.getEffectValue());
+ final Date now = new Date();
+ record.setStartTime(now);
+ final int hours = buff.getDurationHours() == null ? PlanetConstant.AUTO_SHIELD_HOURS : buff.getDurationHours();
+ record.setEndTime(new Date(now.getTime() + (long) hours * 3600 * 1000));
+ record.setStatus(PlanetConstant.BUFF_STATUS_ACTIVE);
+ record.setSource(source);
+ record.setCreateTime(now);
+ buffRecordMapper.insert(record);
+ return record;
+ }
+
+ private PlanetBuff findBuffByType(String regionId, String type) {
+ // 鍟嗗湀寮洪殧绂伙細浠呮煡鏈晢鍦圔UFF閰嶇疆锛屼笉鍐嶅洖閫鍏ㄥ眬榛樿
+ if (StringUtils.isEmpty(regionId)) {
+ return null;
+ }
+ PlanetBuff buff = selectRegionBuff(regionId, type);
+ if (buff == null) {
+ seedDefaultBuffs(regionId);
+ buff = selectRegionBuff(regionId, type);
+ }
+ return buff;
+ }
+
+ private PlanetBuff selectRegionBuff(String regionId, String type) {
+ final LambdaQueryWrapper rq = new LambdaQueryWrapper<>();
+ rq.eq(PlanetBuff::getType, type).eq(PlanetBuff::getEnabled, 1)
+ .eq(PlanetBuff::getRegionId, regionId).last("limit 1");
+ return buffMapper.selectOne(rq);
+ }
+
+ private List listEnabledBuffs(String regionId) {
+ // 鍟嗗湀寮洪殧绂伙細浠呰繑鍥炴湰鍟嗗湀BUFF锛岄娆¤闂嚜鍔ㄥ垵濮嬪寲榛樿BUFF
+ if (StringUtils.isEmpty(regionId)) {
+ return new ArrayList<>();
+ }
+ List list = selectRegionBuffs(regionId);
+ if (list.isEmpty()) {
+ seedDefaultBuffs(regionId);
+ list = selectRegionBuffs(regionId);
+ }
+ return list;
+ }
+
+ private List selectRegionBuffs(String regionId) {
+ final LambdaQueryWrapper rq = new LambdaQueryWrapper<>();
+ rq.eq(PlanetBuff::getRegionId, regionId).eq(PlanetBuff::getEnabled, 1)
+ .orderByAsc(PlanetBuff::getSort);
+ final List list = buffMapper.selectList(rq);
+ return list == null ? new ArrayList<>() : list;
+ }
+
+ /**
+ * 涓烘寚瀹氬晢鍦堝垵濮嬪寲榛樿BUFF閰嶇疆(骞傜瓑锛氭寜 type 瀛樺湪鍒欒烦杩)銆
+ */
+ private void seedDefaultBuffs(String regionId) {
+ if (StringUtils.isEmpty(regionId)) {
+ return;
+ }
+ seedBuff(regionId, PlanetConstant.BUFF_DOUBLE, "鍙屽嶈兘閲", "24灏忔椂鍐呰幏寰楁槦鐞冨埜缈诲", 5, 24, new BigDecimal("2.00"), 1);
+ seedBuff(regionId, PlanetConstant.BUFF_SHIELD, "鏄熼檯闃叉姢缃", "24灏忔椂鍐呭厤鐤拷鎹(鍏诲彧灏忔澗榧犺鍗)", 8, 24, BigDecimal.ZERO, 2);
+ seedBuff(regionId, PlanetConstant.BUFF_LUCKY, "骞歌繍鏄熻景", "寮濂栨潈閲 +20%", 6, 24, new BigDecimal("0.20"), 3);
+ seedBuff(regionId, PlanetConstant.BUFF_HUNT, "杩界寧闆疯揪", "杩芥崟鎴愬姛鐜囨彁鍗", 4, 24, new BigDecimal("0.30"), 4);
+ seedBuff(regionId, PlanetConstant.BUFF_STEALTH, "闅愯韩鏂楃", "涓嶅嚭鐜板湪閫氱級姒", 6, 24, BigDecimal.ZERO, 5);
+ }
+
+ private void seedBuff(String regionId, String type, String name, String desc, int cost, int hours, BigDecimal effect, int sort) {
+ final LambdaQueryWrapper exist = new LambdaQueryWrapper<>();
+ exist.eq(PlanetBuff::getRegionId, regionId).eq(PlanetBuff::getType, type);
+ if (buffMapper.selectCount(exist) > 0) {
+ return;
+ }
+ final PlanetBuff buff = new PlanetBuff();
+ buff.setId(UUID.randomUUID().toString().replace("-", ""));
+ buff.setRegionId(regionId);
+ buff.setType(type);
+ buff.setName(name);
+ buff.setDescription(desc);
+ buff.setCostTickets(cost);
+ buff.setDurationHours(hours);
+ buff.setEffectValue(effect);
+ buff.setSort(sort);
+ buff.setEnabled(1);
+ buff.setCreateTime(new Date());
+ buffMapper.insert(buff);
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetDrawServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetDrawServiceImpl.java
new file mode 100644
index 00000000..e086c64d
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetDrawServiceImpl.java
@@ -0,0 +1,393 @@
+package cc.hiver.mall.planet.service.impl;
+
+import cc.hiver.core.common.exception.HiverException;
+import cc.hiver.mall.planet.constant.PlanetConstant;
+import cc.hiver.mall.planet.entity.*;
+import cc.hiver.mall.planet.mapper.*;
+import cc.hiver.mall.planet.service.PlanetBuffService;
+import cc.hiver.mall.planet.service.PlanetDrawService;
+import cc.hiver.mall.planet.service.PlanetNewsService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
+
+/**
+ * 鐧藉珫鏄熺悆-寮濂栨湇鍔″疄鐜
+ *
+ * @author hiver
+ */
+@Slf4j
+@Service
+public class PlanetDrawServiceImpl implements PlanetDrawService {
+
+ /** 姣忔棩寮濂栨椂鍒(24灏忔椂鍒)锛屽埌鐐圭敱瀹氭椂浠诲姟鑷姩寮濂栵紝姣忔棩涓鎶 */
+ private static final int DAILY_DRAW_HOUR = 22;
+
+ @Autowired
+ private PlanetPoolMapper poolMapper;
+
+ @Autowired
+ private PlanetRewardMapper rewardMapper;
+
+ @Autowired
+ private PlanetTicketMapper ticketMapper;
+
+ @Autowired
+ private PlanetDrawRecordMapper drawRecordMapper;
+
+ @Autowired
+ private PlanetDrawWinnerMapper drawWinnerMapper;
+
+ @Autowired
+ private PlanetBuffService buffService;
+
+ @Autowired
+ private PlanetNewsService newsService;
+
+ /**
+ * 鎶藉鍔熻兘寮哄埗鎸夊晢鍦堥殧绂伙細缂哄皯 regionId 鐩存帴鎷掔粷锛屾潨缁濊法鍖哄煙寮濂/鏌ヨ銆
+ */
+ private void requireRegion(String regionId) {
+ if (StringUtils.isEmpty(regionId)) {
+ throw new HiverException("缂哄皯鍟嗗湀鍙傛暟锛屾娊濂栦粎闄愭湰鍖哄煙鍐呰繘琛");
+ }
+ }
+
+ @Override
+ public PlanetPool getCurrentPool(String regionId) {
+ requireRegion(regionId);
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetPool::getRegionId, regionId)
+ .eq(PlanetPool::getStatus, PlanetConstant.POOL_STATUS_RUNNING)
+ .orderByDesc(PlanetPool::getCreateTime)
+ .last("limit 1");
+ PlanetPool pool = poolMapper.selectOne(qw);
+ if (pool == null) {
+ pool = createDefaultPool(regionId);
+ }
+ return pool;
+ }
+
+ private PlanetPool createDefaultPool(String regionId) {
+ final Date now = new Date();
+ final PlanetPool pool = new PlanetPool();
+ pool.setId(UUID.randomUUID().toString().replace("-", ""));
+ pool.setRegionId(regionId);
+ pool.setPeriodNo(genPeriodNo());
+ pool.setTitle("鐧藉珫鏄熺悆路鏈湡鐡滃垎");
+ pool.setPoolAmount(new BigDecimal("88.88"));
+ pool.setDrawTime(nextDrawTime(now));
+ pool.setJoinCount(0);
+ pool.setTicketTotal(0);
+ pool.setWinnerCount(0);
+ pool.setStatus(PlanetConstant.POOL_STATUS_RUNNING);
+ pool.setRemark("姣忔棩鎸夋槦鐞冨埜鍔犳潈鐡滃垎濂栭噾姹");
+ pool.setCreateTime(now);
+ pool.setUpdateTime(now);
+ poolMapper.insert(pool);
+ // 榛樿濂栭」
+ createDefaultRewards(pool);
+ return pool;
+ }
+
+ private void createDefaultRewards(PlanetPool pool) {
+ addReward(pool, 1, "涓绛夊", new BigDecimal("28.00"), 1, 1);
+ addReward(pool, 2, "浜岀瓑濂", new BigDecimal("8.80"), 2, 2);
+ addReward(pool, 3, "涓夌瓑濂", new BigDecimal("1.00"), 10, 3);
+ addReward(pool, 4, "骞歌繍濂", new BigDecimal("0.50"), 50, 4);
+ }
+
+ private void addReward(PlanetPool pool, int level, String name, BigDecimal amount, int quota, int sort) {
+ final PlanetReward reward = new PlanetReward();
+ reward.setId(UUID.randomUUID().toString().replace("-", ""));
+ reward.setPoolId(pool.getId());
+ reward.setRegionId(pool.getRegionId());
+ reward.setLevel(level);
+ reward.setLevelName(name);
+ reward.setRewardType(0);
+ reward.setAmount(amount);
+ reward.setQuota(quota);
+ reward.setSort(sort);
+ reward.setCreateTime(new Date());
+ rewardMapper.insert(reward);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public PlanetDrawRecord drawPool(String poolId) {
+ final PlanetPool pool = poolMapper.selectById(poolId);
+ if (pool == null) {
+ throw new HiverException("濂栨睜涓嶅瓨鍦");
+ }
+ if (pool.getStatus() != null && pool.getStatus() == PlanetConstant.POOL_STATUS_DRAWN) {
+ throw new HiverException("璇ユ湡宸插紑濂");
+ }
+ // 濂栨睜蹇呴』褰掑睘鏌愬晢鍦堬紝寮濂栧彧鍦ㄦ湰鍟嗗湀鍐呰繘琛
+ requireRegion(pool.getRegionId());
+ // 鍙備笌鑰咃細浠呮湰鍟嗗湀鍐呮寔鍒哥敤鎴
+ final LambdaQueryWrapper tq = new LambdaQueryWrapper<>();
+ tq.eq(PlanetTicket::getRegionId, pool.getRegionId())
+ .gt(PlanetTicket::getTicketCount, 0);
+ final List participants = ticketMapper.selectList(tq);
+
+ // 濂栭」鎸夊眰绾ф帓搴
+ final LambdaQueryWrapper rq = new LambdaQueryWrapper<>();
+ rq.eq(PlanetReward::getPoolId, poolId).orderByAsc(PlanetReward::getSort);
+ final List rewards = rewardMapper.selectList(rq);
+
+ // 鎵归噺鏌ヨ鎵鏈夊弬涓庤呯殑骞歌繍绯绘暟锛屼竴娆℃煡搴擄紝閬垮厤寰幆閫愪釜鏌ヨ
+ final Set userIds = participants.stream().map(PlanetTicket::getUserId).collect(Collectors.toCollection(LinkedHashSet::new));
+ final Map luckyMap = buffService.luckyWeightFactors(userIds);
+
+ // 鏋勫缓鍔犳潈鍊欓(鏉冮噸= 鍒告暟閲 * 骞歌繍绯绘暟)
+ final List poutPool = new ArrayList<>();
+ int ticketTotal = 0;
+ for (PlanetTicket t : participants) {
+ final double factor = luckyMap.getOrDefault(t.getUserId(), 1.0);
+ final double weight = t.getTicketCount() * factor;
+ poutPool.add(new Candidate(t, weight));
+ ticketTotal += t.getTicketCount();
+ }
+
+ final Date now = new Date();
+ // 寮濂栬褰
+ final PlanetDrawRecord record = new PlanetDrawRecord();
+ record.setId(UUID.randomUUID().toString().replace("-", ""));
+ record.setPoolId(pool.getId());
+ record.setRegionId(pool.getRegionId());
+ record.setPeriodNo(pool.getPeriodNo());
+ record.setPoolAmount(pool.getPoolAmount());
+ record.setJoinCount(participants.size());
+ record.setTicketTotal(ticketTotal);
+ record.setStatus(1);
+ record.setDrawTime(now);
+ record.setCreateTime(now);
+
+ int winnerCount = 0;
+ for (PlanetReward reward : rewards) {
+ final int quota = reward.getQuota() == null ? 0 : reward.getQuota();
+ for (int i = 0; i < quota; i++) {
+ if (poutPoolEmpty(poutPool)) {
+ break;
+ }
+ final Candidate winner = pickWeighted(poutPoolUsable(poutPool));
+ if (winner == null) {
+ break;
+ }
+ winner.win = true;
+ saveWinner(record, reward, winner.ticket, now);
+ winnerCount++;
+ }
+ }
+ record.setWinnerCount(winnerCount);
+ drawRecordMapper.insert(record);
+
+ // 鏇存柊濂栨睜涓哄凡寮濂
+ pool.setStatus(PlanetConstant.POOL_STATUS_DRAWN);
+ pool.setJoinCount(participants.size());
+ pool.setTicketTotal(ticketTotal);
+ pool.setWinnerCount(winnerCount);
+ pool.setUpdateTime(now);
+ poolMapper.updateById(pool);
+
+ // 蹇
+ newsService.addNews(pool.getRegionId(), PlanetConstant.NEWS_DRAW,
+ "鏈湡濂栨睜 锟" + pool.getPoolAmount() + " 宸插紑濂栵紝" + winnerCount + " 浣嶅悓瀛︾摐鍒嗘垚鍔",
+ null, null);
+
+ // 寮鍚笅涓鏈
+ createNextPool(pool);
+ log.info("[鐧藉珫鏄熺悆] 寮濂栧畬鎴 poolId={}, 鍙備笌={}, 涓={}", poolId, participants.size(), winnerCount);
+ return record;
+ }
+
+ private void createNextPool(PlanetPool prev) {
+ final Date now = new Date();
+ final PlanetPool pool = new PlanetPool();
+ pool.setId(UUID.randomUUID().toString().replace("-", ""));
+ pool.setRegionId(prev.getRegionId());
+ pool.setPeriodNo(genPeriodNo());
+ pool.setTitle(prev.getTitle());
+ pool.setPoolAmount(prev.getPoolAmount());
+ pool.setDrawTime(nextDrawTime(now));
+ pool.setJoinCount(0);
+ pool.setTicketTotal(0);
+ pool.setWinnerCount(0);
+ pool.setStatus(PlanetConstant.POOL_STATUS_RUNNING);
+ pool.setRemark(prev.getRemark());
+ pool.setCreateTime(now);
+ pool.setUpdateTime(now);
+ poolMapper.insert(pool);
+ // 澶嶅埗涓婃湡濂栭」閰嶇疆
+ final LambdaQueryWrapper rq = new LambdaQueryWrapper<>();
+ rq.eq(PlanetReward::getPoolId, prev.getId()).orderByAsc(PlanetReward::getSort);
+ for (PlanetReward r : rewardMapper.selectList(rq)) {
+ addReward(pool, r.getLevel(), r.getLevelName(), r.getAmount(), r.getQuota() == null ? 0 : r.getQuota(), r.getSort() == null ? 0 : r.getSort());
+ }
+ }
+
+ @Override
+ public int drawDuePools() {
+ // 鎵弿鎵鏈夊埌鐐规湭寮濂栫殑濂栨睜锛氭瘡涓姹犻兘褰掑睘鍞竴鏍″尯锛岄愪釜鐙珛寮濂栵紝
+ // 鍚勬牎鍖哄弬涓庤/濂栭」/濂栭噾浜掍笉褰卞搷锛屽疄鐜扳滄寜鏍″尯鍒嗗埆寮濂栤濄
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetPool::getStatus, PlanetConstant.POOL_STATUS_RUNNING)
+ .le(PlanetPool::getDrawTime, new Date());
+ final List due = poolMapper.selectList(qw);
+ int count = 0;
+ for (PlanetPool pool : due) {
+ // 闃插尽锛氭棤鏍″尯褰掑睘鐨勫姹犱笉鍙備笌寮濂栵紝閬垮厤璺ㄦ牎鍖
+ if (StringUtils.isEmpty(pool.getRegionId())) {
+ log.warn("[鐧藉珫鏄熺悆] 璺宠繃鏃犳牎鍖哄姹 poolId={}, periodNo={}", pool.getId(), pool.getPeriodNo());
+ continue;
+ }
+ try {
+ drawPool(pool.getId());
+ count++;
+ log.info("[鐧藉珫鏄熺悆] 鏍″尯寮濂栧畬鎴 regionId={}, periodNo={}", pool.getRegionId(), pool.getPeriodNo());
+ } catch (Exception e) {
+ log.error("[鐧藉珫鏄熺悆] 鏍″尯寮濂栧け璐 regionId={}, poolId={}, {}", pool.getRegionId(), pool.getId(), e.getMessage(), e);
+ }
+ }
+ return count;
+ }
+
+ @Override
+ public PlanetDrawRecord latestDrawRecord(String regionId) {
+ requireRegion(regionId);
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetDrawRecord::getRegionId, regionId)
+ .orderByDesc(PlanetDrawRecord::getDrawTime).last("limit 1");
+ return drawRecordMapper.selectOne(qw);
+ }
+
+ @Override
+ public List winnersOfDraw(String drawId) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetDrawWinner::getDrawId, drawId).orderByAsc(PlanetDrawWinner::getRewardLevel);
+ return drawWinnerMapper.selectList(qw);
+ }
+
+ @Override
+ public List myWinning(String userId, String regionId) {
+ requireRegion(regionId);
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetDrawWinner::getUserId, userId)
+ .eq(PlanetDrawWinner::getRegionId, regionId)
+ .orderByDesc(PlanetDrawWinner::getCreateTime).last("limit 50");
+ return drawWinnerMapper.selectList(qw);
+ }
+
+ @Override
+ public void receive(String userId, String regionId, String winnerId) {
+ requireRegion(regionId);
+ final PlanetDrawWinner winner = drawWinnerMapper.selectById(winnerId);
+ if (winner == null || !userId.equals(winner.getUserId()) || !regionId.equals(winner.getRegionId())) {
+ throw new HiverException("涓璁板綍涓嶅瓨鍦");
+ }
+ if (winner.getIsReceived() != null && winner.getIsReceived() == 1) {
+ throw new HiverException("宸查鍙栵紝璇峰嬁閲嶅鎿嶄綔");
+ }
+ winner.setIsReceived(1);
+ winner.setReceivedTime(new Date());
+ drawWinnerMapper.updateById(winner);
+ // TODO: 鐜伴噾濂栧姳鍙湪姝ゅ鎺ラ挶鍖呭叆璐︼紱浼樻儬鍒稿鍔卞彲瀵规帴鍙戝埜閫昏緫
+ }
+
+ private void saveWinner(PlanetDrawRecord record, PlanetReward reward, PlanetTicket ticket, Date now) {
+ final PlanetDrawWinner winner = new PlanetDrawWinner();
+ winner.setId(UUID.randomUUID().toString().replace("-", ""));
+ winner.setDrawId(record.getId());
+ winner.setPoolId(record.getPoolId());
+ winner.setRegionId(record.getRegionId());
+ winner.setPeriodNo(record.getPeriodNo());
+ winner.setUserId(ticket.getUserId());
+ winner.setUserName(ticket.getNickname());
+ winner.setRewardLevel(reward.getLevel());
+ winner.setLevelName(reward.getLevelName());
+ winner.setRewardType(reward.getRewardType());
+ winner.setAmount(reward.getAmount());
+ winner.setCouponId(reward.getCouponId());
+ winner.setIsReceived(0);
+ winner.setCreateTime(now);
+ drawWinnerMapper.insert(winner);
+ }
+
+ private boolean poutPoolEmpty(List pool) {
+ for (Candidate c : pool) {
+ if (!c.win) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private List poutPoolUsable(List pool) {
+ final List usable = new ArrayList<>();
+ for (Candidate c : pool) {
+ if (!c.win) {
+ usable.add(c);
+ }
+ }
+ return usable;
+ }
+
+ private Candidate pickWeighted(List usable) {
+ if (usable.isEmpty()) {
+ return null;
+ }
+ double total = 0;
+ for (Candidate c : usable) {
+ total += Math.max(c.weight, 0.0001);
+ }
+ double r = ThreadLocalRandom.current().nextDouble() * total;
+ for (Candidate c : usable) {
+ r -= Math.max(c.weight, 0.0001);
+ if (r <= 0) {
+ return c;
+ }
+ }
+ return usable.get(usable.size() - 1);
+ }
+
+ private String genPeriodNo() {
+ return "P" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
+ }
+
+ /**
+ * 璁$畻涓嬩竴娆″紑濂栨椂闂达細姣忔棩鍥哄畾 {@link #DAILY_DRAW_HOUR} 鐐瑰紑濂栵紝宸茶繃鍒欓『寤跺埌娆℃棩锛屽疄鐜版瘡鏃ヤ竴鎶姐
+ */
+ private Date nextDrawTime(Date from) {
+ final Calendar c = Calendar.getInstance();
+ c.setTime(from);
+ c.set(Calendar.HOUR_OF_DAY, DAILY_DRAW_HOUR);
+ c.set(Calendar.MINUTE, 0);
+ c.set(Calendar.SECOND, 0);
+ c.set(Calendar.MILLISECOND, 0);
+ if (!c.getTime().after(from)) {
+ c.add(Calendar.DAY_OF_MONTH, 1);
+ }
+ return c.getTime();
+ }
+
+ /** 寮濂栧欓 */
+ private static class Candidate {
+ private final PlanetTicket ticket;
+ private final double weight;
+ private boolean win;
+
+ Candidate(PlanetTicket ticket, double weight) {
+ this.ticket = ticket;
+ this.weight = weight;
+ }
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetHuntServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetHuntServiceImpl.java
new file mode 100644
index 00000000..9e74ad90
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetHuntServiceImpl.java
@@ -0,0 +1,198 @@
+package cc.hiver.mall.planet.service.impl;
+
+import cc.hiver.core.common.exception.HiverException;
+import cc.hiver.mall.planet.constant.PlanetConstant;
+import cc.hiver.mall.planet.entity.PlanetBuffRecord;
+import cc.hiver.mall.planet.entity.PlanetHuntRecord;
+import cc.hiver.mall.planet.entity.PlanetTicket;
+import cc.hiver.mall.planet.mapper.PlanetHuntRecordMapper;
+import cc.hiver.mall.planet.pojo.PlanetHuntResultVo;
+import cc.hiver.mall.planet.service.*;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.util.Date;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * 鐧藉珫鏄熺悆-杩芥崟鏈嶅姟瀹炵幇
+ *
+ * @author hiver
+ */
+@Slf4j
+@Service
+public class PlanetHuntServiceImpl implements PlanetHuntService {
+
+ /** 鍩虹杩芥崟鎴愬姛鐜 */
+ private static final double BASE_SUCCESS_RATE = 0.8;
+
+ @Autowired
+ private PlanetHuntRecordMapper huntRecordMapper;
+
+ @Autowired
+ private PlanetTicketService ticketService;
+
+ @Autowired
+ private PlanetRankService rankService;
+
+ @Autowired
+ private PlanetBuffService buffService;
+
+ @Autowired
+ private PlanetNewsService newsService;
+
+ @Override
+ public PlanetHuntResultVo hunt(String fromUserId, String regionId, String toUserId) {
+ if (StringUtils.isEmpty(fromUserId) || StringUtils.isEmpty(toUserId)) {
+ throw new HiverException("鍙傛暟涓嶅畬鏁");
+ }
+ if (fromUserId.equals(toUserId)) {
+ throw new HiverException("涓嶈兘杩芥崟鑷繁");
+ }
+ final LocalDate today = LocalDate.now();
+ // 浠婃棩杩芥崟娆℃暟鏍¢獙
+ final int huntedToday = countTodayHunts(fromUserId, today);
+ if (huntedToday >= PlanetConstant.HUNT_DAILY_LIMIT) {
+ throw new HiverException("浠婃棩杩芥崟娆℃暟宸茬敤瀹岋紝鏄庡ぉ鍐嶆潵");
+ }
+ final PlanetTicket from = ticketService.getOrCreate(fromUserId, regionId, null, null, null);
+ final PlanetTicket target = rankService.getRankTarget(regionId, toUserId);
+ if (target == null) {
+ throw new HiverException("鐩爣涓嶅瓨鍦");
+ }
+ // 浠呭厑璁稿悓鍖哄煙浜掑姩
+ if (StringUtils.isNotEmpty(from.getRegionId()) && StringUtils.isNotEmpty(target.getRegionId())
+ && !from.getRegionId().equals(target.getRegionId())) {
+ throw new HiverException("鍙兘杩芥崟鍚屾牎鍖虹殑鍚屽");
+ }
+
+ final PlanetHuntResultVo vo = new PlanetHuntResultVo();
+ vo.setTargetName(target.getNickname());
+ vo.setGainTickets(0);
+ vo.setBountyTickets(0);
+ vo.setTotalGain(0);
+
+ // 鐩爣闃叉姢缃╋細闃叉姢BUFF 鎴 浠婃棩宸茶杩芥崟鎴愬姛杈句笂闄
+ final int targetHuntedSuccess = countTodayHuntedSuccess(toUserId, today);
+ final boolean shielded = buffService.hasActiveBuff(toUserId, PlanetConstant.BUFF_SHIELD)
+ || targetHuntedSuccess >= PlanetConstant.HUNTED_SUCCESS_LIMIT;
+
+ String result;
+ if (shielded) {
+ result = PlanetConstant.HUNT_SHIELD;
+ vo.setMessage("鐩爣鍚姩浜嗘槦闄呴槻鎶ょ僵锛屾湰娆¤拷鎹曟湭鑳界即鑾");
+ } else if (target.getTicketCount() == null || target.getTicketCount() <= 0) {
+ result = PlanetConstant.HUNT_FAIL;
+ vo.setMessage("鐩爣鏄熺悆绌虹┖濡備篃锛屾湭鑳界即鑾");
+ } else {
+ // 鎴愬姛鐜囷細鍩虹 + 杩芥崟BUFF鍔犳垚
+ double rate = BASE_SUCCESS_RATE;
+ final PlanetBuffRecord huntBuff = firstActiveBuff(fromUserId, PlanetConstant.BUFF_HUNT);
+ if (huntBuff != null && huntBuff.getEffectValue() != null) {
+ rate += huntBuff.getEffectValue().doubleValue();
+ }
+ if (rate > 1) {
+ rate = 1;
+ }
+ final boolean success = ThreadLocalRandom.current().nextDouble() < rate;
+ if (success) {
+ result = PlanetConstant.HUNT_SUCCESS;
+ final int gain = 1;
+ // 璁$畻鐩爣褰撳墠鎺掑悕鐨勬偓璧忓鍔
+ final int targetRank = rankService.userRankNo(regionId, toUserId);
+ final int bounty = PlanetConstant.bountyByRank(targetRank);
+ // 浠庣洰鏍囩即鑾
+ ticketService.deductTickets(toUserId, target.getRegionId(), gain,
+ PlanetConstant.TICKET_TYPE_HUNT, UUID.randomUUID().toString().replace("-", ""),
+ "琚" + safeName(from.getNickname()) + "杩芥崟缂磋幏");
+ // 鍙戣捣浜鸿幏寰楃即鑾峰埜 + 鎮祻鍒
+ ticketService.addTickets(fromUserId, regionId, gain,
+ PlanetConstant.TICKET_TYPE_HUNT, UUID.randomUUID().toString().replace("-", ""),
+ "杩芥崟缂磋幏" + safeName(target.getNickname()));
+ if (bounty > 0) {
+ ticketService.addTickets(fromUserId, regionId, bounty,
+ PlanetConstant.TICKET_TYPE_HUNT, UUID.randomUUID().toString().replace("-", ""),
+ "閫氱級鎮祻濂栧姳");
+ }
+ vo.setGainTickets(gain);
+ vo.setBountyTickets(bounty);
+ vo.setTotalGain(gain + bounty);
+ vo.setMessage("杩芥崟鎴愬姛锛佺即鑾 " + gain + " 寮犳槦鐞冨埜" + (bounty > 0 ? "锛岄澶栬幏寰 " + bounty + " 寮犳偓璧忓埜" : ""));
+
+ // 鐩爣琚拷鎴愬姛杈句笂闄愯嚜鍔ㄥ紑闃叉姢缃
+ if (targetHuntedSuccess + 1 >= PlanetConstant.HUNTED_SUCCESS_LIMIT) {
+ buffService.grantBuff(toUserId, target.getRegionId(), PlanetConstant.BUFF_SHIELD, "hunt");
+ }
+ // 蹇
+ newsService.addNews(regionId, PlanetConstant.NEWS_HUNT,
+ safeName(target.getNickname()) + " 閬亣杩芥崟锛屾崯澶 " + (gain) + " 寮犳槦鐞冨埜",
+ toUserId, target.getNickname());
+ } else {
+ result = PlanetConstant.HUNT_FAIL;
+ vo.setMessage("鐩爣椋炶埞鐏垫椿鏈哄姩锛屾湰娆¤拷鎹曟墤绌");
+ }
+ }
+ vo.setResult(result);
+
+ // 鍐欒拷鎹曡褰
+ saveHuntRecord(from, target, regionId, result, vo.getGainTickets(), vo.getBountyTickets(), today);
+ vo.setRemainHunt(Math.max(0, PlanetConstant.HUNT_DAILY_LIMIT - (huntedToday + 1)));
+ return vo;
+ }
+
+ @Override
+ public int remainHunt(String userId) {
+ final int used = countTodayHunts(userId, LocalDate.now());
+ return Math.max(0, PlanetConstant.HUNT_DAILY_LIMIT - used);
+ }
+
+ private int countTodayHunts(String userId, LocalDate date) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetHuntRecord::getFromUserId, userId)
+ .eq(PlanetHuntRecord::getHuntDate, date);
+ return huntRecordMapper.selectCount(qw).intValue();
+ }
+
+ private int countTodayHuntedSuccess(String userId, LocalDate date) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetHuntRecord::getToUserId, userId)
+ .eq(PlanetHuntRecord::getResult, PlanetConstant.HUNT_SUCCESS)
+ .eq(PlanetHuntRecord::getHuntDate, date);
+ return huntRecordMapper.selectCount(qw).intValue();
+ }
+
+ private PlanetBuffRecord firstActiveBuff(String userId, String type) {
+ for (PlanetBuffRecord r : buffService.activeBuffs(userId)) {
+ if (type.equals(r.getType())) {
+ return r;
+ }
+ }
+ return null;
+ }
+
+ private void saveHuntRecord(PlanetTicket from, PlanetTicket target, String regionId, String result,
+ int gain, int bounty, LocalDate date) {
+ final PlanetHuntRecord record = new PlanetHuntRecord();
+ record.setId(UUID.randomUUID().toString().replace("-", ""));
+ record.setFromUserId(from.getUserId());
+ record.setFromUserName(from.getNickname());
+ record.setToUserId(target.getUserId());
+ record.setToUserName(target.getNickname());
+ record.setRegionId(regionId);
+ record.setResult(result);
+ record.setGainTickets(gain);
+ record.setBountyTickets(bounty);
+ record.setHuntDate(date);
+ record.setCreateTime(new Date());
+ huntRecordMapper.insert(record);
+ }
+
+ private String safeName(String name) {
+ return StringUtils.isEmpty(name) ? "绁炵鍚屽" : name;
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetNewsServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetNewsServiceImpl.java
new file mode 100644
index 00000000..1d5cb3e0
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetNewsServiceImpl.java
@@ -0,0 +1,54 @@
+package cc.hiver.mall.planet.service.impl;
+
+import cc.hiver.mall.planet.entity.PlanetNews;
+import cc.hiver.mall.planet.mapper.PlanetNewsMapper;
+import cc.hiver.mall.planet.service.PlanetNewsService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * 鐧藉珫鏄熺悆-鏄熺悆蹇鏈嶅姟瀹炵幇
+ *
+ * @author hiver
+ */
+@Service
+public class PlanetNewsServiceImpl implements PlanetNewsService {
+
+ @Autowired
+ private PlanetNewsMapper newsMapper;
+
+ @Override
+ public void addNews(String regionId, String type, String content, String userId, String userName) {
+ final PlanetNews news = new PlanetNews();
+ news.setId(UUID.randomUUID().toString().replace("-", ""));
+ news.setRegionId(regionId);
+ news.setType(type);
+ news.setContent(content);
+ news.setUserId(userId);
+ news.setUserName(userName);
+ news.setIsTop(0);
+ news.setEnabled(1);
+ news.setCreateTime(new Date());
+ newsMapper.insert(news);
+ }
+
+ @Override
+ public List latest(String regionId, int limit) {
+ // 鍟嗗湀寮洪殧绂伙細浠呰繑鍥炴湰鍟嗗湀蹇锛屼笉鍐嶆贩鍏ュ叏灞蹇
+ if (StringUtils.isEmpty(regionId)) {
+ return new java.util.ArrayList<>();
+ }
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetNews::getEnabled, 1)
+ .eq(PlanetNews::getRegionId, regionId)
+ .orderByDesc(PlanetNews::getIsTop).orderByDesc(PlanetNews::getCreateTime)
+ .last("limit " + limit);
+ return newsMapper.selectList(qw);
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetRankServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetRankServiceImpl.java
new file mode 100644
index 00000000..84cb0f96
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetRankServiceImpl.java
@@ -0,0 +1,157 @@
+package cc.hiver.mall.planet.service.impl;
+
+import cc.hiver.core.common.redis.RedisTemplateHelper;
+import cc.hiver.mall.planet.constant.PlanetConstant;
+import cc.hiver.mall.planet.entity.PlanetTicket;
+import cc.hiver.mall.planet.mapper.PlanetTicketMapper;
+import cc.hiver.mall.planet.pojo.PlanetRankItemVo;
+import cc.hiver.mall.planet.service.PlanetBuffService;
+import cc.hiver.mall.planet.service.PlanetRankService;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * 鐧藉珫鏄熺悆-閫氱級姒滄湇鍔″疄鐜
+ *
+ * @author hiver
+ */
+@Slf4j
+@Service
+public class PlanetRankServiceImpl implements PlanetRankService {
+
+ /** 閫氱級姒滅紦瀛樺墠缂(鍩虹姒滃崟锛屼笉鍚 self 鏍囪) */
+ private static final String RANK_CACHE_PREFIX = "planet:rank:";
+ /** 缂撳瓨鏈夋晥鏈(绉)锛屾鍗曞厑璁哥煭鏃跺急涓鑷达紝闄嶄綆楂橀鏌ュ簱鍘嬪姏 */
+ private static final long RANK_CACHE_SECONDS = 30L;
+
+ @Autowired
+ private PlanetTicketMapper ticketMapper;
+
+ @Autowired
+ private PlanetBuffService buffService;
+
+ @Autowired
+ private RedisTemplateHelper redisTemplateHelper;
+
+ @Override
+ public List rankTop(String regionId, int topN, String currentUserId) {
+ List base = readCache(regionId, topN);
+ if (base == null) {
+ base = buildRankFromDb(regionId, topN);
+ writeCache(regionId, topN, base);
+ }
+ // self 鏍囪涓庤姹傜敤鎴风浉鍏筹紝缂撳瓨涓嶅瓨鍌紝杩斿洖鏃舵寜褰撳墠鐢ㄦ埛鍗虫椂鎵撴爣
+ for (PlanetRankItemVo vo : base) {
+ vo.setSelf(vo.getUserId() != null && vo.getUserId().equals(currentUserId));
+ }
+ return base;
+ }
+
+ /**
+ * 浠庢暟鎹簱鏋勫缓鍩虹姒滃崟锛氬厛鎵归噺鍙栧欓夊埜璁板綍锛屽啀涓娆℃ф壒閲忔煡 BUFF锛屽唴瀛樼粍瑁咃紝閬垮厤寰幆鏌ュ簱銆
+ */
+ private List buildRankFromDb(String regionId, int topN) {
+ // 澶氬彇涓浜涗互渚胯繃婊ら殣韬敤鎴峰悗浠嶈兘鍑戞弧topN
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ if (StringUtils.isNotEmpty(regionId)) {
+ qw.eq(PlanetTicket::getRegionId, regionId);
+ }
+ qw.gt(PlanetTicket::getTicketCount, 0)
+ .orderByDesc(PlanetTicket::getTicketCount)
+ .orderByDesc(PlanetTicket::getTotalTicket)
+ .last("limit " + (topN + 20));
+ final List tickets = ticketMapper.selectList(qw);
+ if (tickets.isEmpty()) {
+ return new ArrayList<>();
+ }
+ // 鎵归噺鏌ヨ鍊欓夌敤鎴风殑鐢熸晥 BUFF 绫诲瀷锛屼竴娆℃煡搴
+ final Set userIds = tickets.stream().map(PlanetTicket::getUserId).collect(Collectors.toCollection(LinkedHashSet::new));
+ final Map> buffMap = buffService.activeBuffTypes(userIds);
+
+ final List result = new ArrayList<>();
+ int rankNo = 0;
+ for (PlanetTicket t : tickets) {
+ final Set buffs = buffMap.get(t.getUserId());
+ // 闅愯韩BUFF鐢ㄦ埛涓嶅嚭鐜板湪姒滃崟
+ if (buffs != null && buffs.contains(PlanetConstant.BUFF_STEALTH)) {
+ continue;
+ }
+ rankNo++;
+ if (rankNo > topN) {
+ break;
+ }
+ final PlanetRankItemVo vo = new PlanetRankItemVo();
+ vo.setRankNo(rankNo);
+ vo.setUserId(t.getUserId());
+ vo.setNickname(t.getNickname());
+ vo.setAvatar(t.getAvatar());
+ vo.setCollege(t.getCollege());
+ vo.setTicketCount(t.getTicketCount());
+ vo.setDangerLevel(PlanetConstant.dangerLevel(t.getTicketCount()));
+ vo.setDangerLevelName(PlanetConstant.dangerLevelName(t.getTicketCount()));
+ vo.setRankKeepDays(t.getRankKeepDays() == null ? 0 : t.getRankKeepDays());
+ vo.setBountyTickets(PlanetConstant.bountyByRank(rankNo));
+ vo.setShielded(buffs != null && buffs.contains(PlanetConstant.BUFF_SHIELD));
+ vo.setSelf(false);
+ result.add(vo);
+ }
+ return result;
+ }
+
+ private List readCache(String regionId, int topN) {
+ try {
+ final String json = redisTemplateHelper.get(cacheKey(regionId, topN));
+ if (StringUtils.isNotEmpty(json)) {
+ return JSON.parseArray(json, PlanetRankItemVo.class);
+ }
+ } catch (Exception e) {
+ log.warn("[鐧藉珫鏄熺悆] 璇诲彇閫氱級姒滅紦瀛樺け璐 {}", e.getMessage());
+ }
+ return null;
+ }
+
+ private void writeCache(String regionId, int topN, List list) {
+ try {
+ redisTemplateHelper.set(cacheKey(regionId, topN), JSON.toJSONString(list), RANK_CACHE_SECONDS, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ log.warn("[鐧藉珫鏄熺悆] 鍐欏叆閫氱級姒滅紦瀛樺け璐 {}", e.getMessage());
+ }
+ }
+
+ private String cacheKey(String regionId, int topN) {
+ return RANK_CACHE_PREFIX + (StringUtils.isEmpty(regionId) ? "all" : regionId) + ":" + topN;
+ }
+
+ @Override
+ public PlanetTicket getRankTarget(String regionId, String userId) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetTicket::getUserId, userId);
+ if (StringUtils.isNotEmpty(regionId)) {
+ qw.eq(PlanetTicket::getRegionId, regionId);
+ }
+ qw.last("limit 1");
+ return ticketMapper.selectOne(qw);
+ }
+
+ @Override
+ public int userRankNo(String regionId, String userId) {
+ final PlanetTicket me = getRankTarget(regionId, userId);
+ if (me == null || me.getTicketCount() == null || me.getTicketCount() <= 0) {
+ return 0;
+ }
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ if (StringUtils.isNotEmpty(regionId)) {
+ qw.eq(PlanetTicket::getRegionId, regionId);
+ }
+ qw.gt(PlanetTicket::getTicketCount, me.getTicketCount());
+ return ticketMapper.selectCount(qw).intValue() + 1;
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetServiceImpl.java
new file mode 100644
index 00000000..2b304553
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetServiceImpl.java
@@ -0,0 +1,104 @@
+package cc.hiver.mall.planet.service.impl;
+
+import cc.hiver.mall.planet.constant.PlanetConstant;
+import cc.hiver.mall.planet.entity.PlanetBuffRecord;
+import cc.hiver.mall.planet.entity.PlanetPool;
+import cc.hiver.mall.planet.entity.PlanetTicket;
+import cc.hiver.mall.planet.pojo.PlanetBuffVo;
+import cc.hiver.mall.planet.pojo.PlanetHomeVo;
+import cc.hiver.mall.planet.pojo.PlanetQuery;
+import cc.hiver.mall.planet.service.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 鐧藉珫鏄熺悆-棣栭〉鑱氬悎鏈嶅姟瀹炵幇
+ *
+ * @author hiver
+ */
+@Service
+public class PlanetServiceImpl implements PlanetService {
+
+ @Autowired
+ private PlanetTicketService ticketService;
+
+ @Autowired
+ private PlanetRankService rankService;
+
+ @Autowired
+ private PlanetTaskService taskService;
+
+ @Autowired
+ private PlanetBuffService buffService;
+
+ @Autowired
+ private PlanetBoxService boxService;
+
+ @Autowired
+ private PlanetHuntService huntService;
+
+ @Autowired
+ private PlanetDrawService drawService;
+
+ @Autowired
+ private PlanetNewsService newsService;
+
+ @Override
+ public PlanetHomeVo home(PlanetQuery query) {
+ final String userId = query.getUserId();
+ final String regionId = query.getRegionId();
+ final PlanetHomeVo vo = new PlanetHomeVo();
+
+ // 鍒濆鍖/琛ュ叏鐢ㄦ埛鍒歌褰
+ final PlanetTicket ticket = ticketService.getOrCreate(userId, regionId,
+ query.getNickname(), query.getAvatar(), query.getCollege());
+
+ // 瀹囧畽Header锛氬綋鍓嶅姹
+ final PlanetPool pool = drawService.getCurrentPool(regionId);
+ vo.setPoolAmount(pool.getPoolAmount() == null ? BigDecimal.ZERO : pool.getPoolAmount());
+ vo.setDrawTime(pool.getDrawTime());
+ vo.setPeriodNo(pool.getPeriodNo());
+ if (pool.getDrawTime() != null) {
+ vo.setCountdownMillis(Math.max(0, pool.getDrawTime().getTime() - new Date().getTime()));
+ } else {
+ vo.setCountdownMillis(0L);
+ }
+ vo.setJoinCount(pool.getJoinCount() == null ? 0 : pool.getJoinCount());
+
+ // 鎴戠殑鏄熺悆涓績
+ vo.setMyTicketCount(ticket.getTicketCount() == null ? 0 : ticket.getTicketCount());
+ vo.setNickname(ticket.getNickname());
+ vo.setAvatar(ticket.getAvatar());
+ vo.setConsecutiveSignDays(ticket.getConsecutiveSignDays() == null ? 0 : ticket.getConsecutiveSignDays());
+ vo.setSignedToday(taskService.signedToday(userId, regionId));
+ vo.setBoxAvailable(boxService.boxAvailable(userId, regionId));
+ vo.setRemainHunt(huntService.remainHunt(userId));
+ vo.setLevel(PlanetConstant.dangerLevel(vo.getMyTicketCount()));
+ vo.setMyRankNo(rankService.userRankNo(regionId, userId));
+
+ // 鎴戠敓鏁堢殑BUFF
+ final List myBuffs = new ArrayList<>();
+ for (PlanetBuffRecord r : buffService.activeBuffs(userId)) {
+ final PlanetBuffVo bv = new PlanetBuffVo();
+ bv.setId(r.getBuffId());
+ bv.setType(r.getType());
+ bv.setEffectValue(r.getEffectValue());
+ bv.setActive(true);
+ bv.setActiveEndTime(r.getEndTime());
+ myBuffs.add(bv);
+ }
+ vo.setMyBuffs(myBuffs);
+
+ // 鍚勬澘鍧
+ vo.setTasks(taskService.listTasks(userId, regionId));
+ vo.setRankList(rankService.rankTop(regionId, 10, userId));
+ vo.setBuffShop(buffService.shopList(userId, regionId));
+ vo.setNewsList(newsService.latest(regionId, 20));
+ return vo;
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetTaskServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetTaskServiceImpl.java
new file mode 100644
index 00000000..c9280cad
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetTaskServiceImpl.java
@@ -0,0 +1,199 @@
+package cc.hiver.mall.planet.service.impl;
+
+import cc.hiver.core.common.exception.HiverException;
+import cc.hiver.mall.planet.constant.PlanetConstant;
+import cc.hiver.mall.planet.entity.PlanetTask;
+import cc.hiver.mall.planet.entity.PlanetTicket;
+import cc.hiver.mall.planet.entity.PlanetTicketLog;
+import cc.hiver.mall.planet.mapper.PlanetTaskMapper;
+import cc.hiver.mall.planet.mapper.PlanetTicketLogMapper;
+import cc.hiver.mall.planet.mapper.PlanetTicketMapper;
+import cc.hiver.mall.planet.pojo.PlanetTaskVo;
+import cc.hiver.mall.planet.service.PlanetNewsService;
+import cc.hiver.mall.planet.service.PlanetTaskService;
+import cc.hiver.mall.planet.service.PlanetTicketService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * 鐧藉珫鏄熺悆-浠诲姟鏈嶅姟瀹炵幇
+ *
+ * @author hiver
+ */
+@Service
+public class PlanetTaskServiceImpl implements PlanetTaskService {
+
+ @Autowired
+ private PlanetTaskMapper taskMapper;
+
+ @Autowired
+ private PlanetTicketMapper ticketMapper;
+
+ @Autowired
+ private PlanetTicketLogMapper ticketLogMapper;
+
+ @Autowired
+ private PlanetTicketService ticketService;
+
+ @Autowired
+ private PlanetNewsService newsService;
+
+ @Override
+ public List listTasks(String userId, String regionId) {
+ final List tasks = listEnabledTasks(regionId);
+ final List list = new ArrayList<>();
+ for (PlanetTask task : tasks) {
+ final PlanetTaskVo vo = new PlanetTaskVo();
+ vo.setCode(task.getCode());
+ vo.setName(task.getName());
+ vo.setDescription(task.getDescription());
+ vo.setIcon(task.getIcon());
+ vo.setRewardTickets(task.getRewardTickets());
+ vo.setDailyLimit(task.getDailyLimit());
+ final int todayCount = countTodayByType(userId, task.getCode());
+ vo.setTodayCount(todayCount);
+ if (PlanetConstant.TASK_SIGN.equals(task.getCode())) {
+ vo.setActionType("claim");
+ vo.setCanClaim(!signedToday(userId, regionId));
+ } else {
+ vo.setActionType("auto");
+ vo.setCanClaim(false);
+ }
+ list.add(vo);
+ }
+ return list;
+ }
+
+ @Override
+ public int sign(String userId, String regionId) {
+ final PlanetTicket ticket = ticketService.getOrCreate(userId, regionId, null, null, null);
+ final LocalDate today = LocalDate.now();
+ if (ticket.getLastSignDate() != null && ticket.getLastSignDate().isEqual(today)) {
+ throw new HiverException("浠婃棩宸茬鍒");
+ }
+ // 杩炵画绛惧埌澶╂暟
+ int days = 1;
+ if (ticket.getLastSignDate() != null && ticket.getLastSignDate().isEqual(today.minusDays(1))) {
+ days = (ticket.getConsecutiveSignDays() == null ? 0 : ticket.getConsecutiveSignDays()) + 1;
+ }
+ ticket.setConsecutiveSignDays(days);
+ ticket.setLastSignDate(today);
+ ticket.setUpdateTime(new Date());
+ ticketMapper.updateById(ticket);
+
+ final PlanetTask task = getTaskConfig(regionId, PlanetConstant.TASK_SIGN);
+ final int reward = task != null && task.getRewardTickets() != null ? task.getRewardTickets() : 1;
+ return ticketService.addTickets(userId, regionId, reward, PlanetConstant.TICKET_TYPE_SIGN,
+ "sign:" + userId + ":" + today, "姣忔棩绛惧埌");
+ }
+
+ @Override
+ public int claim(String userId, String regionId, String taskCode) {
+ if (PlanetConstant.TASK_SIGN.equals(taskCode)) {
+ return sign(userId, regionId);
+ }
+ throw new HiverException("璇ヤ换鍔¢氳繃瀹屾垚瀵瑰簲琛屼负鑷姩鍙戞斁鏄熺悆鍒");
+ }
+
+ @Override
+ public PlanetTask getTaskConfig(String regionId, String code) {
+ // 鍟嗗湀寮洪殧绂伙細浠呮煡鏈晢鍦堜换鍔¢厤缃紝涓嶅啀鍥為鍏ㄥ眬榛樿
+ if (StringUtils.isEmpty(regionId)) {
+ return null;
+ }
+ PlanetTask t = selectRegionTask(regionId, code);
+ if (t == null) {
+ seedDefaultTasks(regionId);
+ t = selectRegionTask(regionId, code);
+ }
+ return t;
+ }
+
+ private PlanetTask selectRegionTask(String regionId, String code) {
+ final LambdaQueryWrapper rq = new LambdaQueryWrapper<>();
+ rq.eq(PlanetTask::getCode, code).eq(PlanetTask::getEnabled, 1)
+ .eq(PlanetTask::getRegionId, regionId).last("limit 1");
+ return taskMapper.selectOne(rq);
+ }
+
+ @Override
+ public boolean signedToday(String userId, String regionId) {
+ final PlanetTicket ticket = ticketService.getByUser(userId, regionId);
+ return ticket != null && ticket.getLastSignDate() != null && ticket.getLastSignDate().isEqual(LocalDate.now());
+ }
+
+ private List listEnabledTasks(String regionId) {
+ // 鍟嗗湀寮洪殧绂伙細浠呰繑鍥炴湰鍟嗗湀浠诲姟锛岄娆¤闂嚜鍔ㄥ垵濮嬪寲榛樿浠诲姟
+ if (StringUtils.isEmpty(regionId)) {
+ return new ArrayList<>();
+ }
+ List list = selectRegionTasks(regionId);
+ if (list.isEmpty()) {
+ seedDefaultTasks(regionId);
+ list = selectRegionTasks(regionId);
+ }
+ return list;
+ }
+
+ private List selectRegionTasks(String regionId) {
+ final LambdaQueryWrapper rq = new LambdaQueryWrapper<>();
+ rq.eq(PlanetTask::getRegionId, regionId).eq(PlanetTask::getEnabled, 1)
+ .orderByAsc(PlanetTask::getSort);
+ final List list = taskMapper.selectList(rq);
+ return list == null ? new ArrayList<>() : list;
+ }
+
+ /**
+ * 涓烘寚瀹氬晢鍦堝垵濮嬪寲榛樿浠诲姟閰嶇疆(骞傜瓑锛氭寜 code 瀛樺湪鍒欒烦杩)銆
+ */
+ private void seedDefaultTasks(String regionId) {
+ if (StringUtils.isEmpty(regionId)) {
+ return;
+ }
+ seedTask(regionId, PlanetConstant.TASK_WAIMAI, "瀹屾垚澶栧崠璁㈠崟", "姣忓畬鎴愪竴绗斿鍗栬鍗 +1 鏄熺悆鍒", 1, 0, 1);
+ seedTask(regionId, PlanetConstant.TASK_GROUP, "瀹屾垚鍥㈣喘璁㈠崟", "姣忓畬鎴愪竴绗斿洟璐鍗 +1 鏄熺悆鍒", 1, 0, 2);
+ seedTask(regionId, PlanetConstant.TASK_INVITE, "閭璇峰ソ鍙嬫敞鍐", "姣忛個璇蜂竴浣嶅ソ鍙嬫敞鍐 +3 鏄熺悆鍒", 3, 0, 3);
+ seedTask(regionId, PlanetConstant.TASK_SIGN, "姣忔棩绛惧埌", "姣忔棩绛惧埌棰嗗彇 +1 鏄熺悆鍒", 1, 1, 4);
+ }
+
+ private void seedTask(String regionId, String code, String name, String desc, int reward, int dailyLimit, int sort) {
+ final LambdaQueryWrapper exist = new LambdaQueryWrapper<>();
+ exist.eq(PlanetTask::getRegionId, regionId).eq(PlanetTask::getCode, code);
+ if (taskMapper.selectCount(exist) > 0) {
+ return;
+ }
+ final PlanetTask task = new PlanetTask();
+ task.setId(UUID.randomUUID().toString().replace("-", ""));
+ task.setRegionId(regionId);
+ task.setCode(code);
+ task.setName(name);
+ task.setDescription(desc);
+ task.setRewardTickets(reward);
+ task.setDailyLimit(dailyLimit);
+ task.setSort(sort);
+ task.setEnabled(1);
+ task.setCreateTime(new Date());
+ taskMapper.insert(task);
+ }
+
+ private int countTodayByType(String userId, String type) {
+ final LocalDateTime start = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
+ final Date startDate = Date.from(start.atZone(ZoneId.systemDefault()).toInstant());
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetTicketLog::getUserId, userId)
+ .eq(PlanetTicketLog::getType, type)
+ .ge(PlanetTicketLog::getCreateTime, startDate);
+ return ticketLogMapper.selectCount(qw).intValue();
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetTicketServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetTicketServiceImpl.java
new file mode 100644
index 00000000..28751d4d
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetTicketServiceImpl.java
@@ -0,0 +1,229 @@
+package cc.hiver.mall.planet.service.impl;
+
+import cc.hiver.core.common.exception.HiverException;
+import cc.hiver.core.entity.User;
+import cc.hiver.core.service.UserService;
+import cc.hiver.mall.planet.constant.PlanetConstant;
+import cc.hiver.mall.planet.entity.PlanetBuffRecord;
+import cc.hiver.mall.planet.entity.PlanetTicket;
+import cc.hiver.mall.planet.entity.PlanetTicketLog;
+import cc.hiver.mall.planet.mapper.PlanetBuffRecordMapper;
+import cc.hiver.mall.planet.mapper.PlanetTicketLogMapper;
+import cc.hiver.mall.planet.mapper.PlanetTicketMapper;
+import cc.hiver.mall.planet.service.PlanetTicketService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * 鐧藉珫鏄熺悆-鏄熺悆鍒告湇鍔″疄鐜
+ *
+ * @author hiver
+ */
+@Slf4j
+@Service
+public class PlanetTicketServiceImpl implements PlanetTicketService {
+
+ @Autowired
+ private PlanetTicketMapper ticketMapper;
+
+ @Autowired
+ private PlanetTicketLogMapper ticketLogMapper;
+
+ @Autowired
+ private PlanetBuffRecordMapper buffRecordMapper;
+
+ @Autowired
+ private UserService userService;
+
+ @Override
+ public PlanetTicket getOrCreate(String userId, String regionId, String nickname, String avatar, String college) {
+ if (StringUtils.isEmpty(userId)) {
+ throw new HiverException("鐢ㄦ埛id涓嶈兘涓虹┖");
+ }
+ PlanetTicket ticket = getByUser(userId, regionId);
+ if (ticket == null) {
+ ticket = new PlanetTicket();
+ ticket.setId(UUID.randomUUID().toString().replace("-", ""));
+ ticket.setUserId(userId);
+ ticket.setRegionId(regionId);
+ ticket.setTicketCount(0);
+ ticket.setTotalTicket(0);
+ ticket.setConsecutiveSignDays(0);
+ ticket.setRankKeepDays(0);
+ ticket.setLastRankNo(0);
+ ticket.setHuntCountToday(0);
+ ticket.setHuntedSuccessToday(0);
+ fillUserInfo(ticket, nickname, avatar, college);
+ ticket.setCreateTime(new Date());
+ ticket.setUpdateTime(new Date());
+ ticketMapper.insert(ticket);
+ } else {
+ // 琛ュ叏鍐椾綑淇℃伅
+ boolean needUpdate = false;
+ if (StringUtils.isEmpty(ticket.getNickname()) && StringUtils.isNotEmpty(nickname)) {
+ ticket.setNickname(nickname);
+ needUpdate = true;
+ }
+ if (StringUtils.isEmpty(ticket.getAvatar()) && StringUtils.isNotEmpty(avatar)) {
+ ticket.setAvatar(avatar);
+ needUpdate = true;
+ }
+ if (StringUtils.isEmpty(ticket.getCollege()) && StringUtils.isNotEmpty(college)) {
+ ticket.setCollege(college);
+ needUpdate = true;
+ }
+ if (needUpdate) {
+ ticket.setUpdateTime(new Date());
+ ticketMapper.updateById(ticket);
+ }
+ }
+ return ticket;
+ }
+
+ private void fillUserInfo(PlanetTicket ticket, String nickname, String avatar, String college) {
+ ticket.setNickname(nickname);
+ ticket.setAvatar(avatar);
+ ticket.setCollege(college);
+ if (StringUtils.isEmpty(nickname) || StringUtils.isEmpty(avatar)) {
+ try {
+ final User user = userService.findById(ticket.getUserId());
+ if (user != null) {
+ if (StringUtils.isEmpty(ticket.getNickname())) {
+ ticket.setNickname(user.getNickname());
+ }
+ if (StringUtils.isEmpty(ticket.getAvatar())) {
+ ticket.setAvatar(user.getAvatar());
+ }
+ }
+ } catch (Exception e) {
+ log.warn("[鐧藉珫鏄熺悆] 鑾峰彇鐢ㄦ埛淇℃伅澶辫触 userId={}, {}", ticket.getUserId(), e.getMessage());
+ }
+ }
+ }
+
+ @Override
+ public PlanetTicket getByUser(String userId, String regionId) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetTicket::getUserId, userId);
+ if (StringUtils.isNotEmpty(regionId)) {
+ qw.eq(PlanetTicket::getRegionId, regionId);
+ }
+ qw.last("limit 1");
+ return ticketMapper.selectOne(qw);
+ }
+
+ @Override
+ public int addTickets(String userId, String regionId, int baseCount, String type, String sourceId, String remark) {
+ if (baseCount <= 0) {
+ return 0;
+ }
+ // 骞傜瓑锛氬悓涓鏉ユ簮涓氬姟id鍙彂鏀句竴娆
+ if (StringUtils.isNotEmpty(sourceId)) {
+ final LambdaQueryWrapper exist = new LambdaQueryWrapper<>();
+ exist.eq(PlanetTicketLog::getType, type)
+ .eq(PlanetTicketLog::getSourceId, sourceId);
+ if (ticketLogMapper.selectCount(exist) > 0) {
+ log.info("[鐧藉珫鏄熺悆] 閲嶅鍙戝埜宸叉嫤鎴 type={}, sourceId={}", type, sourceId);
+ return 0;
+ }
+ }
+ final PlanetTicket ticket = getOrCreate(userId, regionId, null, null, null);
+ // 鍙屽岯UFF鍔犳垚
+ int finalCount = baseCount;
+ final BigDecimal multiplier = getActiveDoubleMultiplier(userId);
+ if (multiplier != null && multiplier.compareTo(BigDecimal.ONE) > 0) {
+ finalCount = multiplier.multiply(BigDecimal.valueOf(baseCount)).intValue();
+ }
+ ticket.setTicketCount(ticket.getTicketCount() + finalCount);
+ ticket.setTotalTicket(ticket.getTotalTicket() + finalCount);
+ ticket.setUpdateTime(new Date());
+ ticketMapper.updateById(ticket);
+ saveLog(userId, regionId, finalCount, ticket.getTicketCount(), type, sourceId, remark);
+ return finalCount;
+ }
+
+ @Override
+ public int deductTickets(String userId, String regionId, int count, String type, String sourceId, String remark) {
+ if (count <= 0) {
+ throw new HiverException("鎵e噺鏁伴噺蹇呴』澶т簬0");
+ }
+ final PlanetTicket ticket = getOrCreate(userId, regionId, null, null, null);
+ if (ticket.getTicketCount() < count) {
+ throw new HiverException("鏄熺悆鍒镐笉瓒");
+ }
+ ticket.setTicketCount(ticket.getTicketCount() - count);
+ ticket.setUpdateTime(new Date());
+ ticketMapper.updateById(ticket);
+ saveLog(userId, regionId, -count, ticket.getTicketCount(), type, sourceId, remark);
+ return ticket.getTicketCount();
+ }
+
+ @Override
+ public int getRankNo(String regionId, String userId) {
+ final PlanetTicket me = getByUser(userId, regionId);
+ if (me == null || me.getTicketCount() == null || me.getTicketCount() <= 0) {
+ return 0;
+ }
+ // 姣旀垜鍒稿鐨勪汉鏁 + 1 鍗充负鎺掑悕
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ if (StringUtils.isNotEmpty(regionId)) {
+ qw.eq(PlanetTicket::getRegionId, regionId);
+ }
+ qw.gt(PlanetTicket::getTicketCount, me.getTicketCount());
+ final Long greater = ticketMapper.selectCount(qw);
+ return greater.intValue() + 1;
+ }
+
+ @Override
+ public Page pageLog(String userId, String regionId, Integer pageNumber, Integer pageSize) {
+ final int num = pageNumber == null || pageNumber <= 0 ? 1 : pageNumber;
+ final int size = pageSize == null || pageSize <= 0 ? 10 : Math.min(pageSize, 50);
+ final Page page = new Page<>(num, size);
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetTicketLog::getUserId, userId);
+ if (StringUtils.isNotEmpty(regionId)) {
+ qw.eq(PlanetTicketLog::getRegionId, regionId);
+ }
+ qw.orderByDesc(PlanetTicketLog::getCreateTime);
+ return ticketLogMapper.selectPage(page, qw);
+ }
+
+ private BigDecimal getActiveDoubleMultiplier(String userId) {
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.eq(PlanetBuffRecord::getUserId, userId)
+ .eq(PlanetBuffRecord::getType, PlanetConstant.BUFF_DOUBLE)
+ .eq(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_ACTIVE)
+ .gt(PlanetBuffRecord::getEndTime, new Date())
+ .orderByDesc(PlanetBuffRecord::getEffectValue)
+ .last("limit 1");
+ final List list = buffRecordMapper.selectList(qw);
+ if (list != null && !list.isEmpty()) {
+ final BigDecimal v = list.get(0).getEffectValue();
+ return v == null ? BigDecimal.valueOf(2) : v;
+ }
+ return BigDecimal.ONE;
+ }
+
+ private void saveLog(String userId, String regionId, int change, int balance, String type, String sourceId, String remark) {
+ final PlanetTicketLog logEntity = new PlanetTicketLog();
+ logEntity.setId(UUID.randomUUID().toString().replace("-", ""));
+ logEntity.setUserId(userId);
+ logEntity.setRegionId(regionId);
+ logEntity.setChangeCount(change);
+ logEntity.setBalance(balance);
+ logEntity.setType(type);
+ logEntity.setSourceId(sourceId);
+ logEntity.setRemark(remark);
+ logEntity.setCreateTime(new Date());
+ ticketLogMapper.insert(logEntity);
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/PlanetDailyResetTask.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/PlanetDailyResetTask.java
new file mode 100644
index 00000000..a7998a15
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/PlanetDailyResetTask.java
@@ -0,0 +1,86 @@
+package cc.hiver.mall.quartz;
+
+import cc.hiver.mall.planet.constant.PlanetConstant;
+import cc.hiver.mall.planet.entity.PlanetBuffRecord;
+import cc.hiver.mall.planet.entity.PlanetTicket;
+import cc.hiver.mall.planet.mapper.PlanetBuffRecordMapper;
+import cc.hiver.mall.planet.mapper.PlanetTicketMapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 鐧藉珫鏄熺悆-姣忔棩閲嶇疆瀹氭椂浠诲姟
+ *
+ * 姣忔棩 00:05 鎵ц锛
+ * 1. 杩囨湡BUFF缃负澶辨晥鐘舵
+ * 2. 閲嶇疆浠婃棩杩芥崟/琚拷璁℃暟(灞曠ず鐢)
+ * 3. 鏇存柊杩炵画闇告澶╂暟
+ *
+ * @author hiver
+ */
+@Slf4j
+@Component
+public class PlanetDailyResetTask {
+
+ @Autowired
+ private PlanetTicketMapper ticketMapper;
+
+ @Autowired
+ private PlanetBuffRecordMapper buffRecordMapper;
+
+ @Scheduled(cron = "0 5 0 * * ?")
+ public void dailyReset() {
+ try {
+ // 1. 杩囨湡BUFF
+ final LambdaUpdateWrapper bw = new LambdaUpdateWrapper<>();
+ bw.eq(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_ACTIVE)
+ .lt(PlanetBuffRecord::getEndTime, new Date())
+ .set(PlanetBuffRecord::getStatus, PlanetConstant.BUFF_STATUS_EXPIRED);
+ buffRecordMapper.update(null, bw);
+
+ // 2. 閲嶇疆姣忔棩璁℃暟(灞曠ず瀛楁)
+ final LambdaUpdateWrapper tw = new LambdaUpdateWrapper<>();
+ tw.set(PlanetTicket::getHuntCountToday, 0)
+ .set(PlanetTicket::getHuntedSuccessToday, 0);
+ ticketMapper.update(null, tw);
+
+ // 3. 鏇存柊杩炵画闇告澶╂暟锛氫粛鍦═OP10鐨勭敤鎴 keepDays+1锛屽惁鍒欐竻闆
+ updateRankKeepDays();
+ log.info("[鐧藉珫鏄熺悆] 姣忔棩閲嶇疆浠诲姟瀹屾垚");
+ } catch (Exception e) {
+ log.error("[鐧藉珫鏄熺悆] 姣忔棩閲嶇疆浠诲姟寮傚父: {}", e.getMessage(), e);
+ }
+ }
+
+ private void updateRankKeepDays() {
+ // 鍙栨墍鏈夋寔鍒哥敤鎴凤紝鎸夊尯鍩熷垎鍒绠楀墠10
+ final LambdaQueryWrapper qw = new LambdaQueryWrapper<>();
+ qw.gt(PlanetTicket::getTicketCount, 0).orderByDesc(PlanetTicket::getTicketCount);
+ final List all = ticketMapper.selectList(qw);
+ // 绠鍖栧鐞嗭細鍏ㄥ眬/鍚勫尯鍩熺粺涓鎸夊埜鏁版帓搴忓悗锛岃褰曞叾鍦ㄦ湰鍖哄煙鐨勫悕娆
+ // 杩欓噷浠呭姣忎釜鐢ㄦ埛鍩轰簬鍏跺綋鍓嶆槸鍚﹁繘鍏ユ湰鍖哄煙TOP10鏇存柊keepDays
+ final java.util.Map regionRankCounter = new java.util.HashMap<>();
+ for (PlanetTicket t : all) {
+ final String region = t.getRegionId() == null ? "" : t.getRegionId();
+ final int rank = regionRankCounter.merge(region, 1, Integer::sum);
+ int keep = t.getRankKeepDays() == null ? 0 : t.getRankKeepDays();
+ if (rank <= 10) {
+ keep = keep + 1;
+ t.setLastRankNo(rank);
+ } else {
+ keep = 0;
+ t.setLastRankNo(0);
+ }
+ t.setRankKeepDays(keep);
+ t.setUpdateTime(new Date());
+ ticketMapper.updateById(t);
+ }
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/PlanetDrawTask.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/PlanetDrawTask.java
new file mode 100644
index 00000000..5775a0fc
--- /dev/null
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/quartz/PlanetDrawTask.java
@@ -0,0 +1,33 @@
+package cc.hiver.mall.quartz;
+
+import cc.hiver.mall.planet.service.PlanetDrawService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+/**
+ * 鐧藉珫鏄熺悆-鍒扮偣寮濂栧畾鏃朵换鍔
+ * 姣忓垎閽熸壂鎻忓埌杈惧紑濂栨椂闂翠笖鏈紑濂栫殑濂栨睜骞舵墽琛屽姞鏉冨紑濂
+ *
+ * @author hiver
+ */
+@Slf4j
+@Component
+public class PlanetDrawTask {
+
+ @Autowired
+ private PlanetDrawService drawService;
+
+ @Scheduled(cron = "0 * * * * ?")
+ public void drawDuePools() {
+ try {
+ final int count = drawService.drawDuePools();
+ if (count > 0) {
+ log.info("[鐧藉珫鏄熺悆] 鏈疆寮濂栧姹犳暟閲={}", count);
+ }
+ } catch (Exception e) {
+ log.error("[鐧藉珫鏄熺悆] 寮濂栦换鍔″紓甯: {}", e.getMessage(), e);
+ }
+ }
+}
diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java
index 96ec442c..4ae940ec 100644
--- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java
+++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java
@@ -184,6 +184,9 @@ public class MallOrderServiceImpl extends ServiceImpl