This commit is contained in:
2026-06-10 14:38:57 +08:00
parent 06b8a74f41
commit 351806c339
10 changed files with 397 additions and 518 deletions
@@ -18,6 +18,7 @@ import com.budwk.app.zhgh.activity.basic.models.ActivityUserScope;
import com.budwk.app.zhgh.activity.trainSignUp.models.*;
import com.budwk.app.zhgh.activity.trainSignUp.service.TrainSignUpActivityService;
import com.budwk.app.zhgh.activity.trainSignUp.service.TrainSignUpActivityStatisticsService;
import com.budwk.app.zhgh.activity.trainSignUp.service.TrainSignUpBlackListService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -48,10 +49,13 @@ import java.util.stream.Collectors;
public class TrainSignUpApplyController {
private final ReentrantLock lock = new ReentrantLock(true);
private static final String SIGN_UP_LIMIT_MSG = "您已被限制报名香樟工会讲堂,请联系校工会。";
@Inject
private TrainSignUpActivityService trainSignUpActivityService;
@Inject
private TrainSignUpBlackListService trainSignUpBlackListService;
@Inject
private SysDictService dictService;
@Inject
private TrainSignUpActivityStatisticsService statisticsService;
@@ -218,6 +222,9 @@ public class TrainSignUpApplyController {
@Param(value = "currentFamilyNumber") Integer currentFamilyNumber,String IsVerifyUserApply) {
try {
lock.lock();
if (isLimitedByAbsenceBlackList()) {
return Result.error(SIGN_UP_LIMIT_MSG);
}
if (StrUtil.isBlank(courseId)) {
return Result.error("报名信息为空");
}
@@ -350,6 +357,9 @@ public class TrainSignUpApplyController {
public Result doSignUp(TrainSignUpUser trainSignUpUser) {
try {
lock.lock();
if (isLimitedByAbsenceBlackList()) {
return Result.error(SIGN_UP_LIMIT_MSG);
}
boolean courseByUser = trainSignUpActivityService.isSignCourseByUser(trainSignUpUser.getCourseId(), SecurityUtil.getUserId());
if (courseByUser) {
return Result.error("您已报过该活动");
@@ -430,12 +440,16 @@ public class TrainSignUpApplyController {
@SaCheckPermission(value = {"trainSignUp.apply", "h5.trainSignUp.apply"}, mode = SaMode.OR)
@SLog(tag = "品牌活动-活动报名", msg = "判断缺席次数")
public Result validSignCount(@Param("activityId") String activityId) {
if (trainSignUpActivityService.isAbsenceSignUpLimited(SecurityUtil.getUserId())) {
return Result.error(99, "您已连续2次未签到活动,不能报名,请联系校工会");
if (isLimitedByAbsenceBlackList()) {
return Result.error(99, SIGN_UP_LIMIT_MSG);
}
return Result.success();
}
private boolean isLimitedByAbsenceBlackList() {
return trainSignUpBlackListService.autoBlackByYearAndUser(DateUtil.year(new Date()), SecurityUtil.getUserId());
}
@At
@ApiOperation("活动代人报名")
@SaCheckPermission(value = {"trainSignUp.apply"}, mode = SaMode.OR)
@@ -10,11 +10,8 @@ import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_unit;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.trainSignUp.models.*;
import com.budwk.app.zhgh.activity.trainSignUp.service.TrainSignUpActivityService;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.services.SysMsgService;
import com.budwk.app.zhgh.club.model.SysClub;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -26,6 +23,8 @@ import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
@@ -53,8 +52,6 @@ public class TrainSignUpManageController {
@Inject
private TrainSignUpActivityService trainSignUpActivityManageService;
@Inject
private SysMsgService sysMsgService;
@Inject
private Dao dao;
@At("")
@@ -245,24 +242,14 @@ public class TrainSignUpManageController {
@Ok("json:full")
@ApiOperation("通知报名人员")
@SaCheckPermission("trainSignUp.manage")
public Result sendNotify(@Param("activityId") String activityId, @Param("content") String content) {
if (StrUtil.isBlank(activityId) || StrUtil.isBlank(content)) {
public Result sendNotify(@Param("activityId") String activityId, @Param("courseIds") String courseIds, @Param("content") String content) {
if (StrUtil.isBlank(activityId) || StrUtil.isBlank(courseIds) || StrUtil.isBlank(content)) {
return Result.error("参数不完整");
}
// 查询该活动下所有报名人员(state=1为正常报名)
List<TrainSignUpUser> userList = dao.query(TrainSignUpUser.class, Cnd.where("activityId", "=", activityId).and("state", "=", 1));
if (userList.isEmpty()) {
return Result.error("该活动暂无报名人员");
List<String> courseIdList = Json.fromJsonAsList(String.class, courseIds);
if (Lang.isEmpty(courseIdList)) {
return Result.error("请选择要通知的分活动");
}
// 获取所有报名人员的userId
List<String> userIds = userList.stream().map(TrainSignUpUser::getUserId).distinct().toList();
// 查询用户信息获取loginname
List<Sys_user> sysUsers = dao.query(Sys_user.class, Cnd.where("id", "in", userIds));
List<String> loginNames = sysUsers.stream().map(Sys_user::getLoginname).filter(StrUtil::isNotBlank).toList();
if (loginNames.isEmpty()) {
return Result.error("未找到有效的报名人员");
}
sysMsgService.sendMsg(loginNames, "香樟工会讲堂活动通知", content, SecurityUtil.getUserId());
return Result.success("通知发送成功");
return trainSignUpActivityManageService.sendNotify(activityId, courseIdList, content);
}
}
@@ -1,42 +1,24 @@
package com.budwk.app.zhgh.activity.trainSignUp.controller.manage;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.afterturn.easypoi.excel.export.ExcelExportService;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.zhgh.activity.trainSignUp.models.TrainSignUpActivity;
import com.budwk.app.zhgh.activity.trainSignUp.models.TrainSignUpCourse;
import com.budwk.app.zhgh.activity.trainSignUp.models.TrainSignUpUser;
import com.budwk.app.zhgh.activity.trainSignUp.service.TrainSignUpBlackListService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
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.cri.SqlExpressionGroup;
import org.nutz.dao.util.cri.Static;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author zxy
* @Description 人员管理
@@ -48,8 +30,6 @@ import java.util.stream.Collectors;
@At("/platform/trainSignUp/userManage")
public class TrainSignUpUserManageController {
@Inject
private Dao dao;
@Inject
private TrainSignUpBlackListService trainSignUpBlackListService;
@@ -63,288 +43,34 @@ public class TrainSignUpUserManageController {
@ApiOperation("分页查询")
@SaCheckPermission("trainSignUp.userManage")
public Result pageData(PageForm pageForm,
@Param(value = "activityId") String activityId,
@Param(value = "courseId") String courseId,
@Param(value = "year") Integer year,
@Param(value = "userKeyWord") String userKeyWord) {
// 接口参数:pageForm 传分页和排序参数,year 传统计年度,userKeyWord 传姓名或工号关键字;
// 返回 Result<Pagination>,列表包含用户信息、年度签到统计、当前黑名单状态和最近解封时间。
Integer queryYear = year == null ? DateUtil.thisYear() : year;
Cnd cnd = Cnd.NEW();
cnd.andEX("tsuu.activityId", "=", activityId);
cnd.andEX("tsuu.courseId", "=", courseId);
cnd.andEX("act.year", "=", year);
if (StrUtil.isNotBlank(userKeyWord)) {
// 人员关键词支持姓名和工号;搜索时允许展示未报名但存在于用户视图中的人员,便于管理员手动拉黑。
SqlExpressionGroup seg = new SqlExpressionGroup();
cnd.and(seg.andLike("u.username", userKeyWord).orLike("u.loginname", userKeyWord));
}
if(Strings.isNotBlank(pageForm.getPageOrderName()) && Strings.isNotBlank(pageForm.getPageOrderBy())){
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
} else {
// 默认列表只展示本年度有报名统计或已有黑名单记录的人员,避免一次性铺开全部用户。
cnd.and(new Static("(signStats.userId is not null OR tsubl.userId is not null)"));
}
Pagination pagination = trainSignUpBlackListService.pageData(pageForm, cnd, activityId, courseId);
Pagination pagination = trainSignUpBlackListService.pageData(pageForm, cnd, queryYear);
return Result.success(pagination);
}
@At
@ApiOperation("报名人员处理")
@SaCheckPermission("trainSignUp.userManage")
@SLog(tag = "品牌活动-人员管理", msg = "报名人员处理")
@SLog(tag = "品牌活动-人员管理", msg = "报名人员处理")
public Result doHandleUser(@Param("userId") String userId) {
// 接口参数:userId 传被处理人员ID;返回 Result,code=0 表示本次拉黑或解封处理成功。
// 手动处理只切换当前黑名单状态:拉黑时禁止后续报名;解封时记录 lastUnblockTime,后续自动拉黑从该时间重新累计缺席。
trainSignUpBlackListService.doHandleUser(userId);
return Result.success();
}
@At
@ApiOperation("根据活动Id获取子活动")
@SaCheckPermission("trainSignUp.userManage")
public Result getCourseByActivityId(@Param("activityId") String activityId) {
List<TrainSignUpCourse> list = dao.query(TrainSignUpCourse.class, Cnd.where("activityId", "=", activityId));
return Result.success(list);
}
@At
@ApiOperation("获取子活动具体时间")
@SaCheckPermission("trainSignUp.userManage")
public Result attendClassRecord(String userId) {
trainSignUpBlackListService.attendClassRecord(userId);
return Result.success();
}
@At
@ApiOperation("获取候补人员")
@SaCheckPermission("trainSignUp.userManage")
public Result getReserveUser(String courseId) {
Sql sql = Sqls.create("""
SELECT
uc.* ,
(select signUpTime from train_sign_up_user su where su.activityId=uc.activityId and su.courseId = uc.courseId and su.userId=uc.userId) as signUpTime,
(select state from train_sign_up_user su where su.activityId=uc.activityId and su.courseId = uc.courseId and su.userId=uc.userId) as state,
u.username,
u.loginname,
u.mobile,
u.unitName,
u.unionName
FROM
train_sign_up_user_course uc LEFT JOIN `vw_user` u on uc.userId = u.id
WHERE uc.courseId = @courseId and uc.isAttend = false HAVING state = 2 order by signUpTime desc
""").setParam("courseId", courseId);
return Result.success(trainSignUpBlackListService.listMap(sql));
}
@At
@ApiOperation("补充人员")
@SaCheckPermission("trainSignUp.userManage")
@SLog(tag = "品牌活动-人员管理", msg = "补充人员")
public Result reserveSingUp(String[] ids, String courseId) {
//先查询这个课程有多少个未签到的人员
Sql sql = Sqls.create("""
SELECT
uc.* ,
(select signUpTime from train_sign_up_user su where su.activityId=uc.activityId and su.courseId = uc.courseId and su.userId=uc.userId) as signUpTime,
(select state from train_sign_up_user su where su.activityId=uc.activityId and su.courseId = uc.courseId and su.userId=uc.userId) as state
FROM
train_sign_up_user_course uc
WHERE uc.courseId = @courseId and uc.isAttend = false HAVING state = 1 order by signUpTime desc
""").setParam("courseId", courseId);
List<NutMap> list = trainSignUpBlackListService.listMap(sql);
if(ids.length > list.size()) {
return Result.error("您选择了" + ids.length + "位,未签到人员只有" + list.size() + "");
}
//ids的长度为几,就搞几个
List<NutMap> mapList = list.subList(0, ids.length);
List<String> idList = mapList.stream().map(o -> o.getString("userId")).collect(Collectors.toList());
//将这几个没签到的设置为4
dao.update(TrainSignUpUser.class, Chain.make("state", 4), Cnd.where("courseId", "=", courseId)
.and("userId", "in", idList));
//将补充的设置为1
dao.update(TrainSignUpUser.class, Chain.make("state", 1), Cnd.where("courseId", "=", courseId)
.and("userId", "in", ids));
return Result.success();
}
@At
@Ok("void")
@ApiOperation("导出签到人员")
public void exportSignPerson(@Param(value = "activityId") String activityId,
HttpServletResponse response) {
TrainSignUpActivity activity = dao.fetch(TrainSignUpActivity.class, activityId);
Sql sql = Sqls.create("""
SELECT
uc.*,
DATE_FORMAT(uc.courseStartTime, '%Y-%m-%d %H:%i:%s') as courseStartTimeExcel,
DATE_FORMAT(uc.courseEndTime, '%Y-%m-%d %H:%i:%s') as courseEndTimeExcel,
DATE_FORMAT(uc.attendTime, '%Y-%m-%d %H:%i:%s') as attendTimeExcel,
u.username,
u.loginname,
u.unitName,
u.unionName,
u.sex
FROM
train_sign_up_user_course uc
left join train_sign_up_course course on uc.courseId = course.id
left join `vw_user` u on u.id = uc.userId
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("uc.activityId", "=", activityId);
cnd.and("course.isMobileSign", "=", true);
cnd.desc("isAttend").desc("attendTime");
sql.setCondition(cnd);
List<NutMap> userList = trainSignUpBlackListService.listMap(sql);
List<TrainSignUpCourse> courseList = dao.query(TrainSignUpCourse.class, Cnd.where("activityId", "=", activityId));
List<Map<String, Object>> sheetsList = new ArrayList<>();
List<ExcelExportEntity> excelCommonExportEntity = new ArrayList<>();
excelCommonExportEntity.add(new ExcelExportEntity("姓名", "username", 20));
excelCommonExportEntity.add(new ExcelExportEntity("工号", "loginname", 20));
excelCommonExportEntity.add(new ExcelExportEntity("单位", "unitName", 20));
excelCommonExportEntity.add(new ExcelExportEntity("分工会", "unionName", 20));
excelCommonExportEntity.add(new ExcelExportEntity("性别", "sex", 20));
excelCommonExportEntity.add(new ExcelExportEntity("开始时间", "courseStartTimeExcel", 22));
excelCommonExportEntity.add(new ExcelExportEntity("结束时间", "courseEndTimeExcel", 22));
excelCommonExportEntity.add(new ExcelExportEntity("是否签到", "isAttend", 20));
excelCommonExportEntity.add(new ExcelExportEntity("签到时间", "attendTimeExcel", 22));
for (TrainSignUpCourse c : courseList) {
if(!c.isMobileSign()) {
continue;
}
String courseId = c.getId();
String courseName = c.getCourseName();
List<NutMap> v = userList.stream().filter(x -> x.getString("courseId").equals(courseId)).collect(Collectors.toList());
ExportParams userExportParams = new ExportParams();
userExportParams.setSheetName(courseName);
List<ExcelExportEntity> currentEntities = new ArrayList<>(excelCommonExportEntity);
for (NutMap userSignData : v) {
if(!userSignData.getBoolean("isAttend")) {
userSignData.put("isAttend", "未签到");
userSignData.put("attendTimeExcel", "未签到");
}else {
userSignData.put("isAttend", "已签到");
}
}
Map<String, Object> userExportMap = new HashMap<>();
userExportMap.put("name", courseName);
userExportMap.put("title", userExportParams);
userExportMap.put("entity", currentEntities);
userExportMap.put("data", v);
sheetsList.add(userExportMap);
}
try {
String fileName = activity.getActivityName() + "签到人员名单.xls";
String disposition = "attachment;filename=" + URLEncoder.encode(fileName, "utf-8");
response.setCharacterEncoding("utf-8");
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", disposition);
Workbook workbook = new HSSFWorkbook();
for (Map<String, Object> map : sheetsList) {
ExcelExportService service = new ExcelExportService();
service.createSheetForMap(workbook,(ExportParams) map.get("title"),(List<ExcelExportEntity>) map.get("entity"),(Collection<?>) map.get("data"));
}
workbook.write(response.getOutputStream());
workbook.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@At
@Ok("void")
@ApiOperation("导出领取人员")
public void exportGiftPerson(@Param(value = "activityId") String activityId,
HttpServletResponse response) {
TrainSignUpActivity activity = dao.fetch(TrainSignUpActivity.class, activityId);
Sql sql = Sqls.create("""
SELECT
uc.*,
DATE_FORMAT(uc.courseStartTime, '%Y-%m-%d %H:%i:%s') as courseStartTimeExcel,
DATE_FORMAT(uc.courseEndTime, '%Y-%m-%d %H:%i:%s') as courseEndTimeExcel,
DATE_FORMAT(uc.receiveTime, '%Y-%m-%d %H:%i:%s') as receiveTimeExcel,
u.username,
u.loginname,
u.unitName,
u.unionName,
u.sex
FROM
train_sign_up_user_course uc
left join train_sign_up_course course on uc.courseId = course.id
left join `vw_user` u on u.id = uc.userId
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("uc.activityId", "=", activityId);
cnd.and("course.isReceiveGift", "=", true).and("course.giftType" ,"=", 1);
cnd.desc("isReceive").desc("receiveTime");
sql.setCondition(cnd);
List<NutMap> userList = trainSignUpBlackListService.listMap(sql);
List<TrainSignUpCourse> courseList = dao.query(TrainSignUpCourse.class, Cnd.where("activityId", "=", activityId));
List<Map<String, Object>> sheetsList = new ArrayList<>();
List<ExcelExportEntity> excelCommonExportEntity = new ArrayList<>();
excelCommonExportEntity.add(new ExcelExportEntity("姓名", "username", 20));
excelCommonExportEntity.add(new ExcelExportEntity("工号", "loginname", 20));
excelCommonExportEntity.add(new ExcelExportEntity("单位", "unitName", 20));
excelCommonExportEntity.add(new ExcelExportEntity("分工会", "unionName", 20));
excelCommonExportEntity.add(new ExcelExportEntity("性别", "sex", 20));
excelCommonExportEntity.add(new ExcelExportEntity("开始时间", "courseStartTimeExcel", 22));
excelCommonExportEntity.add(new ExcelExportEntity("结束时间", "courseEndTimeExcel", 22));
excelCommonExportEntity.add(new ExcelExportEntity("是否领取", "isReceive", 20));
excelCommonExportEntity.add(new ExcelExportEntity("领取时间", "receiveTimeExcel", 22));
for (TrainSignUpCourse c : courseList) {
String courseId = c.getId();
String courseName = c.getCourseName();
List<NutMap> v = userList.stream().filter(x -> x.getString("courseId").equals(courseId)).collect(Collectors.toList());
ExportParams userExportParams = new ExportParams();
userExportParams.setSheetName(courseName);
List<ExcelExportEntity> currentEntities = new ArrayList<>(excelCommonExportEntity);
for (NutMap userSignData : v) {
if(!userSignData.getBoolean("isReceive")) {
userSignData.put("isReceive", "未领取");
userSignData.put("receiveTimeExcel", "未领取");
}else {
userSignData.put("isReceive", "已领取");
}
}
Map<String, Object> userExportMap = new HashMap<>();
userExportMap.put("name", courseName);
userExportMap.put("title", userExportParams);
userExportMap.put("entity", currentEntities);
userExportMap.put("data", v);
sheetsList.add(userExportMap);
}
try {
String fileName = activity.getActivityName() + "礼品领取人员名单.xls";
String disposition = "attachment;filename=" + URLEncoder.encode(fileName, "utf-8");
response.setCharacterEncoding("utf-8");
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", disposition);
Workbook workbook = new HSSFWorkbook();
for (Map<String, Object> map : sheetsList) {
ExcelExportService service = new ExcelExportService();
service.createSheetForMap(workbook,(ExportParams) map.get("title"),(List<ExcelExportEntity>) map.get("entity"),(Collection<?>) map.get("data"));
}
workbook.write(response.getOutputStream());
workbook.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -6,6 +6,8 @@ import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
@Data
@Accessors(chain = true)
@Comment("品牌活动黑名单(香樟工会)")
@@ -28,4 +30,10 @@ public class TrainSignUpBlackList {
@Comment("是否禁用")
private Boolean isDisabled;
@Column
@ColDefine(type = ColType.DATETIME)
@Comment("最近解封时间,用于重置本年度自动拉黑缺席统计起点")
private Date lastUnblockTime;
}
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.activity.trainSignUp.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.activity.trainSignUp.models.TrainSignUpActivity;
import com.budwk.app.zhgh.activity.trainSignUp.models.TrainSignUpCourse;
@@ -140,4 +141,14 @@ public interface TrainSignUpActivityService extends BaseService<TrainSignUpActiv
* @param trainSignUpUser
*/
void doSignUpInsteadApply(TrainSignUpUser trainSignUpUser);
/**
* 按活动下的分活动通知报名人员。
*
* @param activityId 主活动ID,用于限定本次通知所属活动
* @param courseIds 分活动ID列表,至少选择一个;仅通知这些分活动下正常报名人员
* @param content 通知正文内容
* @return Result,成功时返回通知发送成功,失败时返回无报名人员或无有效接收人等原因
*/
Result sendNotify(String activityId, List<String> courseIds, String content);
}
@@ -17,20 +17,39 @@ import java.util.List;
public interface TrainSignUpBlackListService extends BaseService<TrainSignUpBlackList> {
/**
* 分页
* 年度人员黑名单分页
*
* @param pageForm 分页
* @return Pagination
* @param pageForm 分页参数,包含页码、每页数量、排序字段和排序方向
* @param cnd 人员查询条件,搜索时按姓名/工号过滤,未搜索时只展示年度报名人员或已有黑名单记录人员
* @param year 统计年度,对应 train_sign_up_activity.year
* @return Pagination 列表字段包含用户基础信息、年度报名统计、当前黑名单状态和最近解封时间
*/
Pagination pageData(PageForm pageForm, Cnd cnd, String activityId, String courseId);
Pagination pageData(PageForm pageForm, Cnd cnd, Integer year);
/**
* 拉黑解封用户
* 手动拉黑解封用户
*
* @param userId
* @param userId 用户ID;当前无记录时新增拉黑,当前已拉黑时解封并记录 lastUnblockTime,当前已解封时再次拉黑
*/
void doHandleUser(String userId);
/**
* 判断用户是否处于品牌活动黑名单中。
*
* @param userId 当前报名用户ID
* @return true 表示用户已被拉黑,不能继续报名品牌活动
*/
boolean isBlackListed(String userId);
/**
* 按年度自动判断用户是否应被拉黑。
*
* @param year 当前统计年度,统计 train_sign_up_activity.year 等于该年度的报名签到记录
* @param userId 当前报名用户ID
* @return true 表示用户当前已被拉黑或已达到年度自动拉黑条件,不能继续报名
*/
boolean autoBlackByYearAndUser(Integer year, String userId);
/**
* 上课记录
*
@@ -6,9 +6,12 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_config;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.services.SysMsgService;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.trainSignUp.models.*;
@@ -48,6 +51,8 @@ public class TrainSignUpActivityServiceImpl extends BaseServiceImpl<TrainSignUpA
@Inject
private TrainSignUpActivityStatisticsService statisticsService;
@Inject
private SysMsgService sysMsgService;
public TrainSignUpActivityServiceImpl(Dao dao) {
super(dao);
@@ -621,4 +626,45 @@ public class TrainSignUpActivityServiceImpl extends BaseServiceImpl<TrainSignUpA
asyncInsertUserCourse(trainSignUpUser.getActivityId(), trainSignUpUser.getCourseId(), trainSignUpUser.getUserId());
}
}
@Override
@Aop(TransAop.READ_COMMITTED)
public Result sendNotify(String activityId, List<String> courseIds, String content) {
if (StrUtil.isBlank(activityId) || Lang.isEmpty(courseIds) || StrUtil.isBlank(content)) {
return Result.error("参数不完整");
}
// 按主活动和所选分活动限定通知范围,仅发送给正常报名人员;state=3 是候补转正常后的状态。
List<TrainSignUpUser> userList = dao().query(
TrainSignUpUser.class,
Cnd.where("activityId", "=", activityId)
.and("courseId", "in", courseIds)
.and("state", "in", List.of(1, 3))
);
if (Lang.isEmpty(userList)) {
return Result.error("所选分活动暂无报名人员");
}
List<String> userIds = userList.stream()
.map(TrainSignUpUser::getUserId)
.filter(StrUtil::isNotBlank)
.distinct()
.toList();
if (Lang.isEmpty(userIds)) {
return Result.error("未找到有效的报名人员");
}
List<Sys_user> sysUsers = dao().query(Sys_user.class, Cnd.where("id", "in", userIds));
List<String> loginNames = sysUsers.stream()
.map(Sys_user::getLoginname)
.filter(StrUtil::isNotBlank)
.distinct()
.toList();
if (Lang.isEmpty(loginNames)) {
return Result.error("未找到有效的报名人员");
}
sysMsgService.sendMsg(loginNames, "香樟工会讲堂活动通知", content, SecurityUtil.getUserId());
return Result.success("通知发送成功");
}
}
@@ -7,15 +7,19 @@ import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.zhgh.activity.trainSignUp.models.TrainSignUpBlackList;
import com.budwk.app.zhgh.activity.trainSignUp.service.TrainSignUpBlackListService;
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.Criteria;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
@@ -31,41 +35,63 @@ public class TrainSignUpUserServiceImpl extends BaseServiceImpl<TrainSignUpBlack
}
@Override
public Pagination pageData(PageForm pageForm, Cnd cnd, String activityId, String courseId) {
public Pagination pageData(PageForm pageForm, Cnd cnd, Integer year) {
// 年度黑名单列表按一次报名统计签到结果:同一 userId + activityId + courseId 下多个签到时间段只算一次。
// 只要该报名任意时间段已签到,就算已签到;全部未签到且最后一个时间段已结束,才算缺席。
Sql sql = Sqls.create("""
SELECT
u.id AS userId,
u.username,
u.loginname,
ifnull(tsuu.mobile, u.mobile) as mobile,
u.unitName,
u.unionName,
tsuc.courseName,
tsuu.state,
( SELECT count( 1 ) FROM train_sign_up_user_course WHERE userId = tsuu.userId $var) courseTotal,
( SELECT count( 1 ) FROM train_sign_up_user_course WHERE userId = tsuu.userId AND isAttend = 0 and tsuu.state!=2 AND now()> courseEndTime $var) AS absentCount,
( SELECT count( 1 ) FROM train_sign_up_user_course WHERE userId = tsuu.userId AND isAttend = 1 and tsuu.state!=2 AND now()> courseEndTime $var) AS signCount,
( SELECT count( 1 ) FROM train_sign_up_user_course WHERE userId = tsuu.userId and tsuu.state!=2 $var) AS totalCount,
if(tsubl.isDisabled=1,true,false) isDisabled,
group_CONCAT( tsuc.courseName ) AS courseNames
u.username,
u.loginname,
u.mobile,
u.unitName,
u.unionName,
IFNULL(signStats.totalCount, 0) AS totalCount,
IFNULL(signStats.signCount, 0) AS signCount,
IFNULL(signStats.absentCount, 0) AS absentCount,
if(tsubl.isDisabled=1,true,false) isDisabled,
tsubl.lastUnblockTime
FROM
`train_sign_up_user` tsuu
LEFT JOIN `vw_user` u ON u.id = tsuu.userId
LEFT JOIN train_sign_up_activity act on act.id = tsuu.activityId
LEFT JOIN train_sign_up_course tsuc ON tsuc.id = tsuu.courseId
LEFT JOIN train_sign_up_black_list tsubl on tsubl.userId = tsuu.userId
`vw_user` u
LEFT JOIN train_sign_up_black_list tsubl on tsubl.userId = u.id
LEFT JOIN (
SELECT
applyStats.userId,
sum(case when applyStats.lastCourseEndTime < now() then 1 else 0 end) AS totalCount,
sum(case when applyStats.lastCourseEndTime < now() and applyStats.hasAttend = 1 then 1 else 0 end) AS signCount,
sum(case when applyStats.lastCourseEndTime < now() and applyStats.hasAttend = 0 then 1 else 0 end) AS absentCount
FROM
(
SELECT
su.userId,
su.activityId,
su.courseId,
max(case when uc.isAttend = true then 1 else 0 end) AS hasAttend,
max(uc.courseEndTime) AS lastCourseEndTime
FROM
train_sign_up_user su
LEFT JOIN train_sign_up_activity act ON act.id = su.activityId
LEFT JOIN train_sign_up_course course ON course.id = su.courseId
LEFT JOIN train_sign_up_user_course uc ON uc.activityId = su.activityId
AND uc.courseId = su.courseId
AND uc.userId = su.userId
WHERE
act.year = @year
AND su.state != 2
AND course.isMobileSign = 1
GROUP BY
su.userId,
su.activityId,
su.courseId
) applyStats
GROUP BY
applyStats.userId
) signStats ON signStats.userId = u.id
$condition
""");
cnd.groupBy("tsuu.userId");
Criteria varCnd = Cnd.cri();
varCnd.where().setTop(false);
varCnd.where().andEX("activityId", "=", activityId);
varCnd.where().andEX("courseId", "=", courseId);
if (!varCnd.where().isEmpty()) {
sql.vars().set("var", "and " + varCnd.toSql(null));
}
sql.setParam("year", year);
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.desc("tsuu.unitId");
cnd.desc("u.unitName").asc("u.loginname");
} else {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
}
@@ -75,6 +101,7 @@ public class TrainSignUpUserServiceImpl extends BaseServiceImpl<TrainSignUpBlack
@Override
@Aop(TransAop.READ_COMMITTED)
public void doHandleUser(String userId) {
TrainSignUpBlackList blackRecord = dao().fetch(TrainSignUpBlackList.class, Cnd.where("userId", "=", userId));
if (Lang.isEmpty(blackRecord)) {
@@ -82,14 +109,103 @@ public class TrainSignUpUserServiceImpl extends BaseServiceImpl<TrainSignUpBlack
blackList.setUserId(userId);
blackList.setIsDisabled(true);
dao().insert(blackList);
} else if (Boolean.TRUE.equals(blackRecord.getIsDisabled())) {
dao().update(TrainSignUpBlackList.class,
Chain.make("isDisabled", false).add("lastUnblockTime", new Date()),
Cnd.where("userId", "=", userId));
} else {
// blackRecord.setIsDisabled(!blackRecord.getIsDisabled());
// dao().update(blackRecord);
dao().delete(blackRecord);
dao().update(TrainSignUpBlackList.class,
Chain.make("isDisabled", true),
Cnd.where("userId", "=", userId));
}
}
@Override
public boolean isBlackListed(String userId) {
return dao().count(TrainSignUpBlackList.class, Cnd.where("userId", "=", userId)
.and("isDisabled", "=", true)) > 0;
}
@Override
@Aop(TransAop.READ_COMMITTED)
public boolean autoBlackByYearAndUser(Integer year, String userId) {
TrainSignUpBlackList blackRecord = dao().fetch(TrainSignUpBlackList.class, Cnd.where("userId", "=", userId));
if (blackRecord != null && Boolean.TRUE.equals(blackRecord.getIsDisabled())) {
return true;
}
Date yearStartTime = getYearStartTime(year);
Date countStartTime = yearStartTime;
if (blackRecord != null && blackRecord.getLastUnblockTime() != null
&& blackRecord.getLastUnblockTime().after(yearStartTime)) {
countStartTime = blackRecord.getLastUnblockTime();
}
// 自动拉黑按一次报名累计缺席:同一报名下多个签到时间段全部未签到,只计 1 次缺席。
Sql sql = Sqls.create("""
SELECT
count(1)
FROM
(
SELECT
su.userId,
su.activityId,
su.courseId,
max(case when uc.isAttend = true then 1 else 0 end) AS hasAttend,
max(uc.courseEndTime) AS lastCourseEndTime
FROM
train_sign_up_user su
LEFT JOIN train_sign_up_activity act ON act.id = su.activityId
LEFT JOIN train_sign_up_course course ON course.id = su.courseId
LEFT JOIN train_sign_up_user_course uc ON uc.activityId = su.activityId
AND uc.courseId = su.courseId
AND uc.userId = su.userId
WHERE
su.userId = @userId
AND act.year = @year
AND su.state != 2
AND course.isMobileSign = 1
GROUP BY
su.userId,
su.activityId,
su.courseId
) applyStats
WHERE
applyStats.hasAttend = 0
AND applyStats.lastCourseEndTime < now()
AND applyStats.lastCourseEndTime > @countStartTime
""");
sql.setParam("userId", userId);
sql.setParam("year", year);
sql.setParam("countStartTime", countStartTime);
if (count(sql) < 2) {
return false;
}
if (blackRecord == null) {
TrainSignUpBlackList blackList = new TrainSignUpBlackList();
blackList.setUserId(userId);
blackList.setIsDisabled(true);
dao().insert(blackList);
} else {
dao().update(TrainSignUpBlackList.class,
Chain.make("isDisabled", true),
Cnd.where("userId", "=", userId));
}
return true;
}
private Date getYearStartTime(Integer year) {
Calendar calendar = Calendar.getInstance();
calendar.clear();
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, Calendar.JANUARY);
calendar.set(Calendar.DAY_OF_MONTH, 1);
return calendar.getTime();
}
@Override
public List<NutMap> attendClassRecord(String userId) {
Sql sql = Sqls.create("""