# 会员管理模块 - 后端详细设计 --- | 文档信息 | 内容 | |---------|------| | **文档版本** | V1.0 | | **模块名称** | 会员管理模块 - 后端 | | **编写团队** | pangu | | **创建日期** | 2026-01-31 | --- ## 1. 设计概述 ### 1.1 技术选型 | 技术 | 版本 | 说明 | |-----|------|------| | Spring Boot | 3.3.x | 应用框架(LTS版本) | | Spring Security | 6.x | 安全框架 | | MyBatis Plus | 3.5.x | ORM框架 | | JWT | 0.12.x | Token认证 | | Hutool | 5.x | 工具库 | | Lombok | - | 简化代码 | | JDK | 17+ | 运行环境(LTS) | | MySQL | 8.0+ | 数据库 | | Redis | 7.x | 缓存 | ### 1.2 模块结构 ``` pangu-admin/ └── src/main/java/com/pangu/ └── member/ ├── controller/ │ └── MemberController.java # 会员管理控制器 ├── service/ │ ├── IMemberService.java # 会员服务接口 │ └── impl/ │ └── MemberServiceImpl.java # 会员服务实现 ├── mapper/ │ └── MemberMapper.java # 会员数据访问 ├── domain/ │ ├── Member.java # 会员实体 │ ├── MemberVO.java # 会员视图对象 │ └── MemberDTO.java # 会员数据传输对象 └── enums/ ├── IdentityTypeEnum.java # 身份类型枚举 └── RegisterSourceEnum.java # 注册来源枚举 pangu-admin/ └── src/main/resources/ └── mapper/ └── member/ └── MemberMapper.xml # Mapper映射文件 ``` --- ## 2. 实体设计 ### 2.1 会员实体(Member.java) ```java package com.pangu.member.domain; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import lombok.EqualsAndHashCode; import com.pangu.common.core.domain.BaseEntity; import java.time.LocalDate; import java.time.LocalDateTime; /** * 会员实体 * @author 湖北新华业务中台研发团队 */ @Data @EqualsAndHashCode(callSuper = true) @TableName("pg_member") public class Member extends BaseEntity { private static final long serialVersionUID = 1L; /** 会员ID */ @TableId(type = IdType.AUTO) private Long memberId; /** 会员编号 */ private String memberCode; /** 手机号 */ private String phone; /** 密码 */ private String password; /** 昵称 */ private String nickname; /** 头像URL */ private String avatar; /** 性别(0未知 1男 2女) */ private String gender; /** 出生日期 */ private LocalDate birthday; /** 身份类型(1家长 2教师) */ private String identityType; /** 微信OpenID */ private String openId; /** 微信UnionID */ private String unionId; /** 所属区域ID(教师必填) */ private Long regionId; /** 所属学校ID(教师必填) */ private Long schoolId; /** 所属学校年级ID(教师必填) */ private Long schoolGradeId; /** 所属学校班级ID(教师必填) */ private Long schoolClassId; /** 注册来源(1小程序 2H5 3后台 4导入) */ private String registerSource; /** 注册时间 */ private LocalDateTime registerTime; /** 最后登录时间 */ private LocalDateTime lastLoginTime; /** 最后登录IP */ private String lastLoginIp; /** 登录次数 */ private Integer loginCount; /** 状态(0正常 1停用) */ private String status; /** 删除标志(0存在 1删除) */ @TableLogic private String delFlag; } ``` ### 2.2 会员DTO(MemberDTO.java) ```java package com.pangu.member.domain; import com.pangu.common.core.domain.BaseDTO; import lombok.Data; import lombok.EqualsAndHashCode; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import java.time.LocalDate; import java.util.List; /** * 会员数据传输对象 * @author 湖北新华业务中台研发团队 */ @Data @EqualsAndHashCode(callSuper = true) public class MemberDTO extends BaseDTO { private static final long serialVersionUID = 1L; /** 会员ID */ private Long memberId; /** 手机号 */ @NotBlank(message = "手机号不能为空") @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") private String phone; /** 昵称 */ private String nickname; /** 性别(0未知 1男 2女) */ private String gender; /** 出生日期 */ private LocalDate birthday; /** 身份类型(1家长 2教师) */ @NotBlank(message = "请选择身份类型") private String identityType; /** 所属区域ID(教师必填) */ private Long regionId; /** 所属学校ID(教师必填) */ private Long schoolId; /** 所属学校年级ID(教师必填) */ private Long schoolGradeId; /** 所属学校班级ID(教师必填) */ private Long schoolClassId; /** 状态(0正常 1停用) */ private String status; /** 绑定的学生ID列表(新增时使用) */ private List studentIds; // ========== 查询条件 ========== /** 注册开始时间 */ private String beginTime; /** 注册结束时间 */ private String endTime; } ``` ### 2.3 会员VO(MemberVO.java) ```java package com.pangu.member.domain; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; /** * 会员视图对象 * @author 湖北新华业务中台研发团队 */ @Data public class MemberVO { /** 会员ID */ private Long memberId; /** 会员编号 */ private String memberCode; /** 手机号(脱敏) */ private String phone; /** 手机号(完整,编辑时使用) */ private String phoneFull; /** 昵称 */ private String nickname; /** 头像URL */ private String avatar; /** 性别代码 */ private String gender; /** 性别名称 */ private String genderName; /** 出生日期 */ @JsonFormat(pattern = "yyyy-MM-dd") private LocalDate birthday; /** 身份类型代码 */ private String identityType; /** 身份类型名称 */ private String identityTypeName; /** 微信OpenID */ private String openId; /** 所属区域ID */ private Long regionId; /** 区域路径(如:湖北省-武汉市-武昌区) */ private String regionPath; /** 所属学校ID */ private Long schoolId; /** 学校名称 */ private String schoolName; /** 所属学校年级ID */ private Long schoolGradeId; /** 年级名称 */ private String gradeName; /** 所属学校班级ID */ private Long schoolClassId; /** 班级名称 */ private String className; /** 注册来源代码 */ private String registerSource; /** 注册来源名称 */ private String registerSourceName; /** 注册时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime registerTime; /** 状态 */ private String status; /** 绑定的学生列表 */ private List students; /** * 学生视图对象 */ @Data public static class StudentVO { /** 学生ID */ private Long studentId; /** 学生姓名 */ private String studentName; /** 学号 */ private String studentNo; /** 学校名称 */ private String schoolName; /** 年级名称 */ private String gradeName; /** 班级名称 */ private String className; } } ``` ### 2.4 枚举类 #### 身份类型枚举(IdentityTypeEnum.java) ```java package com.pangu.member.enums; import lombok.AllArgsConstructor; import lombok.Getter; /** * 身份类型枚举 * @author 湖北新华业务中台研发团队 */ @Getter @AllArgsConstructor public enum IdentityTypeEnum { PARENT("1", "家长"), TEACHER("2", "教师"); private final String code; private final String name; public static String getNameByCode(String code) { for (IdentityTypeEnum type : values()) { if (type.getCode().equals(code)) { return type.getName(); } } return ""; } public static boolean isTeacher(String code) { return TEACHER.getCode().equals(code); } public static boolean isParent(String code) { return PARENT.getCode().equals(code); } } ``` #### 注册来源枚举(RegisterSourceEnum.java) ```java package com.pangu.member.enums; import lombok.AllArgsConstructor; import lombok.Getter; /** * 注册来源枚举 * @author 湖北新华业务中台研发团队 */ @Getter @AllArgsConstructor public enum RegisterSourceEnum { MINI_PROGRAM("1", "小程序"), H5("2", "H5"), BACKEND("3", "后台新增"), IMPORT("4", "批量导入"); private final String code; private final String name; public static String getNameByCode(String code) { for (RegisterSourceEnum source : values()) { if (source.getCode().equals(code)) { return source.getName(); } } return ""; } } ``` --- ## 3. 数据访问层 ### 3.1 Mapper接口(MemberMapper.java) ```java package com.pangu.member.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pangu.member.domain.Member; import com.pangu.member.domain.MemberDTO; import com.pangu.member.domain.MemberVO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; /** * 会员数据访问接口 * @author 湖北新华业务中台研发团队 */ @Mapper public interface MemberMapper extends BaseMapper { /** * 查询会员列表(带关联信息) * @param page 分页对象 * @param dto 查询条件 * @return 会员列表 */ List selectMemberVOList(Page page, @Param("dto") MemberDTO dto); /** * 根据ID查询会员详情(带关联信息) * @param memberId 会员ID * @return 会员详情 */ MemberVO selectMemberVOById(@Param("memberId") Long memberId); /** * 根据手机号查询会员数量(排除指定ID) * @param phone 手机号 * @param memberId 排除的会员ID(可为空) * @return 数量 */ int countByPhone(@Param("phone") String phone, @Param("memberId") Long memberId); /** * 根据手机号查询会员 * @param phone 手机号 * @return 会员信息 */ Member selectByPhone(@Param("phone") String phone); /** * 根据OpenID查询会员 * @param openId 微信OpenID * @return 会员信息 */ Member selectByOpenId(@Param("openId") String openId); } ``` ### 3.2 Mapper XML(MemberMapper.xml) ```xml m.member_id, m.member_code, CONCAT(LEFT(m.phone, 3), '****', RIGHT(m.phone, 4)) AS phone, m.phone AS phone_full, m.nickname, m.avatar, m.gender, m.birthday, m.identity_type, m.open_id, m.region_id, m.school_id, m.school_grade_id, m.school_class_id, m.register_source, m.register_time, m.status, s.school_name, s.region_path, g.grade_name, c.class_name ``` --- ## 4. 服务层 ### 4.1 服务接口(IMemberService.java) ```java package com.pangu.member.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pangu.common.core.page.TableDataInfo; import com.pangu.member.domain.Member; import com.pangu.member.domain.MemberDTO; import com.pangu.member.domain.MemberVO; /** * 会员服务接口 * @author 湖北新华业务中台研发团队 */ public interface IMemberService extends IService { /** * 查询会员列表 * @param memberDTO 查询条件 * @return 分页结果 */ TableDataInfo selectMemberList(MemberDTO memberDTO); /** * 根据ID获取会员详情 * @param memberId 会员ID * @return 会员详情 */ MemberVO getMemberById(Long memberId); /** * 新增会员 * @param memberDTO 会员信息 * @return 结果 */ int insertMember(MemberDTO memberDTO); /** * 修改会员 * @param memberDTO 会员信息 * @return 结果 */ int updateMember(MemberDTO memberDTO); /** * 删除会员 * @param memberId 会员ID * @return 结果 */ int deleteMember(Long memberId); /** * 重置密码 * @param memberId 会员ID * @return 新密码 */ String resetPassword(Long memberId); /** * 修改会员状态 * @param memberId 会员ID * @param status 状态 * @return 结果 */ int changeStatus(Long memberId, String status); /** * 绑定学生 * @param memberId 会员ID * @param studentId 学生ID * @return 结果 */ int bindStudent(Long memberId, Long studentId); /** * 解绑学生 * @param memberId 会员ID * @param studentId 学生ID * @return 结果 */ int unbindStudent(Long memberId, Long studentId); /** * 检查手机号是否唯一 * @param phone 手机号 * @param memberId 会员ID(编辑时排除自己) * @return 是否唯一 */ boolean checkPhoneUnique(String phone, Long memberId); /** * 校验会员是否可删除 * @param memberId 会员ID * @return 是否可删除 */ boolean checkCanDelete(Long memberId); /** * 根据手机号查询会员 * @param phone 手机号 * @return 会员信息 */ Member getMemberByPhone(String phone); /** * 根据OpenID查询会员 * @param openId 微信OpenID * @return 会员信息 */ Member getMemberByOpenId(String openId); /** * 创建会员(批量导入时使用) * @param phone 手机号 * @param identityType 身份类型 * @return 会员ID */ Long createMemberForImport(String phone, String identityType); } ``` ### 4.2 服务实现(MemberServiceImpl.java) ```java package com.pangu.member.service.impl; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pangu.common.core.page.TableDataInfo; import com.pangu.common.exception.ServiceException; import com.pangu.common.utils.SecurityUtils; import com.pangu.member.domain.Member; import com.pangu.member.domain.MemberDTO; import com.pangu.member.domain.MemberVO; import com.pangu.member.enums.IdentityTypeEnum; import com.pangu.member.enums.RegisterSourceEnum; import com.pangu.member.mapper.MemberMapper; import com.pangu.member.service.IMemberService; import com.pangu.student.service.IStudentService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; /** * 会员服务实现 * @author 湖北新华业务中台研发团队 */ @Slf4j @Service @RequiredArgsConstructor public class MemberServiceImpl extends ServiceImpl implements IMemberService { private final MemberMapper memberMapper; private final IStudentService studentService; /** 默认密码 */ private static final String DEFAULT_PASSWORD = "123456"; /** 重置密码长度 */ private static final int RESET_PASSWORD_LENGTH = 8; @Override public TableDataInfo selectMemberList(MemberDTO memberDTO) { // 创建分页对象 Page page = new Page<>(memberDTO.getPageNum(), memberDTO.getPageSize()); // 查询数据 List list = memberMapper.selectMemberVOList(page, memberDTO); // 填充名称字段 list.forEach(this::fillMemberVONames); return TableDataInfo.build(list, page.getTotal()); } @Override public MemberVO getMemberById(Long memberId) { MemberVO memberVO = memberMapper.selectMemberVOById(memberId); if (memberVO == null) { throw new ServiceException("会员不存在"); } // 填充名称 fillMemberVONames(memberVO); // 查询绑定的学生 memberVO.setStudents(studentService.selectStudentVOsByMemberId(memberId)); return memberVO; } @Override @Transactional(rollbackFor = Exception.class) public int insertMember(MemberDTO memberDTO) { // 校验手机号唯一性 if (!checkPhoneUnique(memberDTO.getPhone(), null)) { throw new ServiceException("手机号已存在"); } // 校验教师信息完整性 if (IdentityTypeEnum.isTeacher(memberDTO.getIdentityType())) { validateTeacherInfo(memberDTO); } // 构建会员对象 Member member = buildMember(memberDTO); member.setMemberCode(generateMemberCode()); member.setPassword(SecurityUtils.encryptPassword(DEFAULT_PASSWORD)); member.setRegisterSource(RegisterSourceEnum.BACKEND.getCode()); member.setRegisterTime(LocalDateTime.now()); member.setLoginCount(0); member.setStatus("0"); // 自动生成昵称 if (StrUtil.isBlank(member.getNickname())) { member.setNickname(generateNickname(member.getPhone())); } int result = memberMapper.insert(member); // 绑定学生(如果有) if (memberDTO.getStudentIds() != null && !memberDTO.getStudentIds().isEmpty()) { for (Long studentId : memberDTO.getStudentIds()) { studentService.updateStudentMember(studentId, member.getMemberId()); } } log.info("新增会员成功, memberId={}, phone={}", member.getMemberId(), member.getPhone()); return result; } @Override @Transactional(rollbackFor = Exception.class) public int updateMember(MemberDTO memberDTO) { Member existMember = memberMapper.selectById(memberDTO.getMemberId()); if (existMember == null) { throw new ServiceException("会员不存在"); } // 校验手机号唯一性 if (!checkPhoneUnique(memberDTO.getPhone(), memberDTO.getMemberId())) { throw new ServiceException("手机号已存在"); } // 校验教师信息完整性 if (IdentityTypeEnum.isTeacher(memberDTO.getIdentityType())) { validateTeacherInfo(memberDTO); } // 更新会员信息 Member member = buildMember(memberDTO); member.setMemberId(memberDTO.getMemberId()); // 如果从教师改为家长,清空学校信息 if (IdentityTypeEnum.isParent(memberDTO.getIdentityType())) { member.setRegionId(null); member.setSchoolId(null); member.setSchoolGradeId(null); member.setSchoolClassId(null); } int result = memberMapper.updateById(member); log.info("更新会员成功, memberId={}", member.getMemberId()); return result; } @Override @Transactional(rollbackFor = Exception.class) public int deleteMember(Long memberId) { // 检查是否可删除 if (!checkCanDelete(memberId)) { throw new ServiceException("该会员已绑定学生,请先解绑学生后再删除"); } int result = memberMapper.deleteById(memberId); log.info("删除会员成功, memberId={}", memberId); return result; } @Override public String resetPassword(Long memberId) { Member member = memberMapper.selectById(memberId); if (member == null) { throw new ServiceException("会员不存在"); } // 生成随机密码 String newPassword = RandomUtil.randomString(RESET_PASSWORD_LENGTH); // 更新密码 Member updateMember = new Member(); updateMember.setMemberId(memberId); updateMember.setPassword(SecurityUtils.encryptPassword(newPassword)); memberMapper.updateById(updateMember); log.info("重置会员密码成功, memberId={}", memberId); return newPassword; } @Override public int changeStatus(Long memberId, String status) { Member member = new Member(); member.setMemberId(memberId); member.setStatus(status); int result = memberMapper.updateById(member); log.info("修改会员状态成功, memberId={}, status={}", memberId, status); return result; } @Override @Transactional(rollbackFor = Exception.class) public int bindStudent(Long memberId, Long studentId) { Member member = memberMapper.selectById(memberId); if (member == null) { throw new ServiceException("会员不存在"); } // 教师只能绑定本校学生 if (IdentityTypeEnum.isTeacher(member.getIdentityType())) { if (!studentService.isStudentInSchool(studentId, member.getSchoolId())) { throw new ServiceException("教师只能绑定本校学生"); } } int result = studentService.updateStudentMember(studentId, memberId); log.info("绑定学生成功, memberId={}, studentId={}", memberId, studentId); return result; } @Override @Transactional(rollbackFor = Exception.class) public int unbindStudent(Long memberId, Long studentId) { int result = studentService.unbindStudent(studentId, memberId); log.info("解绑学生成功, memberId={}, studentId={}", memberId, studentId); return result; } @Override public boolean checkPhoneUnique(String phone, Long memberId) { return memberMapper.countByPhone(phone, memberId) == 0; } @Override public boolean checkCanDelete(Long memberId) { // 检查是否有绑定的学生 return studentService.countByMemberId(memberId) == 0; } @Override public Member getMemberByPhone(String phone) { return memberMapper.selectByPhone(phone); } @Override public Member getMemberByOpenId(String openId) { return memberMapper.selectByOpenId(openId); } @Override @Transactional(rollbackFor = Exception.class) public Long createMemberForImport(String phone, String identityType) { // 检查手机号是否已存在 Member existMember = getMemberByPhone(phone); if (existMember != null) { return existMember.getMemberId(); } // 创建新会员 Member member = new Member(); member.setMemberCode(generateMemberCode()); member.setPhone(phone); member.setPassword(SecurityUtils.encryptPassword(DEFAULT_PASSWORD)); member.setNickname(generateNickname(phone)); member.setGender("0"); member.setIdentityType(identityType); member.setRegisterSource(RegisterSourceEnum.IMPORT.getCode()); member.setRegisterTime(LocalDateTime.now()); member.setLoginCount(0); member.setStatus("0"); memberMapper.insert(member); log.info("批量导入创建会员, memberId={}, phone={}", member.getMemberId(), phone); return member.getMemberId(); } /** * 构建会员对象 */ private Member buildMember(MemberDTO dto) { Member member = new Member(); member.setPhone(dto.getPhone()); member.setNickname(dto.getNickname()); member.setGender(dto.getGender()); member.setBirthday(dto.getBirthday()); member.setIdentityType(dto.getIdentityType()); member.setRegionId(dto.getRegionId()); member.setSchoolId(dto.getSchoolId()); member.setSchoolGradeId(dto.getSchoolGradeId()); member.setSchoolClassId(dto.getSchoolClassId()); member.setStatus(dto.getStatus()); return member; } /** * 校验教师信息完整性 */ private void validateTeacherInfo(MemberDTO memberDTO) { if (memberDTO.getRegionId() == null) { throw new ServiceException("请选择所属区域"); } if (memberDTO.getSchoolId() == null) { throw new ServiceException("请选择所属学校"); } if (memberDTO.getSchoolGradeId() == null) { throw new ServiceException("请选择所属年级"); } if (memberDTO.getSchoolClassId() == null) { throw new ServiceException("请选择所属班级"); } } /** * 生成会员编号 * 格式:JS + 时间戳 */ private String generateMemberCode() { return "JS" + System.currentTimeMillis(); } /** * 生成默认昵称 */ private String generateNickname(String phone) { if (StrUtil.isBlank(phone) || phone.length() < 4) { return "用户" + RandomUtil.randomNumbers(4); } return "用户" + phone.substring(phone.length() - 4); } /** * 填充VO的名称字段 */ private void fillMemberVONames(MemberVO vo) { // 性别名称 vo.setGenderName(getGenderName(vo.getGender())); // 身份类型名称 vo.setIdentityTypeName(IdentityTypeEnum.getNameByCode(vo.getIdentityType())); // 注册来源名称 vo.setRegisterSourceName(RegisterSourceEnum.getNameByCode(vo.getRegisterSource())); } /** * 获取性别名称 */ private String getGenderName(String gender) { return switch (gender) { case "1" -> "男"; case "2" -> "女"; default -> "未知"; }; } } ``` --- ## 5. 控制器层 ### 5.1 会员控制器(MemberController.java) ```java package com.pangu.member.controller; import com.pangu.common.annotation.Log; import com.pangu.common.core.controller.BaseController; import com.pangu.common.core.domain.AjaxResult; import com.pangu.common.core.page.TableDataInfo; import com.pangu.common.enums.BusinessType; import com.pangu.member.domain.MemberDTO; import com.pangu.member.domain.MemberVO; import com.pangu.member.service.IMemberService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; /** * 会员管理控制器 * @author 湖北新华业务中台研发团队 */ @Tag(name = "会员管理") @RestController @RequestMapping("/member") @RequiredArgsConstructor public class MemberController extends BaseController { private final IMemberService memberService; /** * 查询会员列表 */ @Operation(summary = "查询会员列表") @PreAuthorize("@ss.hasPermi('user:member:list')") @GetMapping("/list") public TableDataInfo list(MemberDTO memberDTO) { startPage(); return memberService.selectMemberList(memberDTO); } /** * 获取会员详情 */ @Operation(summary = "获取会员详情") @PreAuthorize("@ss.hasPermi('user:member:query')") @GetMapping("/{memberId}") public AjaxResult getInfo( @Parameter(description = "会员ID") @PathVariable Long memberId) { return success(memberService.getMemberById(memberId)); } /** * 新增会员 */ @Operation(summary = "新增会员") @PreAuthorize("@ss.hasPermi('user:member:add')") @Log(title = "会员管理", businessType = BusinessType.INSERT) @PostMapping public AjaxResult add(@Validated @RequestBody MemberDTO memberDTO) { return toAjax(memberService.insertMember(memberDTO)); } /** * 修改会员 */ @Operation(summary = "修改会员") @PreAuthorize("@ss.hasPermi('user:member:edit')") @Log(title = "会员管理", businessType = BusinessType.UPDATE) @PutMapping public AjaxResult edit(@Validated @RequestBody MemberDTO memberDTO) { return toAjax(memberService.updateMember(memberDTO)); } /** * 删除会员 */ @Operation(summary = "删除会员") @PreAuthorize("@ss.hasPermi('user:member:remove')") @Log(title = "会员管理", businessType = BusinessType.DELETE) @DeleteMapping("/{memberId}") public AjaxResult remove( @Parameter(description = "会员ID") @PathVariable Long memberId) { return toAjax(memberService.deleteMember(memberId)); } /** * 重置密码 */ @Operation(summary = "重置密码") @PreAuthorize("@ss.hasPermi('user:member:resetPwd')") @Log(title = "会员管理", businessType = BusinessType.UPDATE) @PutMapping("/resetPwd/{memberId}") public AjaxResult resetPwd( @Parameter(description = "会员ID") @PathVariable Long memberId) { String newPassword = memberService.resetPassword(memberId); return AjaxResult.success("密码重置成功").put("password", newPassword); } /** * 修改状态 */ @Operation(summary = "修改状态") @PreAuthorize("@ss.hasPermi('user:member:edit')") @Log(title = "会员管理", businessType = BusinessType.UPDATE) @PutMapping("/changeStatus") public AjaxResult changeStatus(@RequestBody MemberDTO memberDTO) { return toAjax(memberService.changeStatus(memberDTO.getMemberId(), memberDTO.getStatus())); } /** * 绑定学生 */ @Operation(summary = "绑定学生") @PreAuthorize("@ss.hasPermi('user:member:edit')") @Log(title = "会员管理", businessType = BusinessType.UPDATE) @PostMapping("/bindStudent") public AjaxResult bindStudent(@RequestBody MemberDTO memberDTO) { return toAjax(memberService.bindStudent(memberDTO.getMemberId(), memberDTO.getStudentId())); } /** * 解绑学生 */ @Operation(summary = "解绑学生") @PreAuthorize("@ss.hasPermi('user:member:edit')") @Log(title = "会员管理", businessType = BusinessType.UPDATE) @DeleteMapping("/unbindStudent/{memberId}/{studentId}") public AjaxResult unbindStudent( @Parameter(description = "会员ID") @PathVariable Long memberId, @Parameter(description = "学生ID") @PathVariable Long studentId) { return toAjax(memberService.unbindStudent(memberId, studentId)); } /** * 检查手机号是否唯一 */ @Operation(summary = "检查手机号唯一性") @GetMapping("/checkPhone") public AjaxResult checkPhoneUnique( @Parameter(description = "手机号") @RequestParam String phone, @Parameter(description = "会员ID") @RequestParam(required = false) Long memberId) { boolean unique = memberService.checkPhoneUnique(phone, memberId); return AjaxResult.success().put("unique", unique); } } ``` --- ## 6. 数据权限配置 ### 6.1 数据权限注解 ```java /** * 会员列表查询需要配置数据权限 * 根据不同角色过滤数据 */ @DataScope(deptAlias = "s", userAlias = "m") public TableDataInfo selectMemberList(MemberDTO memberDTO) { // 自动拼接数据权限SQL } ``` ### 6.2 数据权限SQL ```sql -- 超级管理员:无限制 -- 分公司用户:按区域过滤 AND m.region_id IN (SELECT region_id FROM sys_user_region WHERE user_id = #{userId}) -- 学校用户:按学校过滤 AND m.school_id = #{userSchoolId} AND m.identity_type = '2' ``` --- ## 7. 配置项 ### 7.1 application.yml ```yaml pangu: member: # 默认密码 default-password: 123456 # 重置密码长度 reset-password-length: 8 # 会员编号前缀 code-prefix: JS ``` ### 7.2 菜单权限配置 ```sql -- 会员管理菜单 INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, perms, icon) VALUES ('会员管理', 2000, 1, 'member', 'user/member/index', 'C', 'user:member:list', 'peoples'); -- 按钮权限 INSERT INTO sys_menu (menu_name, parent_id, order_num, perms, menu_type) VALUES ('会员查询', 2010, 1, 'user:member:query', 'F'), ('会员新增', 2010, 2, 'user:member:add', 'F'), ('会员修改', 2010, 3, 'user:member:edit', 'F'), ('会员删除', 2010, 4, 'user:member:remove', 'F'), ('重置密码', 2010, 5, 'user:member:resetPwd', 'F'); ``` --- ## 8. 注意事项 ### 8.1 开发注意事项 1. **手机号唯一性**:使用数据库唯一索引 + 业务层校验双重保障 2. **密码安全**:使用BCrypt加密,不存储明文密码 3. **软删除**:所有删除操作使用逻辑删除 4. **数据权限**:根据用户角色过滤数据 5. **事务控制**:涉及多表操作需要添加事务注解 ### 8.2 性能优化 1. **索引优化**:为常用查询字段添加索引 2. **分页查询**:列表查询必须分页 3. **关联查询**:使用LEFT JOIN避免N+1问题 4. **缓存使用**:高频数据可考虑Redis缓存 ### 8.3 日志记录 1. **操作日志**:使用@Log注解记录关键操作 2. **业务日志**:使用log.info记录业务关键节点 3. **异常日志**:使用log.error记录异常信息 --- *文档结束*