Merge remote-tracking branch 'origin/main'

This commit is contained in:
2026-05-06 19:42:22 +08:00
38 changed files with 1237 additions and 245 deletions
@@ -112,7 +112,7 @@ public class ActivityCultureAuditActivityController {
cnd.andEX("tissue.unionId", "=", unionId);
cnd.andEX("tissue.activity_type", "=", activity_type);
if (approval) {
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.WITHDRAW.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
} else {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
}
@@ -9,6 +9,7 @@ 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.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.services.SysMsgService;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
@@ -81,17 +82,20 @@ public class ActivityWorksCollectionManageController {
@At
@SaCheckPermission("activity.workscollection.manage")
@Aop(TransAop.READ_COMMITTED)
public Result insert(@Param("data") @Valid Activity_works_collection worksCollection) {
worksCollection.setIsSubmit(true);
dao.insertWith(worksCollection, "subjectTypes");
worksCollection.getSubjectTypes().forEach(item -> {
dao.insertLinks(item, "worksTypes");
});
syncHomeActivity(worksCollection);
return Result.success();
}
@At
@SaCheckPermission("activity.workscollection.manage")
@Aop(TransAop.READ_COMMITTED)
public Result save(@Param("data") @Valid Activity_works_collection worksCollection) {
worksCollection.setIsSubmit(false);
dao.insertWith(worksCollection, "subjectTypes");
@@ -122,6 +126,7 @@ public class ActivityWorksCollectionManageController {
Cnd.where(Activity_works_worksType::getId, "not in ", workIds)
.and(Activity_works_worksType::getSubjectId,"=",item.getId()));
});
syncHomeActivity(worksCollection);
return Result.success();
}
@@ -132,6 +137,7 @@ public class ActivityWorksCollectionManageController {
dao.delete(Activity_works_collection.class, id);
dao.clear(Activity_works_collection_upload.class, Cnd.where(Activity_works_collection_upload::getActivityId, "=", id));
dao.clear(Activity_works_subjectType.class, Cnd.where(Activity_works_subjectType::getActivityId, "=", id));
dao.delete(Sys_home_activity.class, id);
return Result.success();
}
@@ -148,11 +154,26 @@ public class ActivityWorksCollectionManageController {
@At
@SaCheckPermission("activity.workscollection.manage")
@Aop(TransAop.READ_COMMITTED)
public Result enableChange(@Valid String id, @Valid Boolean enable) {
dao.update(Activity_works_collection.class, Chain.make("enable", enable), Cnd.where("id", "=", id));
Activity_works_collection worksCollection = dao.fetch(Activity_works_collection.class, id);
worksCollection.setEnable(enable);
syncHomeActivity(worksCollection);
return Result.success();
}
/**
* 同步作品征集到首页活动表:开启的活动写入首页,关闭的活动移除首页入口。
*/
private void syncHomeActivity(Activity_works_collection worksCollection) {
if (Boolean.TRUE.equals(worksCollection.getEnable())) {
dao.insertOrUpdate(worksCollection.covertToSysHomeActivity());
} else {
dao.delete(Sys_home_activity.class, worksCollection.getId());
}
}
@At
@SaCheckPermission("activity.workscollection.manage")
public Result sendNotice(@Valid String id) {
@@ -11,6 +11,7 @@ import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
import com.budwk.app.zhgh.activity.workscollection.models.Activity_works_collection;
import com.budwk.app.zhgh.activity.workscollection.models.Activity_works_collection_upload;
import io.swagger.annotations.ApiOperation;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.FieldFilter;
@@ -18,6 +19,7 @@ import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.Daos;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
@@ -94,11 +96,17 @@ public class ActivityWorksCollectionUploadController {
*/
@At
@SaCheckPermission("activity.workscollection.upload")
@Aop(TransAop.READ_COMMITTED)
public Result insert(@Param("data") @Valid Activity_works_collection_upload upload) {
String activityId = upload.getActivityId();
Activity_works_collection activity = dao.fetch(Activity_works_collection.class, activityId);
if (activity.getEndDateTime().getTime() < System.currentTimeMillis()){
long now = System.currentTimeMillis();
// 作品上传只允许在活动开始时间和结束时间之间提交,防止活动未开始或已结束后继续上传。
if (activity.getStartDateTime().getTime() > now) {
return Result.error("活动未开始,不能上传!");
}
if (activity.getEndDateTime().getTime() < now){
return Result.error("活动已结束!");
}
if (activity.getActivityGroupId() != null) {
@@ -124,9 +132,15 @@ public class ActivityWorksCollectionUploadController {
@At
@SaCheckPermission("activity.workscollection.upload")
@Aop(TransAop.READ_COMMITTED)
public Result update(@Param("data") @Valid Activity_works_collection_upload upload) {
Activity_works_collection activity = dao.fetch(Activity_works_collection.class, upload.getActivityId());
if (activity.getEndDateTime().getTime() < System.currentTimeMillis()){
long now = System.currentTimeMillis();
// 编辑作品同样按活动时间窗口控制,避免过期或未开始活动被继续提交作品信息。
if (activity.getStartDateTime().getTime() > now) {
return Result.error("活动未开始,不能上传!");
}
if (activity.getEndDateTime().getTime() < now){
return Result.error("活动已结束!");
}
dao.updateIgnoreNull(upload);
@@ -155,12 +169,19 @@ public class ActivityWorksCollectionUploadController {
@At
@SaCheckPermission("activity.workscollection.upload")
/**
* 查询当前登录人有权限上传作品的活动主题。
*
* @param year 活动年度,前端传年度选择框的 yyyy 值,按活动开始时间所在年份过滤。
* @return Resultdata 为活动列表,仅返回 id、name,且保留已开启和面向对象权限过滤。
*/
@ApiOperation("获取有权限的活动列表")
@Ok("json:{ignoreNull:true}")
public Result listPerMissionActivity() {
public Result listPerMissionActivity(Long year) {
Dao extDao = Daos.ext(dao, FieldFilter.create(Activity_works_collection.class, "id|name|"));
Cnd cnd = Cnd.NEW();
cnd.and(Activity_works_collection::getEnable,"=",1);
cnd.andEX("YEAR(startDateTime)", "=", year);
cnd.desc(Activity_works_collection::getStartDateTime);
List<Activity_works_collection> list = extDao.query(Activity_works_collection.class, cnd);
list = list.stream().filter(item -> {
@@ -1,6 +1,8 @@
package com.budwk.app.zhgh.activity.workscollection.models;
import com.budwk.app.base.model.BaseModel;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.services.SysHomeConvert;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
@@ -17,7 +19,7 @@ import java.util.List;
@Table("activity_works_collection")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("作品征集活动")
public class Activity_works_collection extends BaseModel {
public class Activity_works_collection extends BaseModel implements SysHomeConvert {
@Name
@Comment("ID")
@@ -123,4 +125,21 @@ public class Activity_works_collection extends BaseModel {
@Many(field = "activityId")
private List<Activity_works_collection_upload> uploads;
@Override
public Sys_home_activity covertToSysHomeActivity() {
Sys_home_activity sysHomeActivity = new Sys_home_activity();
sysHomeActivity.setId(this.getId());
sysHomeActivity.setName(this.getName());
sysHomeActivity.setCover(this.getCover());
sysHomeActivity.setContent(this.getContent());
sysHomeActivity.setUrl("/platform/activity/worksCollection/upload");
sysHomeActivity.setH5Url("/platform/activity/worksCollection/upload/h5");
sysHomeActivity.setStartDate(this.getStartDateTime());
sysHomeActivity.setEndDate(this.getEndDateTime());
sysHomeActivity.setAllowUserGroupId(this.getActivityGroupId());
sysHomeActivity.setEnable(Boolean.TRUE.equals(this.getEnable()));
sysHomeActivity.setClassPath(this.getClass().getPackageName() + this.getClass().getName());
return sysHomeActivity;
}
}
@@ -68,7 +68,7 @@ public class ClubUserJoinApplyController {
@Inject
private ClubUserJoinService clubUserJoinService;
@At("/")
@At("")
@SaCheckPermission("club.join.apply")
@Ok("beetl:/platform/zhgh/club/join/apply/index.html")
public void index() {
@@ -128,6 +128,12 @@ public class ClubUserJoinApplyController {
@SLog(tag = "社团管理系统-申请入/退会", msg = "申请社团入/退会")
public Result submit(@Param("data") ClubUserApply clubUserApply,
@Param("mode") Boolean mode) {
if (Boolean.TRUE.equals(mode)) {
String qualificationMsg = clubUserJoinService.checkJoinQualification(SecurityUtil.getUserId());
if (StrUtil.isNotBlank(qualificationMsg)) {
return Result.error(99, qualificationMsg);
}
}
if(mode == false) {
clubUserApply = clubUserJoinService.buildExitApply(clubUserApply.getClubId(), SecurityUtil.getUserId());
if (ObjectUtil.isEmpty(clubUserApply)) {
@@ -192,6 +198,10 @@ public class ClubUserJoinApplyController {
@ApiOperation("校验是否申请过社团")
@SaCheckPermission(value = {"club.join.apply", "h5.club.join.apply"}, mode = SaMode.OR)
public Result checkApplyClub(@Valid String clubId) {
String qualificationMsg = clubUserJoinService.checkJoinQualification(SecurityUtil.getUserId());
if (StrUtil.isNotBlank(qualificationMsg)) {
return Result.error(99, qualificationMsg);
}
View_user user = dao.fetch(View_user.class, Cnd.where("id", "=", SecurityUtil.getUserId()));
if (user.getMember() != 1) {
return Result.error(99, "抱歉,您不满足入会的条件!");
@@ -48,7 +48,7 @@ public class ClubUserJoinApprovalController {
@Inject
private ClubUserJoinService clubUserJoinService;
@At("/")
@At("")
@SaCheckPermission("club.join.clubApproval")
@Ok("beetl:/platform/zhgh/club/join/clubApproval/index.html")
public void index() {}
@@ -45,7 +45,7 @@ public class ClubUserJoinMineController {
@Inject
private ClubUserJoinService clubUserJoinService;
@At("/")
@At("")
@SaCheckPermission("club.join.mine")
@Ok("beetl:/platform/zhgh/club/join/mine/index.html")
public void index() {}
@@ -25,4 +25,12 @@ public interface ClubUserJoinService extends BaseService<ClubUserApply> {
* @return 入会时间
*/
java.util.Date resolveJoinTime(String clubId, String userId);
/**
* 校验当前用户是否满足申请加入社团的资格。
*
* @param userId 用户ID
* @return 校验不通过时返回提示语,通过时返回null
*/
String checkJoinQualification(String userId);
}
@@ -1,6 +1,9 @@
package com.budwk.app.zhgh.club.service.impl;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.zhgh.club.model.ClubUser;
import com.budwk.app.zhgh.club.model.ClubUserApply;
@@ -15,10 +18,37 @@ import java.util.List;
@IocBean(args = {"refer:dao"})
public class ClubUserJoinServiceImpl extends BaseServiceImpl<ClubUserApply> implements ClubUserJoinService {
private static final List<String> ALLOW_JOIN_PERSON_TYPES = List.of(
"教职工",
"未起薪人员",
"教职工新入会",
"教职工其他",
"劳动服务公司",
"后勤聘用人员",
"退休人员[一]",
"退休人员[二]",
"校聘劳务派遣全额"
);
public ClubUserJoinServiceImpl(Dao dao) {
super(dao);
}
@Override
public String checkJoinQualification(String userId) {
View_user user = dao().fetch(View_user.class, Cnd.where("id", "=", userId));
if (user == null) {
return "抱歉,您不满足入会的条件!";
}
if (user.getMember() == null || user.getMember() != 1) {
return "抱歉,您不满足入会的条件!";
}
if (StrUtil.isBlank(user.getPersonType()) || !ALLOW_JOIN_PERSON_TYPES.contains(user.getPersonType())) {
return "抱歉,您不满足入会的条件!";
}
return null;
}
@Override
public ClubUserApply buildExitApply(String clubId, String userId) {
// 优先复用最近一次申请记录中的补充信息,避免用户重复填写基础资料。
@@ -275,7 +275,14 @@ public class SysClubServiceImpl extends BaseServiceImpl<SysClub> implements SysC
}
cnd.andEX("year(info.createTime)", "=", pageForm.getYear());
cnd.and("info.userId", "=", SecurityUtil.getUserId());
cnd.desc("createTime");
// 我的注册列表默认按社团编码升序展示,点击表头时按用户选择的方向排序。
if (StrUtil.isAllBlank(pageForm.getPageOrderName(), pageForm.getPageOrderBy())) {
cnd.asc("info.clubCode");
} else if ("clubCode".equals(pageForm.getPageOrderName())) {
cnd.orderBy("info.clubCode", PageUtil.getOrder(pageForm.getPageOrderBy()));
} else {
cnd.orderBy(pageForm.getPageOrderName(), PageUtil.getOrder(pageForm.getPageOrderBy()));
}
cnd.groupBy("info.id");
sql.setCondition(cnd);
return listPageVO(pageForm, sql, ClubRegisterPageVo.class);
@@ -1,6 +1,7 @@
package com.budwk.app.zhgh.dayofficework.message.strategy.impl;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.sms.SmsService;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.zhgh.dayofficework.message.enums.GlobalMessageChannel;
import com.budwk.app.zhgh.dayofficework.message.strategy.GlobalMessageSendStrategy;
@@ -21,6 +22,8 @@ public class SmsMessageSendStrategy implements GlobalMessageSendStrategy {
@Inject
private Dao dao;
@Inject
private SmsService smsService;
@Override
public GlobalMessageChannel getChannel() {
@@ -70,29 +73,24 @@ public class SmsMessageSendStrategy implements GlobalMessageSendStrategy {
}
/**
* 发送短信消息
* 发送短信消息
* 接收人由全局消息服务传入的用户ID或登录名查询得到,发送时使用用户工号对接通讯平台;
* 返回结果由 SmsService 内部解析并记录,当前策略只负责逐个用户触发发送并记录异常。
*/
private void sendSmsMessage(String title, String content, Integer type, List<Sys_user> users, JSONObject config) {
for (Sys_user user : users) {
try {
String mobile = user.getMobile();
if (mobile == null || mobile.trim().isEmpty()) {
log.warn("用户{}没有手机号,跳过短信发送", user.getUsername());
String loginName = user.getLoginname();
if (loginName == null || loginName.trim().isEmpty()) {
log.warn("用户{}没有号,跳过短信发送", user.getUsername());
continue;
}
// 构建短信内容
String smsContent = buildSmsContent(title, content, type);
// 执行发送
boolean success = true;
if (success) {
log.info("短信发送成功:用户{},手机号:{}", user.getUsername(), mobile);
} else {
log.warn("短信发送失败:用户{},手机号:{}", user.getUsername(), mobile);
}
smsService.sendSmsByAccount(loginName, smsContent);
log.info("短信发送完成:用户{},工号:{}", user.getUsername(), loginName);
} catch (Exception e) {
log.error("发送短信给用户{}时出错:{}", user.getUsername(), e.getMessage(), e);
}
@@ -0,0 +1,113 @@
package com.budwk.app.zhgh.dayofficework.message.strategy.impl;
import cn.hutool.json.JSONObject;
import com.budwk.app.base.sms.SmsService;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.zhgh.dayofficework.message.enums.GlobalMessageChannel;
import com.budwk.app.zhgh.dayofficework.message.strategy.GlobalMessageSendStrategy;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import java.util.List;
/**
* 微信发送策略
*/
@IocBean
@Slf4j
public class WechatMessageSendStrategy implements GlobalMessageSendStrategy {
@Inject
private Dao dao;
@Inject
private SmsService smsService;
@Override
public GlobalMessageChannel getChannel() {
return GlobalMessageChannel.WECHAT;
}
@Override
public boolean supportAsync() {
return true;
}
@Override
public void send(String title, String content, Integer type, List<String> receiverIds, JSONObject config) {
try {
log.info("开始发送微信消息,标题:{},接收人数量:{}", title, receiverIds.size());
// receiverIds 为 sys_user.id,按用户ID查询后使用工号对接通讯平台微信文本消息接口。
List<Sys_user> users = dao.query(Sys_user.class, Cnd.where(Sys_user::getId, "in", receiverIds));
sendWechatMessage(title, content, type, users, config);
log.info("微信消息发送完成,标题:{},接收人数量:{}", title, users.size());
} catch (Exception e) {
log.error("微信消息发送失败,标题:{},错误:{}", title, e.getMessage(), e);
throw new RuntimeException("微信消息发送失败:" + e.getMessage(), e);
}
}
@Override
public void sendByLoginName(String title, String content, Integer type, List<String> receiverLoginNames, JSONObject config) {
try {
log.info("开始发送微信消息(按登录名),标题:{},接收人数量:{}", title, receiverLoginNames.size());
// receiverLoginNames 为 sys_user.loginname,查询用户后仍使用工号作为通讯平台接收人标识。
List<Sys_user> users = dao.query(Sys_user.class, Cnd.where(Sys_user::getLoginname, "in", receiverLoginNames));
sendWechatMessage(title, content, type, users, config);
log.info("微信消息发送完成(按登录名),标题:{},接收人数量:{}", title, users.size());
} catch (Exception e) {
log.error("微信消息发送失败(按登录名),标题:{},错误:{}", title, e.getMessage(), e);
throw new RuntimeException("微信消息发送失败:" + e.getMessage(), e);
}
}
/**
* 发送微信文本消息。
* 接收人来自全局消息服务传入的用户ID或登录名,方法内部转换为 Sys_user 后取工号发送;
* SmsService 返回 MsgPlatformResponse,当前策略按单人发送记录日志,单个用户失败不影响其他接收人。
*/
private void sendWechatMessage(String title, String content, Integer type, List<Sys_user> users, JSONObject config) {
for (Sys_user user : users) {
try {
String loginName = user.getLoginname();
if (loginName == null || loginName.trim().isEmpty()) {
log.warn("用户{}没有工号,跳过微信发送", user.getUsername());
continue;
}
String wechatContent = buildWechatContent(title, content, type);
smsService.sendWechatTextByAccountOrMobile(loginName, wechatContent);
log.info("微信发送完成:用户{},工号:{}", user.getUsername(), loginName);
} catch (Exception e) {
log.error("发送微信给用户{}时出错:{}", user.getUsername(), e.getMessage(), e);
}
}
}
/**
* 构建微信文本内容。
* type=2 表示待办消息,其余类型按系统通知处理,内容格式保持与短信策略一致。
*/
private String buildWechatContent(String title, String content, Integer type) {
StringBuilder sb = new StringBuilder();
if (type != null && type == 2) {
sb.append("【待办通知】");
} else {
sb.append("【系统通知】");
}
sb.append(title);
if (content != null && !content.trim().isEmpty()) {
sb.append("").append(content);
}
return sb.toString();
}
}
@@ -113,22 +113,23 @@ public class UnionReimburseApplyController {
return Result.success();
}
// @At
// @ApiOperation("重新提交申请")
// @Aop(TransAop.READ_COMMITTED)
// @SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
// public Result submitAgain(@Param("data") UnionReimburse unionReimburse, @Param("taskId") Long taskId) {
// if (StrUtil.isBlank(unionReimburse.getId())) unionReimburse.setCreateTime(new Date());
//
// dao.insertOrUpdate(unionReimburse);
//
// Dict dict = Dict.create();
// dict.set(FlowConst.PROCESS_TASK_ID_KEY, taskId);
// dict.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.RE_APPLY.getCode());
// dict.set("userId", unionReimburse.getCondolenceUserId());
// flowCommonService.executeTask(dict);
// return Result.success();
// }
@At
@ApiOperation("重新提交申请")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission(value = {"unionReimburse.apply", "h5.unionReimburse.apply"}, mode = SaMode.OR)
public Result submitAgain(@Param("data") UnionReimburse unionReimburse, @Param("taskId") Long taskId) {
// 退回重提也必须复用当前提交保存逻辑,避免绕过发票明细落库和提交校验。
Result result = unionReimburseService.saveApply(unionReimburse, 2);
if (result.getCode() != 0) {
return result;
}
Dict dict = Dict.create();
dict.set(FlowConst.PROCESS_TASK_ID_KEY, taskId);
dict.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.RE_APPLY.getCode());
flowCommonService.executeTask(dict);
return Result.success();
}
@At
@SaCheckLogin
@@ -120,6 +120,7 @@ public class ProposalSecondedController {
Cnd cnd = Cnd.NEW();
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
cnd.and("info.createUserId", "!=", SecurityUtil.getUserId());
if (approval) {
// 已附议页签只展示当前登录附议人已经处理过的记录。
@@ -179,7 +179,7 @@ public class TeacherCongressDelegatePushController {
""");
sql.setParam("sessionId", sessionId);
Cnd cnd = Cnd.NEW();
// cnd.and("u.member", "=", 1);
cnd.and("u.member", "=", 1);
cnd.and("u.username", "is not", null);
cnd.andEX("u.id", "not in", userIds);
cnd.and("tcd.userId", "is", null);