This commit is contained in:
2026-05-28 19:52:20 +08:00
parent daac940f3f
commit 8915ace193
48 changed files with 5333 additions and 1644 deletions
@@ -0,0 +1,196 @@
package com.budwk.app.task.job.fitnesswalk;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.budwk.app.sys.models.Sys_task;
import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkActivity;
import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkActivityRelationProject;
import com.budwk.app.zhgh.activity.fitnesswalk.service.FitnessWalkCommonService;
import com.budwk.app.zhgh.activity.fitnesswalk.service.impl.FitnessWalkCommonServiceImpl;
import com.budwk.app.zhgh.activity.fitnesswalk.utils.WeAppCloudUtil;
import lombok.extern.slf4j.Slf4j;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.mvc.Mvcs;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.Arrays;
/**
* @author zhf
* @date 2026/4/11 14:01
* @description 自动创建活动
*/
@IocBean
@Slf4j
public class FitnessWalkAddActivityJob implements Job {
@Override
@Aop(TransAop.READ_COMMITTED)
public void execute(JobExecutionContext context) throws JobExecutionException {
Ioc ioc = Mvcs.ctx().getDefaultIoc();
Dao dao = ioc.get(Dao.class);
WeAppCloudUtil weAppCloudUtil = ioc.get(WeAppCloudUtil.class);
// Quartz 任务上下文里按接口取 Bean 容易命中 Nutz 的接口实例化异常,这里统一按实现类获取后再走 service 公共方法。
FitnessWalkCommonService fitnessWalkCommonService = ioc.get(FitnessWalkCommonServiceImpl.class);
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
String sourceActivityId = getJobString(jobDataMap, "sourceActivityId");
String deleteId = getJobString(jobDataMap, "deleteId");
long targetDate = getJobLong(jobDataMap, "targetDate");
try {
FitnessWalkActivity sourceActivity = loadSourceActivity(weAppCloudUtil, sourceActivityId);
if (sourceActivity == null) {
disableCurrentTask(dao, deleteId, sourceActivityId);
return;
}
FitnessWalkActivity newActivity = JSON.parseObject(JSON.toJSONString(sourceActivity), FitnessWalkActivity.class);
shiftActivityDate(sourceActivity, newActivity, targetDate);
resetBranchActivityNameByRelationProject(dao, newActivity, targetDate);
long currentTime = System.currentTimeMillis();
long normalizedTargetDate = DateUtil.beginOfDay(DateUtil.date(targetDate)).getTime();
newActivity.set_id(null);
newActivity.setCreatedAt(currentTime);
newActivity.setUpdatedAt(currentTime);
// 自动复制出的活动补充来源标记,便于后续编辑原活动时识别哪些日期已经真正生成过活动。
newActivity.setAutoCreateSourceActivityId(sourceActivityId);
newActivity.setAutoCreateTargetDate(normalizedTargetDate);
JSONObject insertResult = weAppCloudUtil.request(WeAppCloudUtil.CRUD.INSERT,
"db.collection('activity').add({data:" + Json.toJson(newActivity) + "})");
if (insertResult.containsKey("id_list") && insertResult.getJSONArray("id_list") != null && !insertResult.getJSONArray("id_list").isEmpty()) {
newActivity.set_id(insertResult.getJSONArray("id_list").getString(0));
}
// 自动复制出的活动也要同步刷新缓存,并补齐该活动自身的抽奖任务。
fitnessWalkCommonService.addLotteryTask(newActivity);
fitnessWalkCommonService.addOrEditDoSaveRedis(newActivity);
} catch (Exception e) {
log.error("自动连续创建活动失败, sourceActivityId={}", sourceActivityId, e);
throw new JobExecutionException(e);
} finally {
disableCurrentTask(dao, deleteId, sourceActivityId);
}
}
/**
* Quartz 的 JobDataMap 可能直接存的是 Long、Integer 等对象,不能一律用 getString 读取。
* 这里统一兜底转成字符串,避免任务参数类型变化时再次触发 ClassCastException。
*/
private String getJobString(JobDataMap jobDataMap, String key) {
Object value = jobDataMap.get(key);
return value == null ? null : String.valueOf(value);
}
/**
* 定时任务中的日期时间参数既可能是数字,也可能是字符串。
* 这里统一兼容处理,确保 targetDate 在测试和正式任务下都能稳定转成时间戳。
*/
private long getJobLong(JobDataMap jobDataMap, String key) {
Object value = jobDataMap.get(key);
if (value == null) {
throw new IllegalArgumentException("定时任务缺少参数:" + key);
}
if (value instanceof Number) {
return ((Number) value).longValue();
}
return Long.parseLong(String.valueOf(value));
}
/**
* 根据原活动ID查询原活动。
* 定时任务执行时统一从云库读取最新活动内容,确保复制时拿到的是当前活动的最新配置。
*/
private FitnessWalkActivity loadSourceActivity(WeAppCloudUtil weAppCloudUtil, String sourceActivityId) {
JSONObject jsonObject = weAppCloudUtil.request(WeAppCloudUtil.CRUD.QUERY,
"db.collection('activity').doc('" + sourceActivityId + "').get()");
return jsonObject.getJSONArray("data").stream()
.map(item -> JSON.parseObject((String) item, FitnessWalkActivity.class))
.findFirst()
.orElse(null);
}
/**
* 将活动的报名时间和活动时间整体平移到目标日期。
* 这里保留原活动的时分秒和时长,仅根据目标日期替换所属自然日。
*/
private void shiftActivityDate(FitnessWalkActivity sourceActivity, FitnessWalkActivity newActivity, long targetDate) {
DateTime sourceStartDate = DateUtil.beginOfDay(DateUtil.date(sourceActivity.getStartTime()));
DateTime targetStartDate = DateUtil.beginOfDay(DateUtil.date(targetDate));
long offsetMillis = targetStartDate.getTime() - sourceStartDate.getTime();
if (sourceActivity.getApplyTime() != null && sourceActivity.getApplyTime().length == 2) {
Long[] applyTime = Arrays.copyOf(sourceActivity.getApplyTime(), 2);
applyTime[0] = applyTime[0] + offsetMillis;
applyTime[1] = applyTime[1] + offsetMillis;
newActivity.setApplyTime(applyTime);
newActivity.setApplyStartTime(applyTime[0]);
newActivity.setApplyEndTime(applyTime[1]);
}
if (sourceActivity.getTime() != null && sourceActivity.getTime().length == 2) {
Long[] time = Arrays.copyOf(sourceActivity.getTime(), 2);
time[0] = time[0] + offsetMillis;
time[1] = time[1] + offsetMillis;
newActivity.setTime(time);
newActivity.setStartTime(time[0]);
newActivity.setEndTime(time[1]);
}
}
/**
* 自动连续创建活动时,分活动名称统一取“关联活动配置名称 + 日期前缀”。
* 优先读取关联活动配置表中的主活动名称;若当前活动没有配置关联活动,则回退到原分活动名称。
*/
private void resetBranchActivityNameByRelationProject(Dao dao, FitnessWalkActivity newActivity, long targetDate) {
if (newActivity == null) {
return;
}
String datePrefix = DateUtil.format(DateUtil.date(targetDate), "yyyy年MM月dd日");
String targetName = null;
if (StrUtil.isNotBlank(newActivity.getRelationActivityId())) {
FitnessWalkActivityRelationProject relationProject = dao.fetch(
FitnessWalkActivityRelationProject.class,
Cnd.where("id", "=", newActivity.getRelationActivityId())
);
if (relationProject != null && StrUtil.isNotBlank(relationProject.getMasterActivityName())) {
targetName = relationProject.getMasterActivityName().trim();
}
}
if (StrUtil.isBlank(targetName) && StrUtil.isNotBlank(newActivity.getBranchActivityName())) {
targetName = newActivity.getBranchActivityName().trim();
}
if (StrUtil.isBlank(targetName)) {
return;
}
if (targetName.startsWith(datePrefix)) {
newActivity.setBranchActivityName(targetName);
return;
}
newActivity.setBranchActivityName(datePrefix + " " + targetName);
}
/**
* 当前任务执行完成后禁用自身,避免同一日期重复创建活动。
*/
private void disableCurrentTask(Dao dao, String deleteId, String sourceActivityId) {
dao.update(Sys_task.class, Chain.make("disabled", 1),
Cnd.where("data", "like", "%" + deleteId + "%").and("data", "like", "%" + sourceActivityId + "%"));
}
}
@@ -0,0 +1,203 @@
package com.budwk.app.task.job.fitnesswalk;
import cn.hutool.core.date.DateUtil;
import com.budwk.app.sys.models.Sys_task;
import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkAwardUser;
import com.budwk.app.zhgh.activity.fitnesswalk.model.FitnessWalkRaffleUser;
import lombok.extern.slf4j.Slf4j;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos;
import org.nutz.dao.util.cri.Static;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.Mvcs;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.*;
import java.util.stream.Collectors;
@IocBean
@Slf4j
public class FitnessWalkLotteryJob implements Job {
@Override
@Aop(TransAop.READ_COMMITTED)
public void execute(JobExecutionContext context) throws JobExecutionException {
Ioc ioc = Mvcs.ctx().getDefaultIoc();
Dao dao = ioc.get(Dao.class);
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
String deleteId = jobDataMap.getString("deleteId");
int lotteryQualificationStep = Integer.parseInt(jobDataMap.getString("lotteryQualificationStep"));
int lotteryUserNum = Integer.parseInt(jobDataMap.getString("lotteryUserNum"));
String mode = jobDataMap.getString("mode");
String activityId = jobDataMap.getString("id");
String lotteryQualificationDay = jobDataMap.getString("lotteryQualificationDay");
String awardName;
Date startDate = null;
Date endDate = null;
if ("custom".equals(mode)) {
awardName = jobDataMap.getString("awardName");
String startTimeStr = jobDataMap.getString("startDate");
String endTimeStr = jobDataMap.getString("endDate");
startDate = DateUtil.parse(startTimeStr, "yyyy-MM-dd");
endDate = DateUtil.parse(endTimeStr, "yyyy-MM-dd");
} else {
awardName = null;
}
List<NutMap> userList = new ArrayList<>();
if ("custom".equals(mode)) {
//查询出符合条件的用户
Sql sql = Sqls.create("""
SELECT
u.id,
COALESCE(SUM(sub.step), 0) AS total_steps,
u.loginname,
u.username,
u.unionid,
u.unionname,
u.unitid,
u.unitname,
over3k.standardsDays
FROM
`user` u
LEFT JOIN (
SELECT
userId,
applyDate,
step
FROM
`fitness_walk_step`
WHERE
activityId = @activityId
AND DATE(applyDate) >= @startDate
AND DATE(applyDate) <= @endDate
$lotteryQualificationDaySql
GROUP BY
userId,
applyDate
) AS sub ON u.id = sub.userId
LEFT JOIN (
SELECT
userId,
COUNT(DISTINCT DATE(applyDate)) AS standardsDays
FROM
`fitness_walk_step`
WHERE
activityId = @activityId
AND DATE(applyDate) >= @startDate
AND DATE(applyDate) <= @endDate
$lotteryQualificationDaySql
GROUP BY
userId
) AS over3k ON u.id = over3k.userId
$whereSql
GROUP BY
u.id,
u.username,
u.loginname,
u.unionname,
u.unitname
HAVING
$stepHavingSql
""");
if (Strings.isNotBlank(lotteryQualificationDay)) {
sql.setVar("lotteryQualificationDaySql", new Static(" AND step >= '%s' ".formatted(lotteryQualificationStep)));
sql.setVar("stepHavingSql", new Static(" over3k.standardsDays >= '%s' ".formatted(lotteryQualificationDay)));
} else {
sql.setVar("stepHavingSql", new Static(" COALESCE(SUM(sub.step), 0) >= '%s' ".formatted(lotteryQualificationStep)));
}
sql.setVar("whereSql", new Static(" where u.loginname not in (select loginName from fitness_walk_award_user where activityId = " + activityId + " ) "));
sql.setParam("activityId", activityId);
sql.setParam("step", lotteryQualificationStep);
sql.setParam("startDate", startDate);
sql.setParam("endDate", endDate);
userList = (List<NutMap>) Daos.query(dao, sql.toString(), Sqls.callback.maps());
} else if ("everyday".equals(mode)) {
Sql sql = Sqls.create("""
SELECT
jws.userId AS id,
u.username,
u.loginname,
u.unionid,
u.unionname,
u.unitid,
u.unitname
FROM
fitness_walk_step jws
LEFT JOIN `user` u ON u.id = jws.userId
WHERE
jws.activityId = @activityId
AND jws.step >= @step
AND jws.applyDate = @date
""");
sql.setParam("activityId", activityId);
sql.setParam("step", lotteryQualificationStep);
sql.setParam("date", DateUtil.today());
userList = (List<NutMap>) Daos.query(dao, sql.toString(), Sqls.callback.maps());
}
// 记录达标人员,表明这些人有资格抽奖
List<FitnessWalkRaffleUser> raffleUserList = userList.stream().map(v -> {
FitnessWalkRaffleUser raffleUser = new FitnessWalkRaffleUser();
raffleUser.setId(R.UU32());
raffleUser.setActivityId(activityId);
raffleUser.setUserId(v.getString("id"));
raffleUser.setLoginName(v.getString("loginname"));
raffleUser.setUsername(v.getString("username"));
if ("custom".equals(mode)) {
raffleUser.setStandardsDays(v.getInt("standardsDays"));
raffleUser.setAwardSteps(v.getString("total_steps"));
}
raffleUser.setIsRaffle(false);
return raffleUser;
}).collect(Collectors.toList());
dao.fastInsert(raffleUserList);
// 随机抽奖,中奖人数为传递过来的 lotteryUserNum
Collections.shuffle(userList);
List<NutMap> lotteryUserList = userList.stream().limit(lotteryUserNum).collect(Collectors.toList());
Date finalStartDate = startDate;
Date finalEndDate = endDate;
List<FitnessWalkAwardUser> awardLists = lotteryUserList.stream().map(v -> {
FitnessWalkAwardUser awardUser = new FitnessWalkAwardUser();
awardUser.setActivityId(activityId);
awardUser.setUserId(v.getString("id"));
awardUser.setUsername(v.getString("username"));
awardUser.setLoginName(v.getString("loginname"));
awardUser.setAwardName(awardName);
awardUser.setUnionId(v.getString("unionid"));
awardUser.setUnionName(v.getString("unionname"));
awardUser.setUnitId(v.getString("unitid"));
awardUser.setUnitName(v.getString("unitname"));
awardUser.setApplyDate(new Date());
awardUser.setStartDate(finalStartDate);
awardUser.setEndDate(finalEndDate);
awardUser.setAwardSteps(v.getString("total_steps"));
return awardUser;
}).collect(Collectors.toList());
dao.insert(awardLists);
// 执行完此定时任务后,禁用此定时任务确保不会下次执行
dao.update(Sys_task.class, Chain.make("disabled", 1), Cnd.where("data", "like", "%" + deleteId + "%").and("data", "like", "%" + activityId + "%"));
}
}