| | |
| | | |
| | | import cn.idev.excel.FastExcel; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.tcm.training.admin.common.DateTimes; |
| | | import com.tcm.training.admin.common.PageResponse; |
| | | import com.tcm.training.admin.common.PhoneMasker; |
| | | import com.tcm.training.admin.live.entity.Clinic; |
| | | import com.tcm.training.admin.member.entity.AppUser; |
| | | import com.tcm.training.admin.live.mapper.ClinicMapper; |
| | | import com.tcm.training.admin.member.dto.UserBatchClinicUpdateRequest; |
| | | import com.tcm.training.admin.member.dto.UserResetPasswordRequest; |
| | | import com.tcm.training.admin.member.dto.UserStatusRequest; |
| | | import com.tcm.training.admin.member.dto.UserUpdateRequest; |
| | | import com.tcm.training.admin.member.entity.AppUser; |
| | | import com.tcm.training.admin.member.excel.MemberUserExcelRow; |
| | | import com.tcm.training.admin.member.excel.UserExcelRow; |
| | | import com.tcm.training.admin.member.mapper.UserMapper; |
| | | import com.tcm.training.admin.member.row.UserDetailRow; |
| | | import com.tcm.training.admin.member.row.UserListRow; |
| | | import com.tcm.training.admin.member.vo.ClinicInUserVO; |
| | | import com.tcm.training.admin.member.vo.ClinicOptionVO; |
| | | import com.tcm.training.admin.member.vo.UserDetailVO; |
| | | import com.tcm.training.admin.member.vo.UserListItemVO; |
| | | import com.tcm.training.admin.member.vo.UserStatusVO; |
| | | import com.tcm.training.admin.member.vo.WechatBindingVO; |
| | | import com.tcm.training.admin.member.vo.*; |
| | | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | |
| | | private static final Pattern PASSWORD_PATTERN = Pattern.compile( |
| | | "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[^A-Za-z\\d]).{12,64}$" |
| | | ); |
| | | private static final String MEMBER_STATUS_ALL = "ALL"; |
| | | private static final String MEMBER_STATUS_ACTIVE = "ACTIVE"; |
| | | private static final String MEMBER_STATUS_EXPIRED = "EXPIRED"; |
| | | |
| | | private final UserMapper userMapper; |
| | | private final ClinicMapper clinicMapper; |
| | |
| | | Long clinicId, |
| | | Integer status, |
| | | Integer isMember, |
| | | String memberStatus, |
| | | LocalDateTime memberExpireFrom, |
| | | LocalDateTime memberExpireTo, |
| | | LocalDateTime registeredFrom, |
| | | LocalDateTime registeredTo) { |
| | | String normalizedMemberStatus = normalizeMemberStatus(memberStatus); |
| | | IPage<UserListRow> result = userMapper.selectUserPage( |
| | | new Page<UserListRow>(page, pageSize), |
| | | trimToNull(nickname), |
| | |
| | | clinicId, |
| | | status, |
| | | isMember, |
| | | normalizedMemberStatus, |
| | | memberExpireFrom, |
| | | memberExpireTo, |
| | | registeredFrom, |
| | | registeredTo |
| | | ); |
| | |
| | | UserListItemVO item = new UserListItemVO(); |
| | | item.setId(row.getId()); |
| | | item.setNickname(row.getNickname()); |
| | | item.setPhone(PhoneMasker.mask(row.getPhone())); |
| | | item.setPhone(row.getPhone()); |
| | | item.setAvatar(row.getAvatar()); |
| | | item.setType(row.getType()); |
| | | item.setClinicName(row.getClinicName()); |
| | | item.setAge(row.getAge()); |
| | | item.setGender(row.getGender()); |
| | | item.setIsMember(row.getIsMember() != null && row.getIsMember() == 1); |
| | | item.setMemberExpireAt(row.getMemberExpireAt()); |
| | | item.setStatus(row.getStatus()); |
| | | item.setCreatedAt(row.getCreatedAt()); |
| | | list.add(item); |
| | |
| | | return vo; |
| | | } |
| | | |
| | | @Transactional |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public UserDetailVO update(Long id, UserUpdateRequest req) { |
| | | AppUser user = requireUser(id); |
| | | if (req.getClinicId() != null) { |
| | |
| | | return detail(id); |
| | | } |
| | | |
| | | @Transactional |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public UserStatusVO patchStatus(Long id, UserStatusRequest req) { |
| | | AppUser user = requireUser(id); |
| | | user.setStatus(req.getStatus()); |
| | |
| | | return new UserStatusVO(id, req.getStatus()); |
| | | } |
| | | |
| | | @Transactional |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void resetPassword(Long id, UserResetPasswordRequest req) { |
| | | AppUser user = requireUser(id); |
| | | if (!req.getNewPassword().equals(req.getConfirmPassword())) { |
| | |
| | | userMapper.updateById(user); |
| | | } |
| | | |
| | | @Transactional |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void delete(Long id) { |
| | | requireUser(id); |
| | | userMapper.deleteById(id); |
| | | } |
| | | |
| | | @Transactional |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public UserDetailVO unbindWechat(Long id) { |
| | | AppUser user = requireUser(id); |
| | | user.setWechatOpenid(null); |
| | | user.setWechatNickname(null); |
| | | user.setWechatAvatar(null); |
| | | user.setWechatBoundAt(null); |
| | | userMapper.updateById(user); |
| | | requireUser(id); |
| | | userMapper.update(null, new LambdaUpdateWrapper<AppUser>() |
| | | .eq(AppUser::getId, id) |
| | | .set(AppUser::getWechatOpenid, null) |
| | | .set(AppUser::getWechatNickname, null) |
| | | .set(AppUser::getWechatAvatar, null) |
| | | .set(AppUser::getWechatBoundAt, null)); |
| | | return detail(id); |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void batchUpdateClinic(UserBatchClinicUpdateRequest req) { |
| | | Clinic clinic = clinicMapper.selectById(req.getClinicId()); |
| | | if (clinic == null || Integer.valueOf(1).equals(clinic.getDeleted())) { |
| | | throw new IllegalArgumentException("诊所不存在"); |
| | | } |
| | | Long userCount = userMapper.selectCount(new LambdaQueryWrapper<AppUser>() |
| | | .in(AppUser::getId, req.getUserIds()) |
| | | .eq(AppUser::getDeleted, 0)); |
| | | if (userCount != req.getUserIds().size()) { |
| | | throw new IllegalArgumentException("部分用户不存在"); |
| | | } |
| | | userMapper.update(null, new LambdaUpdateWrapper<AppUser>() |
| | | .in(AppUser::getId, req.getUserIds()) |
| | | .eq(AppUser::getDeleted, 0) |
| | | .set(AppUser::getClinicId, req.getClinicId())); |
| | | } |
| | | |
| | | public List<ClinicOptionVO> listClinicOptions() { |
| | |
| | | Long clinicId, |
| | | Integer status, |
| | | Integer isMember, |
| | | String memberStatus, |
| | | LocalDateTime memberExpireFrom, |
| | | LocalDateTime memberExpireTo, |
| | | LocalDateTime registeredFrom, |
| | | LocalDateTime registeredTo, |
| | | HttpServletResponse response) throws IOException { |
| | | String normalizedMemberStatus = normalizeMemberStatus(memberStatus); |
| | | boolean memberMode = normalizedMemberStatus != null; |
| | | response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); |
| | | response.setCharacterEncoding("utf-8"); |
| | | String filename = URLEncoder.encode("用户列表", "UTF-8").replace("+", "%20"); |
| | | String filename = URLEncoder.encode(memberMode ? "会员用户列表" : "用户列表", "UTF-8").replace("+", "%20"); |
| | | response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + filename + ".xlsx"); |
| | | |
| | | List<UserListRow> rows = userMapper.selectUserList( |
| | |
| | | clinicId, |
| | | status, |
| | | isMember, |
| | | normalizedMemberStatus, |
| | | memberExpireFrom, |
| | | memberExpireTo, |
| | | registeredFrom, |
| | | registeredTo |
| | | ); |
| | | if (memberMode) { |
| | | List<MemberUserExcelRow> memberExcelRows = new ArrayList<MemberUserExcelRow>(); |
| | | for (UserListRow row : rows) { |
| | | MemberUserExcelRow excel = new MemberUserExcelRow(); |
| | | excel.setId(row.getId()); |
| | | excel.setNickname(row.getNickname()); |
| | | excel.setPhone(PhoneMasker.mask(row.getPhone())); |
| | | excel.setMemberStatus(row.getIsMember() != null && row.getIsMember() == 1 ? "生效中" : "已失效"); |
| | | excel.setMemberExpireAt(DateTimes.format(row.getMemberExpireAt())); |
| | | memberExcelRows.add(excel); |
| | | } |
| | | FastExcel.write(response.getOutputStream(), MemberUserExcelRow.class) |
| | | .sheet("会员用户列表") |
| | | .doWrite(memberExcelRows); |
| | | return; |
| | | } |
| | | List<UserExcelRow> excelRows = new ArrayList<UserExcelRow>(); |
| | | for (UserListRow row : rows) { |
| | | UserExcelRow excel = new UserExcelRow(); |
| | |
| | | return trimmed.isEmpty() ? null : trimmed; |
| | | } |
| | | |
| | | private String normalizeMemberStatus(String memberStatus) { |
| | | String normalized = trimToNull(memberStatus); |
| | | if (normalized == null) { |
| | | return null; |
| | | } |
| | | String upper = normalized.toUpperCase(); |
| | | if (MEMBER_STATUS_ALL.equals(upper) || MEMBER_STATUS_ACTIVE.equals(upper) || MEMBER_STATUS_EXPIRED.equals(upper)) { |
| | | return upper; |
| | | } |
| | | throw new IllegalArgumentException("会员状态筛选不合法"); |
| | | } |
| | | |
| | | private String typeText(Integer type) { |
| | | return type != null && type == 1 ? "诊所" : "个人"; |
| | | } |