wuzhen更新社团管理新需求

This commit is contained in:
“wuzhen” committed 2026-05-22 11:17:26 +08:00
1 parent 52d892337e
commit 0745d287f5
32 files changed
+1638 -76

No files matched your search

@@ -98,4 +98,13 @@ public class StglClassController extends BaseController {
List<StglClassVo> list = stglClassService.queryList(bo);
ExcelUtil.exportExcel(list, "班级学生参与情况", StglClassVo.class, response);
}
/**
* 查询班主任所辖班级中当前正在参加活动的学生列表
*/
//@SaCheckPermission("stgl:stclass:list")
@GetMapping("/current-activities")
public TableDataInfo<Map<String, Object>> currentActivities(StglClassBo bo, PageQuery pageQuery) {
return stglClassService.getCurrentActiveStudents(bo, pageQuery);
}
}
@@ -29,6 +29,9 @@ import org.dromara.stgl.domain.vo.StglMemberVo;
import org.dromara.stgl.domain.vo.StglMemberImportVo;
import org.dromara.stgl.domain.bo.StglMemberBo;
import org.dromara.stgl.service.IStglMemberService;
import org.dromara.stgl.service.IStglMemberApprovalService;
import org.dromara.stgl.domain.bo.StglMemberApprovalBo;
import org.dromara.stgl.domain.vo.StglMemberApprovalVo;
import org.dromara.stgl.listener.StglMemberImportListener;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import io.swagger.v3.oas.annotations.Operation;
@@ -51,6 +54,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
public class StglMemberController extends BaseController {
private final IStglMemberService stglMemberService;
private final IStglMemberApprovalService memberApprovalService;
/**
* 查询社团成员列表
@@ -232,4 +236,44 @@ public class StglMemberController extends BaseController {
ExcelUtil.exportExcel(templateData, "社团成员导入模板", StglMemberImportVo.class, response);
}
/**
* 社长审批成员申请
*/
@Operation(summary = "社长审批成员申请")
@Log(title = "社长审批", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/presidentApprove")
public R<Void> presidentApprove(@RequestBody StglMemberApprovalBo bo) {
return toAjax(memberApprovalService.presidentApprove(bo.getMemberId(), bo.getApproved(), bo.getOpinion()));
}
/**
* 指导老师审批成员申请
*/
@Operation(summary = "指导老师审批成员申请")
@Log(title = "指导老师审批", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/advisorApprove")
public R<Void> advisorApprove(@RequestBody StglMemberApprovalBo bo) {
return toAjax(memberApprovalService.advisorApprove(bo.getMemberId(), bo.getApproved(), bo.getOpinion()));
}
/**
* 获取成员申请的审批流程
*/
@Operation(summary = "获取成员申请的审批流程")
@GetMapping("/approvalFlow/{memberId}")
public R<List<StglMemberApprovalVo>> getApprovalFlow(@PathVariable Long memberId) {
return R.ok(memberApprovalService.getApprovalFlow(memberId));
}
/**
* 获取待当前用户审批的成员列表
*/
@Operation(summary = "获取待当前用户审批的成员列表")
@GetMapping("/pendingApprovals")
public R<List<StglMemberApprovalVo>> getPendingApprovals(@RequestParam(required = false) Long clubId) {
return R.ok(memberApprovalService.getPendingApprovals(clubId));
}
}
@@ -152,6 +152,11 @@ public class StglClub extends TenantEntity {
*/
private String advisorsJson;
/**
* 归属(部门ID)
*/
private Long affiliation;
/**
* 成员规模上限
*/
@@ -197,6 +202,21 @@ public class StglClub extends TenantEntity {
*/
private String sighOpen;
/**
* 社长用户ID
*/
private Long presidentUserId;
/**
* 物资保管员用户ID
*/
private Long materialKeeperUserId;
/**
* 物资保管员姓名
*/
private String materialKeeperName;
/**
* 备注
*/
@@ -0,0 +1,57 @@
package org.dromara.stgl.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.tenant.core.TenantEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.Date;
/**
* 成员申请审批流程对象 stgl_member_approval
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("stgl_member_approval")
public class StglMemberApproval extends TenantEntity {
private static final long serialVersionUID = 1L;
@TableId(value = "id")
private Long id;
/** 成员记录ID(关联stgl_member) */
private Long memberId;
/** 社团ID */
private Long clubId;
/** 节点代码(president_approve/advisor_approve) */
private String nodeCode;
/** 节点名称(社长审批/指导老师审批) */
private String nodeName;
/** 节点顺序(1.0, 2.0) */
private BigDecimal nodeOrder;
/** 审批人ID */
private Long approverId;
/** 审批人姓名 */
private String approverName;
/** 审批结果(通过/不通过) */
private String approveResult;
/** 审批状态(pending/approved/rejected) */
private String auditStatus;
/** 审批意见 */
private String auditOpinion;
/** 审批时间 */
private Date auditTime;
}
@@ -12,6 +12,7 @@ import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.stgl.domain.StglActivity;
import java.util.Date;
import java.util.List;
/**
* 活动信息业务对象 stgl_activity
@@ -229,4 +230,8 @@ public class StglActivityBo extends BaseEntity {
*/
private String remark;
}
/**
* 邀请成员ID列表(非数据库字段,用于创建报名记录)
*/
private List<Long> invitedMemberIds;
}
@@ -184,6 +184,11 @@ public class StglClubBo extends BaseEntity {
*/
private String advisorsJson;
/**
* 归属(部门ID)
*/
private Long affiliation;
/**
* 成员规模上限
*/
@@ -240,6 +245,22 @@ public class StglClubBo extends BaseEntity {
*/
private String remark;
/**
* 社长用户ID(创建时选择学生)
*/
private Long presidentUserId;
/**
* 物资保管员用户ID(创建时选择学生)
*/
private Long materialKeeperUserId;
/**
* 物资保管员姓名
*/
@Size(max = 50, message = "物资保管员姓名长度不能超过50个字符")
private String materialKeeperName;
/**
* 社团ID列表(用于数据权限控制)
*/
@@ -0,0 +1,22 @@
package org.dromara.stgl.domain.bo;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 成员审批请求对象
*/
@Data
public class StglMemberApprovalBo {
/** 成员记录ID */
@NotNull(message = "成员记录ID不能为空")
private Long memberId;
/** 是否通过 */
@NotNull(message = "审批结果不能为空")
private Boolean approved;
/** 审批意见 */
private String opinion;
}
@@ -178,6 +178,11 @@ public class StglClubVo implements Serializable {
*/
private String advisorsJson;
/**
* 归属(部门ID)
*/
private Long affiliation;
/**
* 成员规模上限
*/
@@ -232,6 +237,22 @@ public class StglClubVo implements Serializable {
@ExcelProperty(value = "开启报名")
private String sighOpen;
/**
* 社长用户ID
*/
private Long presidentUserId;
/**
* 物资保管员用户ID
*/
private Long materialKeeperUserId;
/**
* 物资保管员姓名
*/
@ExcelProperty(value = "物资保管员")
private String materialKeeperName;
/**
* 备注
*/
@@ -0,0 +1,35 @@
package org.dromara.stgl.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 成员申请审批流程视图对象
*/
@Data
public class StglMemberApprovalVo implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private Long memberId;
private Long clubId;
private String nodeCode;
private String nodeName;
private BigDecimal nodeOrder;
private Long approverId;
private String approverName;
private String approveResult;
private String auditStatus;
private String auditOpinion;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date auditTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}
@@ -62,4 +62,20 @@ public interface StglClassMapper extends BaseMapperPlus<StglClass, StglClassVo>
* @return 活动详情列表
*/
List<Map<String, Object>> selectStudentActivities(Long studentId);
/**
* 查询指定班级中当前正在参加活动的学生列表
*
* @param params 查询参数(classId, studentName, studentNo, offset, pageSize)
* @return 正在参加活动的学生列表
*/
List<Map<String, Object>> selectCurrentActiveStudents(Map<String, Object> params);
/**
* 查询指定班级中当前正在参加活动的学生总数
*
* @param params 查询参数(classId, studentName, studentNo)
* @return 学生总数
*/
Integer selectCurrentActiveStudentsCount(Map<String, Object> params);
}
@@ -0,0 +1,11 @@
package org.dromara.stgl.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.stgl.domain.StglMemberApproval;
import org.dromara.stgl.domain.vo.StglMemberApprovalVo;
/**
* 成员申请审批流程Mapper接口
*/
public interface StglMemberApprovalMapper extends BaseMapperPlus<StglMemberApproval, StglMemberApprovalVo> {
}
@@ -65,4 +65,13 @@ public interface IStglClassService {
* @return 活动详情列表
*/
List<Map<String, Object>> getStudentActivities(Long studentId);
/**
* 查询班主任所辖班级中当前正在参加活动的学生分页列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 分页结果
*/
TableDataInfo<Map<String, Object>> getCurrentActiveStudents(StglClassBo bo, PageQuery pageQuery);
}
@@ -0,0 +1,56 @@
package org.dromara.stgl.service;
import org.dromara.stgl.domain.vo.StglMemberApprovalVo;
import java.util.List;
/**
* 成员申请审批流程Service接口
*/
public interface IStglMemberApprovalService {
/**
* 为成员申请创建审批流程(社长审批 → 指导老师审批)
*
* @param memberId 成员记录ID
* @param clubId 社团ID
* @return 是否创建成功
*/
Boolean createApprovalFlow(Long memberId, Long clubId);
/**
* 获取成员申请的审批流程列表
*
* @param memberId 成员记录ID
* @return 审批节点列表
*/
List<StglMemberApprovalVo> getApprovalFlow(Long memberId);
/**
* 获取待当前用户审批的列表
*
* @param clubId 社团ID(可选)
* @return 待审批的成员列表
*/
List<StglMemberApprovalVo> getPendingApprovals(Long clubId);
/**
* 社长审批
*
* @param memberId 成员记录ID
* @param approved 是否通过
* @param opinion 审批意见
* @return 是否审批成功
*/
Boolean presidentApprove(Long memberId, Boolean approved, String opinion);
/**
* 指导老师审批
*
* @param memberId 成员记录ID
* @param approved 是否通过
* @param opinion 审批意见
* @return 是否审批成功
*/
Boolean advisorApprove(Long memberId, Boolean approved, String opinion);
}
@@ -47,10 +47,13 @@ import org.dromara.common.json.utils.JsonUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import org.dromara.stgl.domain.StglClub;
import org.dromara.stgl.domain.StglVolunteerTeam;
import org.dromara.stgl.domain.StglActivityRegistration;
import org.dromara.stgl.domain.StglMember;
import org.dromara.stgl.domain.vo.StglClubVo;
import org.dromara.stgl.domain.vo.StglVolunteerTeamVo;
import org.dromara.stgl.mapper.StglClubMapper;
import org.dromara.stgl.mapper.StglVolunteerTeamMapper;
import org.dromara.stgl.mapper.StglActivityRegistrationMapper;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ISysUserService;
@@ -76,6 +79,7 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
private final IStglActivityAuditService activityAuditService;
private final StglActivityAuditMapper activityAuditMapper;
private final IStglClubService clubService;
private final StglActivityRegistrationMapper registrationMapper;
/**
* 查询活动信息
@@ -283,8 +287,8 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
// 为指导老师分配角色
assignAdvisorRoles(add);
createInvitedRegistrations(add.getId(), bo.getInvitedMemberIds());
}
return flag;
}
@@ -311,8 +315,8 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
boolean updateResult = baseMapper.updateById(update) > 0;
if (updateResult) {
// 处理指导老师角色分配
handleAdvisorRoleAssignment(oldActivity, update);
createInvitedRegistrations(update.getId(), bo.getInvitedMemberIds());
// 监听状态变化:如果状态从0(草稿)变为8(已提交),自动创建审批记录
if (oldActivity != null &&
@@ -1018,4 +1022,37 @@ public class StglActivityServiceImpl extends ServiceImpl<StglActivityMapper, Stg
// 注意:submitActivityForApproval 和 withdrawActivityApproval 方法已废弃
// 功能已整合到 updateByBo 方法中,通过监听状态变化自动处理审批记录
}
private void createInvitedRegistrations(Long activityId, List<Long> invitedMemberIds) {
if (invitedMemberIds == null || invitedMemberIds.isEmpty() || activityId == null) {
return;
}
Date now = new Date();
for (Long userId : invitedMemberIds) {
LambdaQueryWrapper<StglActivityRegistration> existLqw = Wrappers.lambdaQuery();
existLqw.eq(StglActivityRegistration::getActivityId, activityId)
.eq(StglActivityRegistration::getUserId, userId);
if (registrationMapper.selectCount(existLqw) > 0) {
continue;
}
StglMember member = stglMemberMapper.selectOne(
Wrappers.<StglMember>lambdaQuery()
.eq(StglMember::getUserId, userId)
.eq(StglMember::getStatus, 2)
.last("LIMIT 1")
);
StglActivityRegistration reg = new StglActivityRegistration();
reg.setActivityId(activityId);
reg.setUserId(userId);
reg.setStudentNo(member != null ? member.getStudentNo() : "");
reg.setStudentName(member != null ? member.getStudentName() : "");
reg.setRegistrationType("1");
reg.setRegistrationTime(now);
reg.setApprovalStatus("1");
reg.setApprovalTime(now);
reg.setRemark("活动邀请自动报名");
registrationMapper.insert(reg);
}
log.info("活动邀请报名完成: activityId={}, 邀请人数={}", activityId, invitedMemberIds.size());
}
}
@@ -115,6 +115,23 @@ public class StglClassServiceImpl implements IStglClassService {
return baseMapper.selectStudentActivities(studentId);
}
@Override
public TableDataInfo<Map<String, Object>> getCurrentActiveStudents(StglClassBo bo, PageQuery pageQuery) {
Map<String, Object> params = buildQueryParams(bo);
params.put("offset", (pageQuery.getPageNum() - 1) * pageQuery.getPageSize());
params.put("pageSize", pageQuery.getPageSize());
List<Map<String, Object>> list = baseMapper.selectCurrentActiveStudents(params);
Map<String, Object> countParams = buildQueryParams(bo);
Integer total = baseMapper.selectCurrentActiveStudentsCount(countParams);
TableDataInfo<Map<String, Object>> result = new TableDataInfo<>();
result.setRows(list);
result.setTotal(total != null ? total : 0);
return result;
}
/**
* 构建查询参数
*
@@ -15,6 +15,7 @@ import org.dromara.stgl.domain.StglMember;
import org.dromara.stgl.mapper.StglClubMapper;
import org.dromara.stgl.mapper.StglMemberMapper;
import org.dromara.stgl.service.IStglClubRegisterService;
import org.dromara.stgl.service.IStglMemberApprovalService;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.stgl.domain.vo.StglClubVo;
import org.dromara.system.domain.SysUser;
@@ -40,6 +41,7 @@ public class StglClubRegisterServiceImpl implements IStglClubRegisterService {
private final StglMemberMapper memberMapper;
private final SysUserMapper sysUserMapper;
private final XgStudentMsgMapper xgStudentMsgMapper;
private final IStglMemberApprovalService memberApprovalService;
/**
* 分页查询社团报名大厅列表
@@ -247,10 +249,15 @@ public class StglClubRegisterServiceImpl implements IStglClubRegisterService {
member.setStudentName(studentName != null ? studentName : user.getNickName()); // 如果没有学生信息,使用用户昵称
// 申请时不设置roleType,等待审核通过后再设置
member.setJoinTime(new Date());
member.setStatus(0); // 待审核状态(stgl_member_status字典,新报名需审核)
member.setRemark(reason); // 将申请理由存储到备注字段
return memberMapper.insert(member) > 0;
member.setStatus(0); // 待审核状态
member.setRemark(reason);
boolean inserted = memberMapper.insert(member) > 0;
if (inserted) {
// 创建两级审批流程:社长审批 → 指导老师审批
memberApprovalService.createApprovalFlow(member.getId(), clubId);
}
return inserted;
}
/**
@@ -334,23 +334,162 @@ public class StglClubServiceImpl implements IStglClubService {
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(StglClubBo bo) {
Long currentUserId = LoginHelper.getUserId();
if (currentUserId == null) {
throw new RuntimeException("用户未登录");
}
StglClub add = MapstructUtils.convert(bo, StglClub.class);
// 根据社长用户ID从学生表查询并填充冗余字段
if (add.getPresidentUserId() != null) {
fillPresidentInfoFromStudent(add);
}
// 创建者自动设为首席指导老师
add.setCreateBy(currentUserId);
if (StringUtils.isBlank(add.getAdvisorName())) {
// 从用户表获取创建者(老师)信息填充指导老师字段
fillCreatorAsAdvisor(add, currentUserId);
}
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
// 自动创建社长和副社长的成员记录
createLeaderMemberRecords(add);
// 为指导老师分配角色
createMaterialKeeperMemberRecord(add);
assignAdvisorRoles(add);
// 为社长分配角色
assignPresidentRole(add);
// 创建指导老师历史记录
createAdvisorHistoryRecords(add);
}
return flag;
}
/**
* 根据社长用户ID从学生表查询信息,自动填充冗余字段
*/
private void fillPresidentInfoFromStudent(StglClub club) {
try {
LambdaQueryWrapper<XgStudentMsg> wrapper = Wrappers.lambdaQuery();
wrapper.eq(XgStudentMsg::getId, club.getPresidentUserId())
.eq(XgStudentMsg::getDeleted, 0)
.last("LIMIT 1");
XgStudentMsg student = studentMsgMapper.selectOne(wrapper);
if (student != null) {
club.setPresidentName(student.getName());
club.setPresidentStudentNo(student.getStudentNo());
club.setPresidentDept(student.getCollegeName());
club.setPresidentMajor(student.getMajorName());
club.setPresidentClass(student.getClassName());
club.setPresidentPhone(student.getPhone());
}
} catch (Exception e) {
log.error("根据用户ID查询社长学生信息失败: {}", e.getMessage());
}
}
/**
* 将创建者设为首席指导老师
*/
private void fillCreatorAsAdvisor(StglClub club, Long userId) {
try {
String nickName = LoginHelper.getLoginUser().getNickname();
club.setAdvisorName(nickName);
} catch (Exception e) {
log.warn("获取创建者老师信息失败: {}", e.getMessage());
}
}
/**
* 创建物资保管员成员记录
*/
private void createMaterialKeeperMemberRecord(StglClub club) {
if (club.getMaterialKeeperUserId() == null) {
return;
}
try {
LambdaQueryWrapper<XgStudentMsg> wrapper = Wrappers.lambdaQuery();
wrapper.eq(XgStudentMsg::getId, club.getMaterialKeeperUserId())
.eq(XgStudentMsg::getDeleted, 0)
.last("LIMIT 1");
XgStudentMsg student = studentMsgMapper.selectOne(wrapper);
if (student == null) {
log.warn("物资保管员学生信息不存在, userId={}", club.getMaterialKeeperUserId());
return;
}
StglMember keeper = new StglMember();
keeper.setMemberType(1);
keeper.setRelationId(club.getId());
keeper.setUserId(club.getMaterialKeeperUserId());
keeper.setStudentNo(student.getStudentNo());
keeper.setStudentName(student.getName());
keeper.setRoleType("物资保管员");
keeper.setJoinTime(new Date());
keeper.setStatus(2);
keeper.setRemark("社团创建时自动添加");
memberMapper.insert(keeper);
// 回填物资保管员姓名到社团表
if (StringUtils.isBlank(club.getMaterialKeeperName())) {
StglClub updateClub = new StglClub();
updateClub.setId(club.getId());
updateClub.setMaterialKeeperName(student.getName());
baseMapper.updateById(updateClub);
}
} catch (Exception e) {
log.error("创建物资保管员成员记录失败: {}", e.getMessage());
}
}
private void syncMaterialKeeperMemberRecord(StglClub newClub, StglClub oldClub) {
Long newKeeperId = newClub.getMaterialKeeperUserId();
Long oldKeeperId = oldClub != null ? oldClub.getMaterialKeeperUserId() : null;
if (java.util.Objects.equals(newKeeperId, oldKeeperId)) {
return;
}
// 删除旧的物资保管员成员记录
if (oldKeeperId != null) {
LambdaQueryWrapper<StglMember> delWrapper = Wrappers.lambdaQuery();
delWrapper.eq(StglMember::getRelationId, newClub.getId())
.eq(StglMember::getMemberType, 1)
.eq(StglMember::getRoleType, "物资保管员");
memberMapper.delete(delWrapper);
}
// 创建新的物资保管员成员记录
if (newKeeperId != null) {
LambdaQueryWrapper<XgStudentMsg> wrapper = Wrappers.lambdaQuery();
wrapper.eq(XgStudentMsg::getId, newKeeperId)
.eq(XgStudentMsg::getDeleted, 0)
.last("LIMIT 1");
XgStudentMsg student = studentMsgMapper.selectOne(wrapper);
if (student != null) {
StglMember keeper = new StglMember();
keeper.setMemberType(1);
keeper.setRelationId(newClub.getId());
keeper.setUserId(newKeeperId);
keeper.setStudentNo(student.getStudentNo());
keeper.setStudentName(student.getName());
keeper.setRoleType("物资保管员");
keeper.setJoinTime(new Date());
keeper.setStatus(2);
keeper.setRemark("修改社团信息时更新");
memberMapper.insert(keeper);
if (StringUtils.isBlank(newClub.getMaterialKeeperName())) {
StglClub updateName = new StglClub();
updateName.setId(newClub.getId());
updateName.setMaterialKeeperName(student.getName());
baseMapper.updateById(updateName);
}
}
}
}
/**
* 修改社团信息
*
@@ -375,6 +514,8 @@ public class StglClubServiceImpl implements IStglClubService {
if (updateResult) {
// 同步更新副社长的成员记录
syncVicePresidentsInMembers(update);
// 同步物资保管员成员记录
syncMaterialKeeperMemberRecord(update, oldClub);
// 处理指导老师角色分配
handleAdvisorRoleAssignment(oldClub, update);
// 更新指导老师历史记录
@@ -522,25 +663,35 @@ public class StglClubServiceImpl implements IStglClubService {
*/
private void createLeaderMemberRecords(StglClub club) {
Date now = new Date();
// 1. 创建社长成员记录
if (StringUtils.isNotBlank(club.getPresidentStudentNo())) {
// 1. 创建社长成员记录(优先用 presidentUserId)
if (club.getPresidentUserId() != null) {
StglMember president = new StglMember();
president.setMemberType(1); // 社团成员
president.setMemberType(1);
president.setRelationId(club.getId());
president.setUserId(club.getPresidentUserId());
president.setStudentNo(club.getPresidentStudentNo());
president.setStudentName(club.getPresidentName());
president.setRoleType("社长");
president.setJoinTime(now);
president.setStatus(2);
president.setRemark("社团创建时自动添加");
memberMapper.insert(president);
} else if (StringUtils.isNotBlank(club.getPresidentStudentNo())) {
// 兼容旧逻辑:通过学号查找
StglMember president = new StglMember();
president.setMemberType(1);
president.setRelationId(club.getId());
president.setStudentNo(club.getPresidentStudentNo());
president.setStudentName(club.getPresidentName());
president.setRoleType("社长"); // 社长
president.setRoleType("社长");
president.setJoinTime(now);
president.setStatus(2); // 已加入状态(stgl_member_status字典)
president.setStatus(2);
president.setRemark("社团创建时自动添加");
// 根据学号查找用户ID
Long presidentUserId = findUserIdByStudentNo(club.getPresidentStudentNo());
if (presidentUserId != null) {
president.setUserId(presidentUserId);
}
memberMapper.insert(president);
}
@@ -0,0 +1,262 @@
package org.dromara.stgl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.stgl.domain.StglClub;
import org.dromara.stgl.domain.StglMember;
import org.dromara.stgl.domain.StglMemberApproval;
import org.dromara.stgl.domain.vo.StglMemberApprovalVo;
import org.dromara.stgl.mapper.StglClubMapper;
import org.dromara.stgl.mapper.StglMemberApprovalMapper;
import org.dromara.stgl.mapper.StglMemberMapper;
import org.dromara.stgl.service.IStglMemberApprovalService;
import com.fasterxml.jackson.core.type.TypeReference;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.*;
/**
* 成员申请审批流程Service业务层处理
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class StglMemberApprovalServiceImpl implements IStglMemberApprovalService {
private final StglMemberApprovalMapper approvalMapper;
private final StglMemberMapper memberMapper;
private final StglClubMapper clubMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean createApprovalFlow(Long memberId, Long clubId) {
StglClub club = clubMapper.selectById(clubId);
if (club == null) {
throw new RuntimeException("社团不存在");
}
// 获取社长信息(审批节点1)
Long presidentUserId = club.getPresidentUserId();
String presidentName = club.getPresidentName();
if (presidentUserId == null) {
// 兼容旧数据:从 stgl_member 表查找社长
LambdaQueryWrapper<StglMember> lqw = Wrappers.lambdaQuery();
lqw.eq(StglMember::getMemberType, 1)
.eq(StglMember::getRelationId, clubId)
.eq(StglMember::getRoleType, "社长")
.eq(StglMember::getStatus, 2)
.last("LIMIT 1");
StglMember president = memberMapper.selectOne(lqw);
if (president != null) {
presidentUserId = president.getUserId();
presidentName = president.getStudentName();
}
}
if (presidentUserId == null) {
throw new RuntimeException("社团暂无社长,无法提交申请");
}
// 获取指导老师信息(审批节点2)- 从 advisors_json 中获取首席指导老师
Long advisorUserId = null;
String advisorName = club.getAdvisorName();
String advisorsJson = club.getAdvisorsJson();
if (StringUtils.isNotBlank(advisorsJson)) {
try {
List<Map<String, Object>> advisors = JsonUtils.parseObject(advisorsJson, new TypeReference<List<Map<String, Object>>>() {});
if (advisors != null && !advisors.isEmpty()) {
Map<String, Object> first = advisors.get(0);
Object uid = first.get("userId");
if (uid != null) {
advisorUserId = Long.parseLong(uid.toString());
}
if (first.get("teacher") instanceof Map) {
Map<?, ?> teacher = (Map<?, ?>) first.get("teacher");
if (teacher.get("nickName") != null) {
advisorName = teacher.get("nickName").toString();
}
}
}
} catch (Exception e) {
log.warn("解析advisors_json失败: {}", e.getMessage());
}
}
if (advisorUserId == null) {
advisorUserId = club.getCreateBy();
}
if (advisorUserId == null) {
throw new RuntimeException("社团暂无指导老师,无法提交申请");
}
Date now = new Date();
Long currentUserId = LoginHelper.getUserId();
// 节点1:社长审批
StglMemberApproval presidentNode = new StglMemberApproval();
presidentNode.setMemberId(memberId);
presidentNode.setClubId(clubId);
presidentNode.setNodeCode("president_approve");
presidentNode.setNodeName("社长审批");
presidentNode.setNodeOrder(new BigDecimal("1.0"));
presidentNode.setApproverId(presidentUserId);
presidentNode.setApproverName(presidentName != null ? presidentName : "");
presidentNode.setAuditStatus("pending");
approvalMapper.insert(presidentNode);
// 节点2:指导老师审批
StglMemberApproval advisorNode = new StglMemberApproval();
advisorNode.setMemberId(memberId);
advisorNode.setClubId(clubId);
advisorNode.setNodeCode("advisor_approve");
advisorNode.setNodeName("指导老师审批");
advisorNode.setNodeOrder(new BigDecimal("2.0"));
advisorNode.setApproverId(advisorUserId);
advisorNode.setApproverName(advisorName != null ? advisorName : "");
advisorNode.setAuditStatus("pending");
approvalMapper.insert(advisorNode);
log.info("创建成员审批流程: memberId={}, clubId={}, 社长={}, 指导老师={}",
memberId, clubId, presidentUserId, advisorUserId);
return true;
}
@Override
public List<StglMemberApprovalVo> getApprovalFlow(Long memberId) {
LambdaQueryWrapper<StglMemberApproval> lqw = Wrappers.lambdaQuery();
lqw.eq(StglMemberApproval::getMemberId, memberId)
.orderByAsc(StglMemberApproval::getNodeOrder);
return approvalMapper.selectVoList(lqw);
}
@Override
public List<StglMemberApprovalVo> getPendingApprovals(Long clubId) {
Long currentUserId = LoginHelper.getUserId();
LambdaQueryWrapper<StglMemberApproval> lqw = Wrappers.lambdaQuery();
lqw.eq(StglMemberApproval::getApproverId, currentUserId)
.eq(StglMemberApproval::getAuditStatus, "pending");
if (clubId != null) {
lqw.eq(StglMemberApproval::getClubId, clubId);
}
lqw.orderByAsc(StglMemberApproval::getNodeOrder);
return approvalMapper.selectVoList(lqw);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean presidentApprove(Long memberId, Boolean approved, String opinion) {
Boolean result = doApprove(memberId, "president_approve", approved, opinion);
if (approved) {
StglMember member = memberMapper.selectById(memberId);
if (member != null) {
StglMember updateMember = new StglMember();
updateMember.setId(memberId);
updateMember.setStatus(1); // 审核中,等待指导老师审批
memberMapper.updateById(updateMember);
log.info("社长审批通过,进入指导老师审批阶段: memberId={}", memberId);
}
}
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean advisorApprove(Long memberId, Boolean approved, String opinion) {
Boolean result = doApprove(memberId, "advisor_approve", approved, opinion);
// 指导老师审批是最后一个节点,如果通过则更新成员状态为已加入
if (approved) {
StglMember member = memberMapper.selectById(memberId);
if (member != null) {
StglMember updateMember = new StglMember();
updateMember.setId(memberId);
updateMember.setStatus(2); // 已加入
updateMember.setRoleType("普通成员");
memberMapper.updateById(updateMember);
log.info("成员申请全部审批通过,已加入社团: memberId={}", memberId);
}
}
return result;
}
/**
* 通用审批处理逻辑
*/
private Boolean doApprove(Long memberId, String nodeCode, Boolean approved, String opinion) {
Long currentUserId = LoginHelper.getUserId();
if (currentUserId == null) {
throw new RuntimeException("用户未登录");
}
// 查找待审批的节点
LambdaQueryWrapper<StglMemberApproval> lqw = Wrappers.lambdaQuery();
lqw.eq(StglMemberApproval::getMemberId, memberId)
.eq(StglMemberApproval::getNodeCode, nodeCode)
.eq(StglMemberApproval::getAuditStatus, "pending");
StglMemberApproval approvalNode = approvalMapper.selectOne(lqw);
if (approvalNode == null) {
throw new RuntimeException("未找到待审批记录或已审批过");
}
// 校验审批人身份
log.info("审批人校验: approverId={}, currentUserId={}, equals={}",
approvalNode.getApproverId(), currentUserId,
approvalNode.getApproverId().equals(currentUserId));
log.info("审批人校验类型: approverId.class={}, currentUserId.class={}",
approvalNode.getApproverId().getClass().getName(),
currentUserId.getClass().getName());
if (!approvalNode.getApproverId().equals(currentUserId)) {
throw new RuntimeException("您不是该节点的审批人(节点审批人:" + approvalNode.getApproverId() + ", 当前用户:" + currentUserId + ")");
}
// 更新审批记录
StglMemberApproval updateNode = new StglMemberApproval();
updateNode.setId(approvalNode.getId());
updateNode.setApproveResult(approved ? "通过" : "不通过");
updateNode.setAuditStatus(approved ? "approved" : "rejected");
updateNode.setAuditOpinion(opinion);
updateNode.setAuditTime(new Date());
approvalMapper.updateById(updateNode);
// 如果拒绝,更新成员状态为已拒绝,并标记后续节点也结束
if (!approved) {
StglMember member = memberMapper.selectById(memberId);
if (member != null) {
StglMember updateMember = new StglMember();
updateMember.setId(memberId);
updateMember.setStatus(3); // 已拒绝
updateMember.setRemark(opinion != null ? "审批拒绝: " + opinion : "审批拒绝");
memberMapper.updateById(updateMember);
}
// 将后续待审批节点也标记为结束
LambdaQueryWrapper<StglMemberApproval> pendingLqw = Wrappers.lambdaQuery();
pendingLqw.eq(StglMemberApproval::getMemberId, memberId)
.eq(StglMemberApproval::getAuditStatus, "pending")
.ne(StglMemberApproval::getId, approvalNode.getId());
List<StglMemberApproval> pendingNodes = approvalMapper.selectList(pendingLqw);
for (StglMemberApproval pending : pendingNodes) {
StglMemberApproval cancelNode = new StglMemberApproval();
cancelNode.setId(pending.getId());
cancelNode.setAuditStatus("rejected");
cancelNode.setApproveResult("前置节点拒绝,自动终止");
cancelNode.setAuditTime(new Date());
approvalMapper.updateById(cancelNode);
}
log.info("成员申请被拒绝: memberId={}, nodeCode={}, 拒绝人={}", memberId, nodeCode, currentUserId);
} else {
log.info("成员申请节点审批通过: memberId={}, nodeCode={}, 审批人={}", memberId, nodeCode, currentUserId);
}
return true;
}
}
@@ -16,6 +16,7 @@ import org.dromara.stgl.domain.vo.StglMemberVo;
import org.dromara.stgl.domain.StglMember;
import org.dromara.stgl.mapper.StglMemberMapper;
import org.dromara.stgl.service.IStglMemberService;
import org.dromara.stgl.service.IStglMemberApprovalService;
import org.dromara.system.service.ISysUserService;
import cn.hutool.core.collection.CollUtil;
import org.dromara.common.satoken.utils.LoginHelper;
@@ -44,6 +45,7 @@ public class StglMemberServiceImpl implements IStglMemberService {
private final StglMemberMapper baseMapper;
private final ISysUserService userService;
private final IStglMemberApprovalService memberApprovalService;
/**
* 查询社团成员
@@ -381,28 +383,25 @@ public class StglMemberServiceImpl implements IStglMemberService {
if (member == null) {
throw new RuntimeException("成员记录不存在");
}
if (member.getStatus() != 0) {
if (member.getStatus() != 0 && member.getStatus() != 1) {
throw new RuntimeException("该申请已审核过,无法重复审核");
}
// 更新成员状态和角色
StglMember updateMember = new StglMember();
updateMember.setId(id);
if (approved) {
updateMember.setStatus(2); // 已加入状态(stgl_member_status字典)
// 根据成员类型设置不同的角色
if (member.getMemberType() == 1) {
updateMember.setRoleType("普通成员"); // 社团成员
} else if (member.getMemberType() == 2) {
updateMember.setRoleType("队员"); // 志愿者队伍成员
Long currentUserId = LoginHelper.getUserId();
java.util.Set<String> roleKeys = LoginHelper.getLoginUser().getRolePermission();
if (roleKeys.contains("stgl_instructor")) {
if (member.getStatus() == 0) {
throw new RuntimeException("该申请需等待社长先审批,请稍后再试");
}
return memberApprovalService.advisorApprove(id, approved, null);
} else {
updateMember.setStatus(3); // 已驳回状态(stgl_member_status字典)
updateMember.setRoleType(null); // 拒绝时清空角色
if (member.getStatus() == 1) {
throw new RuntimeException("社长已审批通过,等待指导老师审批中");
}
return memberApprovalService.presidentApprove(id, approved, null);
}
return baseMapper.updateById(updateMember) > 0;
}
/**
@@ -31,6 +31,7 @@
<result property="advisorEmail" column="advisor_email"/>
<result property="vicePresidentsJson" column="vice_presidents_json"/>
<result property="advisorsJson" column="advisors_json"/>
<result property="affiliation" column="affiliation"/>
<result property="memberLimit" column="member_limit"/>
<result property="departments" column="departments"/>
<result property="recruitCycle" column="recruit_cycle"/>
@@ -56,6 +57,7 @@
c.vice_presidents_json,
c.advisor_name, c.advisor_dept, c.advisor_phone, c.advisor_email,
c.advisors_json,
c.affiliation,
c.member_limit, c.departments, c.recruit_cycle, c.recruit_time, c.activity_frequency, c.funding_sources,
c.charter_url, c.status, c.sigh_open, c.remark, c.create_by, c.create_time, c.update_by, c.update_time, c.tenant_id, c.deleted,
COALESCE(m.member_count, 0) as memberCount
@@ -76,6 +78,7 @@
c.president_dept, c.president_major, c.president_class, c.president_phone,
c.vice_president_name, c.vice_president_student_no, c.vice_president_dept, c.vice_president_major, c.vice_president_class, c.vice_president_phone,
c.advisor_name, c.advisor_dept, c.advisor_phone, c.advisor_email,
c.affiliation,
c.member_limit, c.departments, c.recruit_cycle, c.recruit_time,
c.activity_frequency, c.funding_sources, c.charter_url, c.status, c.sigh_open, c.remark,
c.create_by, c.create_time, c.update_by, c.update_time,
@@ -98,6 +101,7 @@
c.president_dept, c.president_major, c.president_class, c.president_phone,
c.vice_president_name, c.vice_president_student_no, c.vice_president_dept, c.vice_president_major, c.vice_president_class, c.vice_president_phone,
c.advisor_name, c.advisor_dept, c.advisor_phone, c.advisor_email,
c.affiliation,
c.member_limit, c.departments, c.recruit_cycle, c.recruit_time,
c.activity_frequency, c.funding_sources, c.charter_url, c.status, c.sigh_open, c.remark,
c.create_by, c.create_time, c.update_by, c.update_time,
@@ -129,6 +133,7 @@
c.vice_presidents_json,
c.advisor_name, c.advisor_dept, c.advisor_phone, c.advisor_email,
c.advisors_json,
c.affiliation,
c.member_limit, c.departments, c.recruit_cycle, c.recruit_time,
c.activity_frequency, c.funding_sources, c.charter_url, c.status, c.sigh_open, c.remark,
c.create_by, c.create_time, c.update_by, c.update_time,
@@ -445,4 +445,132 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
ORDER BY a.start_time DESC, a.create_time DESC
</select>
<!-- 查询指定班级中当前正在参加活动的学生列表(个人报名 + 团队报名) -->
<select id="selectCurrentActiveStudents" parameterType="java.util.Map" resultType="java.util.Map">
SELECT
s.id,
s.student_no AS studentNo,
s.name AS studentName,
s.gender,
s.phone,
c.class_name AS className,
a.id AS activityId,
a.activity_name AS activityName,
a.start_time AS startTime,
a.end_time AS endTime,
a.location,
CASE
WHEN a.related_type = 1 THEN club.club_name
WHEN a.related_type = 2 THEN vt.team_name
ELSE NULL
END AS organizationName
FROM xg_student_msg s
LEFT JOIN xg_class_msg c ON s.class_id = c.id AND c.tenant_id = '000000'
INNER JOIN (
SELECT ar.user_id, ar.activity_id FROM stgl_activity_registration ar
INNER JOIN stgl_activity a ON ar.activity_id = a.id AND a.deleted = 0 AND a.tenant_id = '000000'
WHERE ar.deleted = 0 AND ar.approval_status = 1 AND ar.tenant_id = '000000'
AND NOW() BETWEEN a.start_time AND a.end_time
UNION
SELECT
CAST(JSON_UNQUOTE(JSON_EXTRACT(ati.team_members, CONCAT('$[', numbers.n, '].userId'))) AS UNSIGNED) AS user_id,
ati.activity_id
FROM stgl_activity_team_info ati
CROSS JOIN (
SELECT 0 AS n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION
SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION
SELECT 10 UNION SELECT 11 UNION SELECT 12 UNION SELECT 13 UNION SELECT 14 UNION
SELECT 15 UNION SELECT 16 UNION SELECT 17 UNION SELECT 18 UNION SELECT 19
) numbers
INNER JOIN stgl_activity a ON ati.activity_id = a.id AND a.deleted = 0 AND a.tenant_id = '000000'
WHERE ati.deleted = 0 AND ati.tenant_id = '000000'
AND NOW() BETWEEN a.start_time AND a.end_time
AND JSON_UNQUOTE(JSON_EXTRACT(ati.team_members, CONCAT('$[', numbers.n, '].userId'))) IS NOT NULL
AND JSON_UNQUOTE(JSON_EXTRACT(ati.team_members, CONCAT('$[', numbers.n, '].userId'))) != ''
AND EXISTS (
SELECT 1 FROM stgl_activity_registration team_ar
WHERE team_ar.team_id = ati.id
AND team_ar.deleted = 0
AND team_ar.approval_status = 1
AND team_ar.registration_type = 2
AND team_ar.tenant_id = '000000'
)
) active ON s.id = active.user_id
INNER JOIN stgl_activity a ON active.activity_id = a.id AND a.deleted = 0 AND a.tenant_id = '000000'
LEFT JOIN stgl_club club ON a.related_type = 1 AND a.related_club_id = club.id AND club.deleted = 0 AND club.tenant_id = '000000'
LEFT JOIN stgl_volunteer_team vt ON a.related_type = 2 AND a.related_club_id = vt.id AND vt.deleted = 0 AND vt.tenant_id = '000000'
<where>
s.deleted = 0
AND s.student_status = '1'
AND s.tenant_id = '000000'
<if test="classId != null and classId != ''">
AND s.class_id = #{classId}
</if>
<if test="studentName != null and studentName != ''">
AND s.name LIKE CONCAT('%', #{studentName}, '%')
</if>
<if test="studentNo != null and studentNo != ''">
AND s.student_no LIKE CONCAT('%', #{studentNo}, '%')
</if>
</where>
ORDER BY a.start_time, s.student_no
<if test="offset != null and pageSize != null">
LIMIT #{pageSize} OFFSET #{offset}
</if>
</select>
<!-- 查询指定班级中当前正在参加活动的学生总数 -->
<select id="selectCurrentActiveStudentsCount" parameterType="java.util.Map" resultType="java.lang.Integer">
SELECT COUNT(*)
FROM xg_student_msg s
INNER JOIN (
SELECT ar.user_id, ar.activity_id FROM stgl_activity_registration ar
INNER JOIN stgl_activity a ON ar.activity_id = a.id AND a.deleted = 0 AND a.tenant_id = '000000'
WHERE ar.deleted = 0 AND ar.approval_status = 1 AND ar.tenant_id = '000000'
AND NOW() BETWEEN a.start_time AND a.end_time
UNION
SELECT
CAST(JSON_UNQUOTE(JSON_EXTRACT(ati.team_members, CONCAT('$[', numbers.n, '].userId'))) AS UNSIGNED) AS user_id,
ati.activity_id
FROM stgl_activity_team_info ati
CROSS JOIN (
SELECT 0 AS n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION
SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION
SELECT 10 UNION SELECT 11 UNION SELECT 12 UNION SELECT 13 UNION SELECT 14 UNION
SELECT 15 UNION SELECT 16 UNION SELECT 17 UNION SELECT 18 UNION SELECT 19
) numbers
INNER JOIN stgl_activity a ON ati.activity_id = a.id AND a.deleted = 0 AND a.tenant_id = '000000'
WHERE ati.deleted = 0 AND ati.tenant_id = '000000'
AND NOW() BETWEEN a.start_time AND a.end_time
AND JSON_UNQUOTE(JSON_EXTRACT(ati.team_members, CONCAT('$[', numbers.n, '].userId'))) IS NOT NULL
AND JSON_UNQUOTE(JSON_EXTRACT(ati.team_members, CONCAT('$[', numbers.n, '].userId'))) != ''
AND EXISTS (
SELECT 1 FROM stgl_activity_registration team_ar
WHERE team_ar.team_id = ati.id
AND team_ar.deleted = 0
AND team_ar.approval_status = 1
AND team_ar.registration_type = 2
AND team_ar.tenant_id = '000000'
)
) active ON s.id = active.user_id
<where>
s.deleted = 0
AND s.student_status = '1'
AND s.tenant_id = '000000'
<if test="classId != null and classId != ''">
AND s.class_id = #{classId}
</if>
<if test="studentName != null and studentName != ''">
AND s.name LIKE CONCAT('%', #{studentName}, '%')
</if>
<if test="studentNo != null and studentNo != ''">
AND s.student_no LIKE CONCAT('%', #{studentNo}, '%')
</if>
</where>
</select>
</mapper>