zc пре 2 месеци
родитељ
комит
9768f287eb
15 измењених фајлова са 930 додато и 1 уклоњено
  1. 75 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivityAchievement.java
  2. 1 1
      alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivitySignup.java
  3. 46 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementVo.java
  4. 30 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupCheckVo.java
  5. 14 0
      alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityVO.java
  6. 15 0
      alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivityAchievementMapper.java
  7. 36 0
      alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivitySignupMapper.java
  8. 36 0
      alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivitySignupMapper.xml
  9. 160 0
      alien-store/src/main/java/shop/alien/store/controller/StoreOperationalActivityController.java
  10. 31 0
      alien-store/src/main/java/shop/alien/store/dto/StoreOperationalActivityAchievementDto.java
  11. 28 0
      alien-store/src/main/java/shop/alien/store/dto/StoreOperationalActivitySignupDto.java
  12. 43 0
      alien-store/src/main/java/shop/alien/store/service/StoreOperationalActivityAchievementService.java
  13. 39 0
      alien-store/src/main/java/shop/alien/store/service/StoreOperationalActivityService.java
  14. 125 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityAchievementServiceImpl.java
  15. 251 0
      alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityServiceImpl.java

+ 75 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivityAchievement.java

@@ -0,0 +1,75 @@
+package shop.alien.entity.storePlatform;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 运营活动成果表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("store_operational_activity_achievement")
+@ApiModel(value = "StoreOperationalActivityAchievement对象", description = "运营活动成果表")
+public class StoreOperationalActivityAchievement {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "活动ID")
+    @TableField("activity_id")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "用户ID")
+    @TableField("user_id")
+    private Integer userId;
+
+    @ApiModelProperty(value = "报名ID")
+    @TableField("signup_id")
+    private Integer signupId;
+
+    @ApiModelProperty(value = "成果描述")
+    @TableField("achievement_desc")
+    private String achievementDesc;
+
+    @ApiModelProperty(value = "图片/视频URL(逗号分隔)")
+    @TableField("media_urls")
+    private String mediaUrls;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+}

+ 1 - 1
alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivitySignup.java

@@ -50,7 +50,7 @@ public class StoreOperationalActivitySignup {
     @TableField("phone")
     private String phone;
 
-    @ApiModelProperty(value = "报名状态:0-已报名,1-已取消")
+    @ApiModelProperty(value = "报名状态:0-待审核,1-拒绝,2-通过")
     @TableField("status")
     private Integer status;
 

+ 46 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityAchievementVo.java

@@ -0,0 +1,46 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+import java.util.Date;
+
+/**
+ * 运营活动成果返回对象
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivityAchievementVo", description = "运营活动成果返回对象")
+public class StoreOperationalActivityAchievementVo {
+
+    @ApiModelProperty(value = "主键")
+    private Integer id;
+
+    @ApiModelProperty(value = "活动ID")
+    private Integer activityId;
+
+    @ApiModelProperty(value = "用户ID")
+    private Integer userId;
+
+    @ApiModelProperty(value = "报名ID")
+    private Integer signupId;
+
+    @ApiModelProperty(value = "成果描述")
+    private String achievementDesc;
+
+    @ApiModelProperty(value = "图片/视频URL(逗号分隔)")
+    private String mediaUrls;
+
+    @ApiModelProperty(value = "图片/视频URL列表")
+    private List<String> mediaUrlList;
+
+    @ApiModelProperty(value = "创建时间")
+    private Date createdTime;
+}

+ 30 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivitySignupCheckVo.java

@@ -0,0 +1,30 @@
+package shop.alien.entity.storePlatform.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 运营活动报名校验结果
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@ApiModel(value = "StoreOperationalActivitySignupCheckVo", description = "运营活动报名校验结果")
+public class StoreOperationalActivitySignupCheckVo {
+
+    @ApiModelProperty(value = "是否已满")
+    private boolean full;
+
+    @ApiModelProperty(value = "是否已成功报名")
+    private boolean signedUp;
+
+    @ApiModelProperty(value = "当前报名人数")
+    private Integer currentSignupCount;
+
+    @ApiModelProperty(value = "活动限制人数")
+    private Integer activityLimitPeople;
+}

+ 14 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityVO.java

@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
 import shop.alien.entity.store.StoreImg;
 import shop.alien.entity.storePlatform.StoreOperationalActivity;
 
@@ -15,6 +16,7 @@ import shop.alien.entity.storePlatform.StoreOperationalActivity;
  * @since 2025-11-26
  */
 @Data
+@EqualsAndHashCode(callSuper = true)
 @JsonInclude
 @ApiModel(value = "StoreOperationalActivityVO", description = "运营活动返回对象")
 public class StoreOperationalActivityVO extends StoreOperationalActivity {
@@ -43,6 +45,18 @@ public class StoreOperationalActivityVO extends StoreOperationalActivity {
     @ApiModelProperty(value = "剩余优惠券数量")
     private Integer remainingCouponQuantity;
 
+    @ApiModelProperty(value = "当前报名人数")
+    private Integer currentSignupCount;
+
+    @ApiModelProperty(value = "当前通过人数")
+    private Integer currentApprovedCount;
+
+    @ApiModelProperty(value = "是否已报名")
+    private Boolean signedUp;
+
+    @ApiModelProperty(value = "报名是否截止")
+    private Boolean signupExpired;
+
     @ApiModelProperty(value = "活动标题图片")
     private StoreImg activityTitleImg;
 

+ 15 - 0
alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivityAchievementMapper.java

@@ -0,0 +1,15 @@
+package shop.alien.mapper.storePlantform;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.storePlatform.StoreOperationalActivityAchievement;
+
+/**
+ * 运营活动成果 Mapper 接口
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Mapper
+public interface StoreOperationalActivityAchievementMapper extends BaseMapper<StoreOperationalActivityAchievement> {
+}

+ 36 - 0
alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivitySignupMapper.java

@@ -23,4 +23,40 @@ public interface StoreOperationalActivitySignupMapper extends BaseMapper<StoreOp
      * @return 报名列表
      */
     List<StoreOperationalActivitySignup> selectByActivityId(@Param("activityId") Integer activityId);
+
+    /**
+     * 统计活动当前报名人数(待审核+通过,未删除)。
+     *
+     * @param activityId 活动ID
+     * @return 报名人数
+     */
+    Integer countSignupByActivityId(@Param("activityId") Integer activityId);
+
+    /**
+     * 统计活动当前通过人数(通过状态,未删除)。
+     *
+     * @param activityId 活动ID
+     * @return 通过人数
+     */
+    Integer countApprovedByActivityId(@Param("activityId") Integer activityId);
+
+    /**
+     * 统计用户是否已通过报名。
+     *
+     * @param activityId 活动ID
+     * @param userId     用户ID
+     * @return 报名数量
+     */
+    Integer countApprovedByActivityAndUser(@Param("activityId") Integer activityId,
+                                           @Param("userId") Integer userId);
+
+    /**
+     * 统计用户是否已报名(待审核/通过)。
+     *
+     * @param activityId 活动ID
+     * @param userId     用户ID
+     * @return 报名数量
+     */
+    Integer countSignedUpByActivityAndUser(@Param("activityId") Integer activityId,
+                                           @Param("userId") Integer userId);
 }

+ 36 - 0
alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivitySignupMapper.xml

@@ -33,4 +33,40 @@
           AND delete_flag = 0
         ORDER BY signup_time DESC
     </select>
+
+    <select id="countSignupByActivityId" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND delete_flag = 0
+          AND status IN (0, 2)
+    </select>
+
+    <select id="countApprovedByActivityId" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND delete_flag = 0
+          AND status = 2
+    </select>
+
+    <select id="countApprovedByActivityAndUser" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND user_id = #{userId}
+          AND delete_flag = 0
+          AND status = 2
+        LIMIT 1
+    </select>
+
+    <select id="countSignedUpByActivityAndUser" resultType="java.lang.Integer">
+        SELECT COUNT(1)
+        FROM store_operational_activity_signup
+        WHERE activity_id = #{activityId}
+          AND user_id = #{userId}
+          AND delete_flag = 0
+          AND status IN (0, 2)
+        LIMIT 1
+    </select>
 </mapper>

+ 160 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreOperationalActivityController.java

@@ -0,0 +1,160 @@
+package shop.alien.store.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import io.swagger.annotations.ApiSort;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.RequestParam;
+import shop.alien.entity.result.R;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupCheckVo;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityVO;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementVo;
+import shop.alien.store.dto.StoreOperationalActivitySignupDto;
+import shop.alien.store.dto.StoreOperationalActivityAchievementDto;
+import shop.alien.store.service.StoreOperationalActivityService;
+import shop.alien.store.service.StoreOperationalActivityAchievementService;
+
+/**
+ * 运营活动详情控制器
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Api(tags = {"用户端-运营活动详情"})
+@ApiSort(11)
+@CrossOrigin
+@RestController
+@RequestMapping("/storeOperationalActivity")
+@RequiredArgsConstructor
+public class StoreOperationalActivityController {
+
+    private final StoreOperationalActivityService operationalActivityService;
+    private final StoreOperationalActivityAchievementService achievementService;
+
+    @ApiOperation("查询活动详情")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParam(name = "id", value = "活动ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    @GetMapping("/detail")
+    public R<StoreOperationalActivityVO> getActivityDetail(@RequestParam("id") Integer id) {
+        log.info("StoreOperationalActivityController.getActivityDetail id={}", id);
+        try {
+            StoreOperationalActivityVO result = operationalActivityService.getActivityDetail(id);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.getActivityDetail ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("报名校验")
+    @ApiOperationSupport(order = 2)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "activityId", value = "活动ID", dataTypeClass = Integer.class, paramType = "query", required = true),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    })
+    @GetMapping("/signup/check")
+    public R<StoreOperationalActivitySignupCheckVo> checkSignup(@RequestParam("activityId") Integer activityId,
+                                                                @RequestParam("userId") Integer userId) {
+        log.info("StoreOperationalActivityController.checkSignup activityId={}, userId={}", activityId, userId);
+        try {
+            StoreOperationalActivitySignupCheckVo result = operationalActivityService.checkSignup(activityId, userId);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.checkSignup ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("活动报名")
+    @ApiOperationSupport(order = 3)
+    @PostMapping("/signup")
+    public R<String> signup(@RequestBody StoreOperationalActivitySignupDto dto) {
+        log.info("StoreOperationalActivityController.signup dto={}", dto);
+        try {
+            boolean result = operationalActivityService.signup(dto);
+            return result ? R.success("报名成功") : R.fail("报名失败");
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.signup ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("新增成果")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/achievement/add")
+    public R<String> addAchievement(@RequestBody StoreOperationalActivityAchievementDto dto) {
+        log.info("StoreOperationalActivityController.addAchievement dto={}", dto);
+        try {
+            boolean result = achievementService.addAchievement(dto);
+            return result ? R.success("新增成功") : R.fail("新增失败");
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.addAchievement ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("成果详情")
+    @ApiOperationSupport(order = 5)
+    @ApiImplicitParam(name = "id", value = "成果ID", dataTypeClass = Integer.class, paramType = "query", required = true)
+    @GetMapping("/achievement/detail")
+    public R<StoreOperationalActivityAchievementVo> getAchievementDetail(@RequestParam("id") Integer id) {
+        log.info("StoreOperationalActivityController.getAchievementDetail id={}", id);
+        try {
+            StoreOperationalActivityAchievementVo result = achievementService.getAchievementDetail(id);
+            return R.data(result);
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.getAchievementDetail ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation("成果列表")
+    @ApiOperationSupport(order = 6)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "activityId", value = "活动ID", dataTypeClass = Integer.class, paramType = "query"),
+            @ApiImplicitParam(name = "userId", value = "用户ID", dataTypeClass = Integer.class, paramType = "query"),
+            @ApiImplicitParam(name = "signupId", value = "报名ID", dataTypeClass = Integer.class, paramType = "query"),
+            @ApiImplicitParam(name = "pageNum", value = "当前页", dataTypeClass = Integer.class, paramType = "query"),
+            @ApiImplicitParam(name = "pageSize", value = "每页条数", dataTypeClass = Integer.class, paramType = "query")
+    })
+    @GetMapping("/achievement/list")
+    public R<IPage<StoreOperationalActivityAchievementVo>> listAchievements(
+            @RequestParam(value = "activityId", required = false) Integer activityId,
+            @RequestParam(value = "userId", required = false) Integer userId,
+            @RequestParam(value = "signupId", required = false) Integer signupId,
+            @RequestParam(value = "pageNum", required = false) Integer pageNum,
+            @RequestParam(value = "pageSize", required = false) Integer pageSize) {
+        log.info("StoreOperationalActivityController.listAchievements activityId={}, userId={}, signupId={}, pageNum={}, pageSize={}",
+                activityId, userId, signupId, pageNum, pageSize);
+        try {
+            IPage<StoreOperationalActivityAchievementVo> result =
+                    achievementService.listAchievements(activityId, userId, signupId, pageNum, pageSize);
+            return R.data(result);
+        } catch (Exception e) {
+            log.error("StoreOperationalActivityController.listAchievements ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+}

+ 31 - 0
alien-store/src/main/java/shop/alien/store/dto/StoreOperationalActivityAchievementDto.java

@@ -0,0 +1,31 @@
+package shop.alien.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 运营活动成果新增请求
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreOperationalActivityAchievementDto", description = "运营活动成果新增请求")
+public class StoreOperationalActivityAchievementDto {
+
+    @ApiModelProperty(value = "活动ID", required = true)
+    private Integer activityId;
+
+    @ApiModelProperty(value = "用户ID", required = true)
+    private Integer userId;
+
+    @ApiModelProperty(value = "报名ID", required = true)
+    private Integer signupId;
+
+    @ApiModelProperty(value = "成果描述")
+    private String achievementDesc;
+
+    @ApiModelProperty(value = "图片/视频URL(逗号分隔)")
+    private String mediaUrls;
+}

+ 28 - 0
alien-store/src/main/java/shop/alien/store/dto/StoreOperationalActivitySignupDto.java

@@ -0,0 +1,28 @@
+package shop.alien.store.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 运营活动报名请求
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@ApiModel(value = "StoreOperationalActivitySignupDto", description = "运营活动报名请求")
+public class StoreOperationalActivitySignupDto {
+
+    @ApiModelProperty(value = "活动ID", required = true)
+    private Integer activityId;
+
+    @ApiModelProperty(value = "用户ID", required = true)
+    private Integer userId;
+
+    @ApiModelProperty(value = "报名人姓名")
+    private String userName;
+
+    @ApiModelProperty(value = "手机号")
+    private String phone;
+}

+ 43 - 0
alien-store/src/main/java/shop/alien/store/service/StoreOperationalActivityAchievementService.java

@@ -0,0 +1,43 @@
+package shop.alien.store.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementVo;
+import shop.alien.store.dto.StoreOperationalActivityAchievementDto;
+
+/**
+ * 运营活动成果服务
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StoreOperationalActivityAchievementService {
+
+    /**
+     * 新增成果
+     *
+     * @param dto 请求参数
+     * @return 是否成功
+     */
+    boolean addAchievement(StoreOperationalActivityAchievementDto dto);
+
+    /**
+     * 成果详情
+     *
+     * @param id 主键
+     * @return 详情
+     */
+    StoreOperationalActivityAchievementVo getAchievementDetail(Integer id);
+
+    /**
+     * 成果列表
+     *
+     * @param activityId 活动ID
+     * @param userId 用户ID
+     * @param signupId 报名ID
+     * @param pageNum 当前页
+     * @param pageSize 每页条数
+     * @return 分页列表
+     */
+    IPage<StoreOperationalActivityAchievementVo> listAchievements(Integer activityId, Integer userId, Integer signupId,
+                                                                  Integer pageNum, Integer pageSize);
+}

+ 39 - 0
alien-store/src/main/java/shop/alien/store/service/StoreOperationalActivityService.java

@@ -0,0 +1,39 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityVO;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupCheckVo;
+import shop.alien.store.dto.StoreOperationalActivitySignupDto;
+
+/**
+ * 用户端运营活动服务
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+public interface StoreOperationalActivityService {
+
+    /**
+     * 查询活动详情
+     *
+     * @param id 活动ID
+     * @return 活动详情
+     */
+    StoreOperationalActivityVO getActivityDetail(Integer id);
+
+    /**
+     * 报名校验:是否已满、是否已成功报名。
+     *
+     * @param activityId 活动ID
+     * @param userId     用户ID
+     * @return 校验结果
+     */
+    StoreOperationalActivitySignupCheckVo checkSignup(Integer activityId, Integer userId);
+
+    /**
+     * 活动报名
+     *
+     * @param dto 报名请求
+     * @return 是否成功
+     */
+    boolean signup(StoreOperationalActivitySignupDto dto);
+}

+ 125 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityAchievementServiceImpl.java

@@ -0,0 +1,125 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import shop.alien.entity.storePlatform.StoreOperationalActivityAchievement;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityAchievementVo;
+import shop.alien.mapper.storePlantform.StoreOperationalActivityAchievementMapper;
+import shop.alien.store.dto.StoreOperationalActivityAchievementDto;
+import shop.alien.store.service.StoreOperationalActivityAchievementService;
+
+/**
+ * 运营活动成果服务实现
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class StoreOperationalActivityAchievementServiceImpl implements StoreOperationalActivityAchievementService {
+
+    private final StoreOperationalActivityAchievementMapper achievementMapper;
+
+    @Override
+    public boolean addAchievement(StoreOperationalActivityAchievementDto dto) {
+        if (dto == null) {
+            throw new IllegalArgumentException("参数不能为空");
+        }
+        if (dto.getActivityId() == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+        if (dto.getUserId() == null) {
+            throw new IllegalArgumentException("用户ID不能为空");
+        }
+        if (dto.getSignupId() == null) {
+            throw new IllegalArgumentException("报名ID不能为空");
+        }
+
+        StoreOperationalActivityAchievement achievement = new StoreOperationalActivityAchievement();
+        achievement.setActivityId(dto.getActivityId());
+        achievement.setUserId(dto.getUserId());
+        achievement.setSignupId(dto.getSignupId());
+        achievement.setAchievementDesc(dto.getAchievementDesc());
+        achievement.setMediaUrls(dto.getMediaUrls());
+        achievement.setDeleteFlag(0);
+        achievement.setCreatedUserId(dto.getUserId());
+        return achievementMapper.insert(achievement) > 0;
+    }
+
+    @Override
+    public StoreOperationalActivityAchievementVo getAchievementDetail(Integer id) {
+        if (id == null) {
+            throw new IllegalArgumentException("成果ID不能为空");
+        }
+        StoreOperationalActivityAchievement achievement = achievementMapper.selectById(id);
+        if (achievement == null || achievement.getDeleteFlag() != null && achievement.getDeleteFlag() == 1) {
+            return null;
+        }
+        StoreOperationalActivityAchievementVo vo = new StoreOperationalActivityAchievementVo();
+        BeanUtils.copyProperties(achievement, vo);
+        vo.setMediaUrlList(splitMediaUrls(achievement.getMediaUrls()));
+        return vo;
+    }
+
+    @Override
+    public IPage<StoreOperationalActivityAchievementVo> listAchievements(Integer activityId, Integer userId, Integer signupId,
+                                                                         Integer pageNum, Integer pageSize) {
+        int current = pageNum == null || pageNum <= 0 ? 1 : pageNum;
+        int size = pageSize == null || pageSize <= 0 ? 10 : pageSize;
+        Page<StoreOperationalActivityAchievement> page = new Page<>(current, size);
+
+        LambdaQueryWrapper<StoreOperationalActivityAchievement> wrapper = new LambdaQueryWrapper<>();
+        if (activityId != null) {
+            wrapper.eq(StoreOperationalActivityAchievement::getActivityId, activityId);
+        }
+        if (userId != null) {
+            wrapper.eq(StoreOperationalActivityAchievement::getUserId, userId);
+        }
+        if (signupId != null) {
+            wrapper.eq(StoreOperationalActivityAchievement::getSignupId, signupId);
+        }
+        wrapper.eq(StoreOperationalActivityAchievement::getDeleteFlag, 0)
+                .orderByDesc(StoreOperationalActivityAchievement::getCreatedTime);
+
+        IPage<StoreOperationalActivityAchievement> entityPage = achievementMapper.selectPage(page, wrapper);
+        Page<StoreOperationalActivityAchievementVo> voPage = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
+        if (entityPage.getRecords() != null) {
+            List<StoreOperationalActivityAchievementVo> voList = new ArrayList<>();
+            for (StoreOperationalActivityAchievement item : entityPage.getRecords()) {
+                if (item == null) {
+                    continue;
+                }
+                StoreOperationalActivityAchievementVo vo = new StoreOperationalActivityAchievementVo();
+                BeanUtils.copyProperties(item, vo);
+                vo.setMediaUrlList(splitMediaUrls(item.getMediaUrls()));
+                voList.add(vo);
+            }
+            voPage.setRecords(voList);
+        }
+        return voPage;
+    }
+
+    private List<String> splitMediaUrls(String mediaUrls) {
+        if (mediaUrls == null || mediaUrls.trim().isEmpty()) {
+            return Collections.emptyList();
+        }
+        String[] parts = mediaUrls.split(",");
+        List<String> results = new ArrayList<>();
+        for (String part : parts) {
+            if (part != null && !part.trim().isEmpty()) {
+                results.add(part.trim());
+            }
+        }
+        return results;
+    }
+}

+ 251 - 0
alien-store/src/main/java/shop/alien/store/service/impl/StoreOperationalActivityServiceImpl.java

@@ -0,0 +1,251 @@
+package shop.alien.store.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.LifeDiscountCoupon;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.entity.store.StoreInfo;
+import shop.alien.entity.storePlatform.StoreOperationalActivity;
+import shop.alien.entity.storePlatform.StoreOperationalActivitySignup;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivityVO;
+import shop.alien.entity.storePlatform.vo.StoreOperationalActivitySignupCheckVo;
+import shop.alien.mapper.LifeDiscountCouponMapper;
+import shop.alien.mapper.StoreImgMapper;
+import shop.alien.mapper.StoreInfoMapper;
+import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
+import shop.alien.mapper.storePlantform.StoreOperationalActivitySignupMapper;
+import shop.alien.store.config.BaseRedisService;
+import shop.alien.store.dto.StoreOperationalActivitySignupDto;
+import shop.alien.store.service.StoreOperationalActivityService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.alibaba.fastjson.JSONObject;
+import shop.alien.util.common.JwtUtil;
+
+import java.util.Date;
+
+/**
+ * 用户端运营活动服务实现
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class StoreOperationalActivityServiceImpl implements StoreOperationalActivityService {
+
+    private final StoreOperationalActivityMapper activityMapper;
+    private final StoreImgMapper imgMapper;
+    private final LifeDiscountCouponMapper lifeDiscountCouponMapper;
+    private final StoreInfoMapper storeInfoMapper;
+    private final StoreOperationalActivitySignupMapper signupMapper;
+    private final BaseRedisService baseRedisService;
+
+    @Override
+    public StoreOperationalActivityVO getActivityDetail(Integer id) {
+        log.info("StoreOperationalActivityServiceImpl.getActivityDetail: id={}", id);
+        if (id == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+        StoreOperationalActivity activity = activityMapper.selectById(id);
+        if (activity == null) {
+            return null;
+        }
+
+        StoreOperationalActivityVO vo = new StoreOperationalActivityVO();
+        BeanUtils.copyProperties(activity, vo);
+        vo.setStatusName(resolveStatusName(activity.getStatus()));
+        fillSignupFlags(vo, activity);
+        fillSignupCounts(vo);
+
+        if (activity.getCouponId() != null) {
+            LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
+            if (coupon != null) {
+                vo.setCouponName(coupon.getName());
+            }
+        }
+
+        attachStoreInfo(vo);
+        fillActivityImages(vo);
+        return vo;
+    }
+
+    @Override
+    public StoreOperationalActivitySignupCheckVo checkSignup(Integer activityId, Integer userId) {
+        log.info("StoreOperationalActivityServiceImpl.checkSignup: activityId={}, userId={}", activityId, userId);
+        if (activityId == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+        if (userId == null) {
+            throw new IllegalArgumentException("用户ID不能为空");
+        }
+        StoreOperationalActivity activity = activityMapper.selectById(activityId);
+        if (activity == null) {
+            throw new IllegalArgumentException("活动不存在");
+        }
+
+        Integer signupCount = signupMapper.countSignupByActivityId(activityId);
+        Integer limitPeople = activity.getActivityLimitPeople();
+        boolean full = limitPeople != null && limitPeople > 0 && signupCount != null && signupCount >= limitPeople;
+
+        Integer userSignupCount = signupMapper.countSignedUpByActivityAndUser(activityId, userId);
+        boolean signedUp = userSignupCount != null && userSignupCount > 0;
+
+        StoreOperationalActivitySignupCheckVo vo = new StoreOperationalActivitySignupCheckVo();
+        vo.setCurrentSignupCount(signupCount != null ? signupCount : 0);
+        vo.setActivityLimitPeople(limitPeople);
+        vo.setFull(full);
+        vo.setSignedUp(signedUp);
+        return vo;
+    }
+
+    @Override
+    public boolean signup(StoreOperationalActivitySignupDto dto) {
+        if (dto == null) {
+            throw new IllegalArgumentException("报名参数不能为空");
+        }
+        if (dto.getActivityId() == null) {
+            throw new IllegalArgumentException("活动ID不能为空");
+        }
+        if (dto.getUserId() == null) {
+            throw new IllegalArgumentException("用户ID不能为空");
+        }
+
+        StoreOperationalActivity activity = activityMapper.selectById(dto.getActivityId());
+        if (activity == null) {
+            throw new IllegalArgumentException("活动不存在");
+        }
+
+        String lockKey = "activity:signup:" + dto.getActivityId();
+        String lockVal = baseRedisService.lock(lockKey, 5000, 5000);
+        if (lockVal == null) {
+            throw new IllegalArgumentException("系统繁忙,请稍后再试");
+        }
+
+        try {
+            Integer userSignupCount = signupMapper.countApprovedByActivityAndUser(dto.getActivityId(), dto.getUserId());
+            if (userSignupCount != null && userSignupCount > 0) {
+                throw new IllegalArgumentException("已成功报名,请勿重复报名");
+            }
+
+            Integer approvedCount = signupMapper.countSignupByActivityId(dto.getActivityId());
+            Integer limitPeople = activity.getActivityLimitPeople();
+            if (limitPeople != null && limitPeople > 0 && approvedCount != null && approvedCount >= limitPeople) {
+                throw new IllegalArgumentException("报名人数已满");
+            }
+
+            StoreOperationalActivitySignup signup = new StoreOperationalActivitySignup();
+            signup.setActivityId(dto.getActivityId());
+            signup.setStoreId(activity.getStoreId());
+            signup.setUserId(dto.getUserId());
+            signup.setUserName(dto.getUserName());
+            signup.setPhone(dto.getPhone());
+            signup.setStatus(0);
+            signup.setSignupTime(new Date());
+            signup.setDeleteFlag(0);
+            signup.setCreatedUserId(dto.getUserId());
+            return signupMapper.insert(signup) > 0;
+        } finally {
+            baseRedisService.unlock(lockKey, lockVal);
+        }
+    }
+
+    private String resolveStatusName(Integer status) {
+        if (status == null) {
+            return null;
+        }
+        switch (status) {
+            case 1:
+                return "待审核";
+            case 2:
+                return "未开始";
+            case 3:
+                return "审核拒绝";
+            case 4:
+                return "已售罄";
+            case 5:
+                return "进行中";
+            case 6:
+                return "已下架";
+            case 7:
+                return "已结束";
+            default:
+                return null;
+        }
+    }
+
+    private void attachStoreInfo(StoreOperationalActivityVO vo) {
+        if (vo == null || vo.getStoreId() == null) {
+            return;
+        }
+        StoreInfo storeInfo = storeInfoMapper.selectById(vo.getStoreId());
+        if (storeInfo != null) {
+            vo.setStoreName(storeInfo.getStoreName());
+        }
+    }
+
+    private void fillActivityImages(StoreOperationalActivityVO vo) {
+        if (vo == null || vo.getStoreId() == null || vo.getId() == null) {
+            return;
+        }
+        StoreImg titleImg = imgMapper.selectOne(new LambdaQueryWrapper<StoreImg>()
+                .eq(StoreImg::getStoreId, vo.getStoreId())
+                .eq(StoreImg::getBusinessId, vo.getId())
+                .eq(StoreImg::getImgType, 26)
+                .eq(StoreImg::getDeleteFlag, 0));
+        if (titleImg != null) {
+            vo.setActivityTitleImg(titleImg);
+            vo.setActivityTitleImgUrl(titleImg.getImgUrl());
+        }
+
+        StoreImg detailImg = imgMapper.selectOne(new LambdaQueryWrapper<StoreImg>()
+                .eq(StoreImg::getStoreId, vo.getStoreId())
+                .eq(StoreImg::getBusinessId, vo.getId())
+                .eq(StoreImg::getImgType, 27)
+                .eq(StoreImg::getDeleteFlag, 0));
+        if (detailImg != null) {
+            vo.setActivityDetailImg(detailImg);
+            vo.setActivityDetailImgUrl(detailImg.getImgUrl());
+        }
+    }
+
+    private void fillSignupCounts(StoreOperationalActivityVO vo) {
+        if (vo == null || vo.getId() == null) {
+            return;
+        }
+        Integer signupCount = signupMapper.countSignupByActivityId(vo.getId());
+        Integer approvedCount = signupMapper.countApprovedByActivityId(vo.getId());
+        vo.setCurrentSignupCount(signupCount != null ? signupCount : 0);
+        vo.setCurrentApprovedCount(approvedCount != null ? approvedCount : 0);
+    }
+
+    private void fillSignupFlags(StoreOperationalActivityVO vo, StoreOperationalActivity activity) {
+        if (vo == null || activity == null) {
+            return;
+        }
+        boolean expired = false;
+        if (activity.getSignupEndTime() != null) {
+            expired = new Date().after(activity.getSignupEndTime());
+        }
+        vo.setSignupExpired(expired);
+
+        Integer currentUserId = null;
+        try {
+            JSONObject userInfo = JwtUtil.getCurrentUserInfo();
+            if (userInfo != null) {
+                currentUserId = userInfo.getInteger("userId");
+            }
+        } catch (Exception e) {
+            log.debug("获取当前用户失败,无法判断是否已报名", e);
+        }
+
+        if (currentUserId == null) {
+            vo.setSignedUp(false);
+            return;
+        }
+        Integer signupCount = signupMapper.countSignedUpByActivityAndUser(activity.getId(), currentUserId);
+        vo.setSignedUp(signupCount != null && signupCount > 0);
+    }
+}