package edu.whut.smilepicturebackend.service.impl; import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.RegexPool; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import edu.whut.smilepicturebackend.constant.CacheConstant; import edu.whut.smilepicturebackend.constant.UserConstant; import edu.whut.smilepicturebackend.exception.BusinessException; import edu.whut.smilepicturebackend.exception.ErrorCode; import edu.whut.smilepicturebackend.exception.ThrowUtils; import edu.whut.smilepicturebackend.manager.auth.StpKit; import edu.whut.smilepicturebackend.manager.email.EmailManager; import edu.whut.smilepicturebackend.manager.upload.FilePictureUpload; import edu.whut.smilepicturebackend.model.dto.user.UserEditPasswordRequest; import edu.whut.smilepicturebackend.model.dto.user.UserEditRequest; import edu.whut.smilepicturebackend.model.dto.user.UserQueryRequest; import edu.whut.smilepicturebackend.model.entity.User; import edu.whut.smilepicturebackend.model.enums.UserRoleEnum; import edu.whut.smilepicturebackend.model.file.UploadPictureResult; import edu.whut.smilepicturebackend.model.vo.LoginUserVO; import edu.whut.smilepicturebackend.model.vo.UserVO; import edu.whut.smilepicturebackend.service.UserService; import edu.whut.smilepicturebackend.mapper.UserMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * @author 张三 * @description 针对表【user(用户)】的数据库操作Service实现 */ @Service @Slf4j @RequiredArgsConstructor public class UserServiceImpl extends ServiceImpl implements UserService{ private final BCryptPasswordEncoder passwordEncoder; private final EmailManager emailManager; private final StringRedisTemplate stringRedisTemplate; private final FilePictureUpload uploadPictureFile; /** * 用户注册 */ @Override public void userRegister(String userEmail, String codeKey, String codeValue) { String KEY = String.format(CacheConstant.EMAIL_CODE_KEY, codeKey, userEmail); // 获取 Redis 中的验证码 String code = stringRedisTemplate.opsForValue().get(KEY); // 删除验证码 if (StrUtil.isEmpty(code) || !code.equals(codeValue)) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "验证码错误"); } boolean existed = existedUserByEmail(userEmail); if (existed) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号已存在, 请直接登录!"); } // 构建参数 User user = new User(); user.setUserEmail(userEmail); // 默认值填充 fillDefaultValue(user); boolean result = this.save(user); ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR,"注册失败!"); stringRedisTemplate.delete(KEY); emailManager.sendEmailAsRegisterSuccess(userEmail, "Smile图库 - 注册成功通知"); } /** * 获取加密后的密码 * * @param userPassword 用户密码 * @return 加密后的密码 */ @Override public String getEncryptPassword(String userPassword) { return passwordEncoder.encode(userPassword); // // 加盐,混淆密码 // final String SALT = "smile12306"; // return DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes()); } @Override public LoginUserVO userLogin(String userAccount, String userPassword, String captchaKey, String captchaCode,HttpServletRequest request) { String KEY = String.format(CacheConstant.CAPTCHA_CODE_KEY, captchaKey); // 获取 Redis 中的验证码 String code = stringRedisTemplate.opsForValue().get(KEY); // 删除验证码 stringRedisTemplate.delete(KEY); if (StrUtil.isEmpty(code) || !code.equals(captchaCode)) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "验证码错误"); } User user; if (ReUtil.isMatch(RegexPool.EMAIL, userAccount)) { // 根据 userEmail 获取用户信息 user = this.getOne(new LambdaQueryWrapper().eq(User::getUserEmail, userAccount)); } else { // 根据 userAccount 获取用户信息 user = this.getOne(new LambdaQueryWrapper().eq(User::getUserAccount, userAccount)); } if (user == null) { throw new BusinessException(ErrorCode.DATA_ERROR, "用户不存在或密码错误"); } // 校验密码 if (!passwordEncoder.matches(userPassword, user.getUserPassword())) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或者密码错误"); } // 存储用户登录态 request.getSession().setAttribute(UserConstant.USER_LOGIN_STATE, user); // 记录用户登录态到 Sa-token,便于空间鉴权时使用,注意保证该用户信息与 SpringSession 中的信息过期时间一致 StpKit.SPACE.login(user.getId()); StpKit.SPACE.getSession().set(UserConstant.USER_LOGIN_STATE, user); return this.getLoginUserVO(user); } /** * 获取脱敏后的用户信息(无密码) * * @param user 用户 * @return 脱敏后的用户信息 */ @Override public LoginUserVO getLoginUserVO(User user) { if (user == null) { return null; } LoginUserVO loginUserVO = new LoginUserVO(); BeanUtil.copyProperties(user, loginUserVO); return loginUserVO; } /** * 获得脱敏后的用户信息(无密码、更新、编辑时间) * * @param user * @return */ @Override public UserVO getUserVO(User user) { if (user == null) { return null; } UserVO userVO = new UserVO(); BeanUtil.copyProperties(user, userVO); return userVO; } /** * 获取脱敏后的用户列表 * * @param userList * @return */ @Override public List getUserVOList(List userList) { if (CollUtil.isEmpty(userList)) { return new ArrayList<>(); } return userList.stream() .map(this::getUserVO) .collect(Collectors.toList()); } /** * 获取当前登录用户 * @param request * @return */ @Override public User getLoginUser(HttpServletRequest request) { // 判断是否已经登录 Object userObj = request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE); User currentUser = (User) userObj; if (currentUser == null || currentUser.getId() == null) { throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR); } // // 从数据库中查询(追求性能的话可以注释,直接返回上述结果) // Long userId = currentUser.getId(); // currentUser = this.getById(userId); // if (currentUser == null) { // throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR); // } return currentUser; } /** * 退出登录 * * @param request * @return */ @Override public boolean userLogout(HttpServletRequest request) { // 判断是否已经登录 Object userObj = request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE); if (userObj == null) { throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录"); } // 移除登录态 request.getSession().removeAttribute(UserConstant.USER_LOGIN_STATE); return true; } /** * 查询条件 * @param req * @return */ @Override public LambdaQueryWrapper getQueryWrapper(UserQueryRequest req) { if (req == null) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空"); } LambdaQueryWrapper qw = Wrappers.lambdaQuery(User.class); // 基本等值和模糊匹配 qw.eq(ObjUtil.isNotNull(req.getId()), User::getId, req.getId()) .eq(StrUtil.isNotBlank(req.getUserRole()), User::getUserRole, req.getUserRole()) .like(StrUtil.isNotBlank(req.getUserAccount()), User::getUserAccount, req.getUserAccount()) .like(StrUtil.isNotBlank(req.getUserName()), User::getUserName, req.getUserName()) .like(StrUtil.isNotBlank(req.getUserProfile()), User::getUserProfile, req.getUserProfile()); // 动态排序 if (StrUtil.isNotBlank(req.getSortField())) { String column = StrUtil.toUnderlineCase(req.getSortField()); boolean asc = "ascend".equalsIgnoreCase(req.getSortOrder()); qw.last("ORDER BY " + column + (asc ? " ASC" : " DESC")); } return qw; } @Override public boolean isAdmin(User user) { return user != null && UserRoleEnum.ADMIN.getValue().equals(user.getUserRole()); } @Override public String sendEmailCode(String userEmail) { boolean existed = existedUserByEmail(userEmail); if (existed) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号已存在, 请直接登录!"); } // 发送邮箱验证码 String code = RandomUtil.randomNumbers(4); emailManager.sendEmailCode(userEmail, "Smile图库 - 注册验证码", code); // 生成一个唯一 ID, 后面注册前端需要带过来 String key = UUID.randomUUID().toString(); // 存入 Redis, 5 分钟过期 stringRedisTemplate.opsForValue().set(String.format(CacheConstant.EMAIL_CODE_KEY, key, userEmail), code, 5, TimeUnit.MINUTES); return key; } /** * 用户修改密码 */ @Override public void editUserPassword(UserEditPasswordRequest userEditPasswordRequest,HttpServletRequest request) { User loginUser = this.getLoginUser(request); log.info(loginUser.toString()); if (!passwordEncoder.matches(userEditPasswordRequest.getOriginPassword(),loginUser.getUserPassword())) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "原密码错误"); } User user=new User(); user.setId(loginUser.getId()); user.setUserPassword(getEncryptPassword(userEditPasswordRequest.getNewPassword())); boolean result = this.updateById(user); if (result) { // 移除登录态 request.getSession().removeAttribute(UserConstant.USER_LOGIN_STATE); return; } throw new BusinessException(ErrorCode.SYSTEM_ERROR, "密码修改失败!"); } @Override public void editUser(UserEditRequest userEditRequest,HttpServletRequest request) { boolean existed= this.getBaseMapper() .exists(new QueryWrapper() .eq("id", userEditRequest.getId()) ); if (!existed) { throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "用户不存在!"); } User user=new User(); BeanUtil.copyProperties(userEditRequest,user); boolean result = this.updateById(user); if (result) { // 移除登录态 request.getSession().removeAttribute(UserConstant.USER_LOGIN_STATE); return; } throw new BusinessException(ErrorCode.SYSTEM_ERROR, "编辑失败!"); } @Override public String uploadAvatar(MultipartFile avatarFile) { long userId = StpKit.SPACE.getLoginIdAsLong(); String pathPrefix = "avatar/" + userId + "/"; // 调用上传图片 UploadPictureResult uploadPictureResult = uploadPictureFile.uploadPicture(avatarFile, pathPrefix); String avatarUrl = uploadPictureResult.getUrl(); if (StrUtil.isEmpty(avatarUrl)) { throw new BusinessException(ErrorCode.OPERATION_ERROR, "头像上传失败"); } User user=new User(); user.setId(userId); user.setUserAvatar(avatarUrl); boolean result = this.updateById(user); if (!result) { throw new BusinessException(ErrorCode.OPERATION_ERROR, "头像更新失败"); } // 移除登录态 // request.getSession().removeAttribute(UserConstant.USER_LOGIN_STATE); return avatarUrl; } public boolean existedUserByEmail(String userEmail) { return this.getBaseMapper() .exists(new QueryWrapper() .eq("user_email", userEmail) ); } /** * 填充默认值 */ public void fillDefaultValue(User user) { String random = RandomUtil.randomString(6); user.setUserAccount("user_" + random); user.setUserName("用户_" + random); user.setUserRole(UserRoleEnum.USER.getText()); user.setUserPassword(getEncryptPassword("12345678")); } }