|
|
@@ -18,6 +18,7 @@ import com.train.mapper.TrainSalonCheckinMapper;
|
|
|
import com.train.mapper.TrainSalonFeedbackMapper;
|
|
|
import com.train.mapper.TrainSalonMapper;
|
|
|
import com.train.mapper.TrainUserMapper;
|
|
|
+import com.train.service.ScoreService;
|
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
|
import org.springframework.util.StringUtils;
|
|
|
@@ -30,6 +31,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|
|
import javax.annotation.Resource;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Arrays;
|
|
|
+import java.util.Date;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
@@ -55,6 +57,8 @@ public class SalonController {
|
|
|
private TrainSalonCheckinMapper trainSalonCheckinMapper;
|
|
|
@Resource
|
|
|
private TrainSalonFeedbackMapper trainSalonFeedbackMapper;
|
|
|
+ @Resource
|
|
|
+ private ScoreService scoreService;
|
|
|
|
|
|
/** 占用名额的报名状态 */
|
|
|
private static final List<String> OCCUPY_STATUS = Arrays.asList("pending", "paid", "confirmed");
|
|
|
@@ -103,6 +107,7 @@ public class SalonController {
|
|
|
row.put("num", num);
|
|
|
row.put("status", salon.getStatus());
|
|
|
row.put("startAt", salon.getStartAt());
|
|
|
+ row.put("endAt", salon.getEndAt());
|
|
|
row.put("createdAt", salon.getCreatedAt());
|
|
|
result.add(row);
|
|
|
}
|
|
|
@@ -230,6 +235,10 @@ public class SalonController {
|
|
|
row.put("orderNo", order == null ? null : order.getOrderNo());
|
|
|
// 沙龙状态(前端据此决定是否显示签到入口)
|
|
|
row.put("salonStatus", s == null ? null : s.getStatus());
|
|
|
+ row.put("salonStartAt", s == null ? null : s.getStartAt());
|
|
|
+ row.put("salonEndAt", s == null ? null : s.getEndAt());
|
|
|
+ // 沙龙展示状态(对齐 cfc applyDisplayState 语义):ended/ongoing/pending_start/active/draft
|
|
|
+ row.put("displayStatus", calcDisplayStatus(s));
|
|
|
// 已签到标记
|
|
|
Long checkedIn = trainSalonCheckinMapper.selectCount(
|
|
|
new LambdaQueryWrapper<TrainSalonCheckin>()
|
|
|
@@ -250,11 +259,12 @@ public class SalonController {
|
|
|
|
|
|
/**
|
|
|
* 沙龙现场签到(需已报名且已支付/已确认;幂等,重复签到返回 true)
|
|
|
+ * 规则对齐 cfc 活动签到:仅在时间窗 [startAt, endAt] 内可签,签到发放积分(checkinPoints)
|
|
|
*/
|
|
|
@Operation(summary = "沙龙签到")
|
|
|
@PostMapping("/checkin")
|
|
|
- public Result<Boolean> checkin(@RequestBody Map<String, Object> body,
|
|
|
- @RequestAttribute("userId") Long userId) {
|
|
|
+ public Result<Map<String, Object>> checkin(@RequestBody Map<String, Object> body,
|
|
|
+ @RequestAttribute("userId") Long userId) {
|
|
|
if (body.get("salonId") == null) {
|
|
|
return Result.error("请选择沙龙期次");
|
|
|
}
|
|
|
@@ -266,6 +276,14 @@ public class SalonController {
|
|
|
if (!"active".equals(salon.getStatus())) {
|
|
|
return Result.error("该沙龙未在进行中");
|
|
|
}
|
|
|
+ // 时间窗校验(对齐 cfc 活动签到):开始前/结束后均不可签到
|
|
|
+ Date now = new Date();
|
|
|
+ if (salon.getStartAt() != null && now.before(salon.getStartAt())) {
|
|
|
+ return Result.error("沙龙尚未开始,暂不能签到");
|
|
|
+ }
|
|
|
+ if (salon.getEndAt() != null && now.after(salon.getEndAt())) {
|
|
|
+ return Result.error("沙龙已结束,不能签到");
|
|
|
+ }
|
|
|
// 报名且已支付/已确认才可签到
|
|
|
Long enrolled = trainEnrollmentMapper.selectCount(
|
|
|
new LambdaQueryWrapper<TrainEnrollment>()
|
|
|
@@ -275,13 +293,16 @@ public class SalonController {
|
|
|
if (enrolled == null || enrolled == 0) {
|
|
|
return Result.error("请先报名并完成支付");
|
|
|
}
|
|
|
- // 幂等:已签到直接返回成功
|
|
|
+ // 幂等:已签到直接返回成功(积分已被发放,pointsEarned=0)
|
|
|
Long existed = trainSalonCheckinMapper.selectCount(
|
|
|
new LambdaQueryWrapper<TrainSalonCheckin>()
|
|
|
.eq(TrainSalonCheckin::getUid, userId)
|
|
|
.eq(TrainSalonCheckin::getSalonId, salonId));
|
|
|
if (existed != null && existed > 0) {
|
|
|
- return Result.success(true);
|
|
|
+ Map<String, Object> done = new HashMap<>();
|
|
|
+ done.put("pointsEarned", 0);
|
|
|
+ done.put("alreadyCompleted", true);
|
|
|
+ return Result.success(done);
|
|
|
}
|
|
|
TrainSalonCheckin checkin = new TrainSalonCheckin();
|
|
|
checkin.setUid(userId);
|
|
|
@@ -291,7 +312,29 @@ public class SalonController {
|
|
|
} catch (Exception ex) {
|
|
|
// 唯一键冲突(并发重复签到)按已签到处理
|
|
|
}
|
|
|
- return Result.success(true);
|
|
|
+ // 签到积分:取关联 cfc 活动的 checkinPoints(创建时默认 5),未关联则用默认 5
|
|
|
+ int points = 5;
|
|
|
+ if (salon.getActivityId() != null) {
|
|
|
+ try {
|
|
|
+ CfcActivity activity = cfcActivityMapper.selectById(salon.getActivityId());
|
|
|
+ if (activity != null && activity.getCheckinPoints() != null && activity.getCheckinPoints() > 0) {
|
|
|
+ points = activity.getCheckinPoints();
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 读取 cfc 活动失败用默认积分
|
|
|
+ }
|
|
|
+ }
|
|
|
+ String title = salon.getTitle() == null ? "" : salon.getTitle();
|
|
|
+ try {
|
|
|
+ scoreService.addUserScoreIfAbsent(userId, "salon_checkin_" + salonId, points, userId, "沙龙签到:" + title);
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 积分发放失败不阻塞签到
|
|
|
+ org.slf4j.LoggerFactory.getLogger(getClass()).warn("沙龙签到积分发放失败: {}", e.getMessage());
|
|
|
+ }
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ result.put("pointsEarned", points);
|
|
|
+ result.put("alreadyCompleted", false);
|
|
|
+ return Result.success(result);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -352,4 +395,30 @@ public class SalonController {
|
|
|
}
|
|
|
return Result.success(true);
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 沙龙展示状态(对齐 cfc 活动 applyDisplayState 语义):
|
|
|
+ * ended=已结束 / ongoing=进行中(时间窗内) / pending_start=未开始 / active=进行中(无时间窗) / draft=草稿
|
|
|
+ */
|
|
|
+ private String calcDisplayStatus(TrainSalon s) {
|
|
|
+ if (s == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String status = s.getStatus();
|
|
|
+ if ("draft".equals(status)) {
|
|
|
+ return "draft";
|
|
|
+ }
|
|
|
+ Date now = new Date();
|
|
|
+ if ("finished".equals(status)
|
|
|
+ || (s.getEndAt() != null && now.after(s.getEndAt()))) {
|
|
|
+ return "ended";
|
|
|
+ }
|
|
|
+ if (!"active".equals(status)) {
|
|
|
+ return status;
|
|
|
+ }
|
|
|
+ if (s.getStartAt() != null && now.before(s.getStartAt())) {
|
|
|
+ return "pending_start";
|
|
|
+ }
|
|
|
+ return "ongoing";
|
|
|
+ }
|
|
|
}
|