refactor: 重命名 app 模块为 client 与 open 模块
- 将 app 包下的用户、文件相关类迁移到 client 包 - 将认证相关类迁移到 open 包,并更新安全配置放行路径 - 修复 ApkMetaParser 合并元数据时图标未保留的问题 - 调整 FilePath 上传路径逻辑
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
package com.youlai.boot.client.controller;
|
||||
|
||||
import com.youlai.boot.client.model.vo.ClientFileInfo;
|
||||
import com.youlai.boot.client.service.ClientFileService;
|
||||
import com.youlai.boot.common.result.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 文件控制层
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2022/10/16
|
||||
*/
|
||||
@Tag(name = "10.文件接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/client/files")
|
||||
@RequiredArgsConstructor
|
||||
public class ClientFileController {
|
||||
|
||||
private final ClientFileService fileService;
|
||||
|
||||
@PostMapping
|
||||
@Operation(summary = "文件上传")
|
||||
public Result<ClientFileInfo> uploadFile(
|
||||
@Parameter(
|
||||
name = "file",
|
||||
description = "表单文件对象",
|
||||
required = true,
|
||||
in = ParameterIn.DEFAULT,
|
||||
schema = @Schema(name = "file", format = "binary")
|
||||
)
|
||||
@RequestPart(value = "file") MultipartFile file
|
||||
) {
|
||||
Assert.isTrue(!file.isEmpty(), "上传文件不能为空文件");
|
||||
ClientFileInfo fileInfo = fileService.uploadFile(file);
|
||||
return Result.success(fileInfo);
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
@Operation(summary = "文件删除")
|
||||
@SneakyThrows
|
||||
public Result<?> deleteFile(
|
||||
@Parameter(description = "文件路径") @RequestParam String filePath
|
||||
) {
|
||||
boolean result = fileService.deleteFile(filePath);
|
||||
return Result.judge(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.youlai.boot.client.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.youlai.boot.common.base.BaseEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 移动端用户实体
|
||||
*/
|
||||
@TableName("app_user")
|
||||
@Getter
|
||||
@Setter
|
||||
public class ClientUser extends BaseEntity {
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 性别((1-男 2-女 0-保密)
|
||||
*/
|
||||
private Integer gender;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 绑定手机
|
||||
*/
|
||||
private String mobile;
|
||||
|
||||
/**
|
||||
* 绑定微信
|
||||
*/
|
||||
private String wechatOpenid;
|
||||
|
||||
/**
|
||||
* 状态((1-正常 0-禁用)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 用户邮箱
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 是否删除(0-否 1-是)
|
||||
*/
|
||||
private Integer isDeleted;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.youlai.boot.client.model.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Range;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户表单对象
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/4/12 11:04
|
||||
*/
|
||||
@Schema(description = "用户表单对象")
|
||||
@Data
|
||||
public class ClientUserForm {
|
||||
|
||||
@Schema(description="用户ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description="用户名")
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
@Schema(description="昵称")
|
||||
@NotBlank(message = "昵称不能为空")
|
||||
private String nickname;
|
||||
|
||||
|
||||
@Schema(description="手机号码")
|
||||
@Pattern(regexp = "^$|^1(3\\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$", message = "手机号码格式不正确")
|
||||
private String mobile;
|
||||
|
||||
@Schema(description="性别")
|
||||
private Integer gender;
|
||||
|
||||
@Schema(description="用户头像")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description="邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description="用户状态(1:正常;0:禁用)")
|
||||
@Range(min = 0, max = 1, message = "用户状态不正确")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description="部门ID")
|
||||
private Long deptId;
|
||||
|
||||
@Schema(description="角色ID集合")
|
||||
@NotEmpty(message = "用户角色不能为空")
|
||||
private List<Long> roleIds;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.youlai.boot.client.model.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
/**
|
||||
* 文件信息对象
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Schema(description = "文件对象")
|
||||
@Data
|
||||
public class ClientFileInfo {
|
||||
|
||||
@Schema(description = "文件名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "文件URL")
|
||||
private String url;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.youlai.boot.client.service;
|
||||
|
||||
import com.youlai.boot.client.model.vo.ClientFileInfo;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 对象存储服务接口层
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2022/11/19
|
||||
*/
|
||||
public interface ClientFileService {
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @param file 表单文件对象
|
||||
* @return 文件信息
|
||||
*/
|
||||
ClientFileInfo uploadFile(MultipartFile file);
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param filePath 文件完整URL
|
||||
* @return 删除结果
|
||||
*/
|
||||
boolean deleteFile(String filePath);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package com.youlai.boot.client.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.youlai.boot.client.model.entity.ClientUser;
|
||||
import com.youlai.boot.client.model.form.ClientUserForm;
|
||||
import com.youlai.boot.common.model.Option;
|
||||
import com.youlai.boot.framework.security.model.SecurityUser;
|
||||
import com.youlai.boot.system.model.form.*;
|
||||
import com.youlai.boot.system.model.query.UserQuery;
|
||||
import com.youlai.boot.system.model.vo.CurrentUserVO;
|
||||
import com.youlai.boot.system.model.vo.UserExportVO;
|
||||
import com.youlai.boot.system.model.vo.UserPageVO;
|
||||
import com.youlai.boot.system.model.vo.UserProfileVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户业务接口
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2022/1/14
|
||||
*/
|
||||
public interface ClientUserService extends IService<ClientUser> {
|
||||
|
||||
/**
|
||||
* 用户分页列表
|
||||
*
|
||||
* @return {@link IPage<UserPageVO>} 用户分页列表
|
||||
*/
|
||||
IPage<UserPageVO> getUserPage(UserQuery queryParams);
|
||||
|
||||
/**
|
||||
* 获取用户表单数据
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return {@link ClientUserForm} 用户表单数据
|
||||
*/
|
||||
ClientUserForm getUserFormData(Long userId);
|
||||
|
||||
|
||||
/**
|
||||
* 新增用户
|
||||
*
|
||||
* @param userForm 用户表单对象
|
||||
* @return {@link Boolean} 是否新增成功
|
||||
*/
|
||||
boolean saveUser(ClientUserForm userForm);
|
||||
|
||||
/**
|
||||
* 修改用户
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param userForm 用户表单对象
|
||||
* @return {@link Boolean} 是否修改成功
|
||||
*/
|
||||
boolean updateUser(Long userId, ClientUserForm userForm);
|
||||
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*
|
||||
* @param idsStr 用户ID,多个以英文逗号(,)分割
|
||||
* @return {@link Boolean} 是否删除成功
|
||||
*/
|
||||
boolean deleteUsers(String idsStr);
|
||||
|
||||
|
||||
/**
|
||||
* 根据用户名获取认证信息
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return {@link SecurityUser}
|
||||
*/
|
||||
SecurityUser getAuthInfoByUsername(String username);
|
||||
|
||||
default SecurityUser getAuthCredentialsByUsername(String username) {
|
||||
return getAuthInfoByUsername(username);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取导出用户列表
|
||||
*
|
||||
* @param queryParams 查询参数
|
||||
* @return {@link List<UserExportVO>} 导出用户列表
|
||||
*/
|
||||
List<UserExportVO> listExportUsers(UserQuery queryParams);
|
||||
|
||||
|
||||
/**
|
||||
* 获取登录用户信息
|
||||
*
|
||||
* @return {@link CurrentUserVO} 登录用户信息
|
||||
*/
|
||||
CurrentUserVO getCurrentUserInfo();
|
||||
|
||||
/**
|
||||
* 获取个人中心用户信息
|
||||
*
|
||||
* @return {@link UserProfileVO} 个人中心用户信息
|
||||
*/
|
||||
UserProfileVO getUserProfile(Long userId);
|
||||
|
||||
/**
|
||||
* 修改个人中心用户信息
|
||||
*
|
||||
* @param formData 表单数据
|
||||
* @return {@link Boolean} 是否修改成功
|
||||
*/
|
||||
boolean updateUserProfile(UserProfileForm formData);
|
||||
|
||||
/**
|
||||
* 修改指定用户密码
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param data 修改密码表单数据
|
||||
* @return {@link Boolean} 是否修改成功
|
||||
*/
|
||||
boolean changeUserPassword(Long userId, PasswordUpdateForm data);
|
||||
|
||||
/**
|
||||
* 重置指定用户密码
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param password 重置后的密码
|
||||
* @return {@link Boolean} 是否重置成功
|
||||
*/
|
||||
boolean resetUserPassword(Long userId, String password);
|
||||
|
||||
/**
|
||||
* 发送短信验证码(绑定或更换手机号)
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @return {@link Boolean} 是否发送成功
|
||||
*/
|
||||
boolean sendMobileCode(String mobile);
|
||||
|
||||
/**
|
||||
* 修改当前用户手机号
|
||||
*
|
||||
* @param data 表单数据
|
||||
* @return {@link Boolean} 是否修改成功
|
||||
*/
|
||||
boolean bindOrChangeMobile(MobileUpdateForm data);
|
||||
|
||||
/**
|
||||
* 发送邮箱验证码(绑定或更换邮箱)
|
||||
*
|
||||
* @param email 邮箱
|
||||
*/
|
||||
void sendEmailCode(String email);
|
||||
|
||||
/**
|
||||
* 绑定或更换邮箱
|
||||
*
|
||||
* @param data 表单数据
|
||||
* @return {@link Boolean} 是否绑定成功
|
||||
*/
|
||||
boolean bindOrChangeEmail(EmailUpdateForm data);
|
||||
|
||||
/**
|
||||
* 解绑手机号
|
||||
*
|
||||
* @param data 表单数据
|
||||
* @return {@link Boolean} 是否解绑成功
|
||||
*/
|
||||
boolean unbindMobile(PasswordVerifyForm data);
|
||||
|
||||
/**
|
||||
* 解绑邮箱
|
||||
*
|
||||
* @param data 表单数据
|
||||
* @return {@link Boolean} 是否解绑成功
|
||||
*/
|
||||
boolean unbindEmail(PasswordVerifyForm data);
|
||||
|
||||
/**
|
||||
* 获取用户选项列表
|
||||
*
|
||||
* @return {@link List<Option<String>>} 用户选项列表
|
||||
*/
|
||||
// List<Option<String>> listUserOptions();
|
||||
|
||||
/**
|
||||
* 根据手机号获取用户认证信息
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @return {@link SecurityUser}
|
||||
*/
|
||||
SecurityUser getAuthInfoByMobile(String mobile);
|
||||
|
||||
default SecurityUser getAuthCredentialsByMobile(String mobile) {
|
||||
return getAuthInfoByMobile(mobile);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.youlai.boot.client.service.impl;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.youlai.boot.client.model.vo.ClientFileInfo;
|
||||
import com.youlai.boot.client.service.ClientFileService;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 本地存储服务类
|
||||
*
|
||||
* @author Theo
|
||||
* @since 2024-12-09 17:11
|
||||
*/
|
||||
@Data
|
||||
@Slf4j
|
||||
@Component
|
||||
//@ConditionalOnProperty(value = "oss.type", havingValue = "local")
|
||||
//@ConfigurationProperties(prefix = "oss.local")
|
||||
@RequiredArgsConstructor
|
||||
public class ClientClientFileService implements ClientFileService {
|
||||
|
||||
@Value("${file-storage.local.path}")
|
||||
private String storagePath;
|
||||
|
||||
/**
|
||||
* 上传文件方法
|
||||
*
|
||||
* @param file 表单文件对象
|
||||
* @return 文件信息
|
||||
*/
|
||||
@Override
|
||||
public ClientFileInfo uploadFile(MultipartFile file) {
|
||||
// 获取文件名
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
// 获取文件后缀
|
||||
String suffix = FileUtil.getSuffix(originalFilename);
|
||||
// 生成uuid
|
||||
String fileName = IdUtil.simpleUUID()+ "." + suffix;;
|
||||
// 生成文件名(日期文件夹)
|
||||
String folder = DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATE_PATTERN);
|
||||
String filePrefix = storagePath.endsWith(File.separator) ? storagePath : storagePath + File.separator;
|
||||
// try-with-resource 语法糖自动释放流
|
||||
try (InputStream inputStream = file.getInputStream()) {
|
||||
// 上传文件
|
||||
FileUtil.writeFromStream(inputStream, filePrefix + folder + File.separator + fileName);
|
||||
} catch (Exception e) {
|
||||
log.error("文件上传失败", e);
|
||||
throw new RuntimeException("文件上传失败");
|
||||
}
|
||||
// 获取文件访问路径,因为这里是本地存储,所以直接返回文件的相对路径,需要前端自行处理访问前缀
|
||||
String fileUrl = File.separator + folder + File.separator + fileName;
|
||||
ClientFileInfo fileInfo = new ClientFileInfo();
|
||||
fileInfo.setName(originalFilename);
|
||||
fileInfo.setUrl(fileUrl);
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param filePath 文件完整URL
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
@Override
|
||||
public boolean deleteFile(String filePath) {
|
||||
//判断文件是否为空
|
||||
if (filePath == null || filePath.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
// 判断filepath是否为文件夹
|
||||
if (FileUtil.isDirectory(storagePath + filePath)) {
|
||||
// 禁止删除文件夹
|
||||
return false;
|
||||
}
|
||||
// 删除文件
|
||||
return FileUtil.del(storagePath + filePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,680 @@
|
||||
package com.youlai.boot.client.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.boot.open.converter.ClientUserConverter;
|
||||
import com.youlai.boot.open.mapper.ClientUserMapper;
|
||||
import com.youlai.boot.client.model.entity.ClientUser;
|
||||
import com.youlai.boot.client.model.form.ClientUserForm;
|
||||
import com.youlai.boot.client.service.ClientUserService;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.common.constant.SystemConstants;
|
||||
import com.youlai.boot.common.exception.BusinessException;
|
||||
import com.youlai.boot.common.model.Option;
|
||||
import com.youlai.boot.framework.security.model.RoleDataScope;
|
||||
import com.youlai.boot.framework.security.model.SecurityUser;
|
||||
import com.youlai.boot.framework.security.token.TokenManager;
|
||||
import com.youlai.boot.framework.security.util.SecurityUtils;
|
||||
import com.youlai.boot.support.mail.EmailService;
|
||||
import com.youlai.boot.support.sms.SmsService;
|
||||
import com.youlai.boot.support.sms.SmsTypeEnum;
|
||||
import com.youlai.boot.system.enums.DictCodeEnum;
|
||||
import com.youlai.boot.system.model.entity.DictItem;
|
||||
import com.youlai.boot.system.model.entity.Role;
|
||||
import com.youlai.boot.system.model.form.*;
|
||||
import com.youlai.boot.system.model.query.UserQuery;
|
||||
import com.youlai.boot.system.model.vo.CurrentUserVO;
|
||||
import com.youlai.boot.system.model.vo.UserExportVO;
|
||||
import com.youlai.boot.system.model.vo.UserPageVO;
|
||||
import com.youlai.boot.system.model.vo.UserProfileVO;
|
||||
import com.youlai.boot.system.service.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* app用户业务实现类
|
||||
*
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ClientUserServiceImpl extends ServiceImpl<ClientUserMapper, ClientUser> implements ClientUserService {
|
||||
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
private final UserRoleService userRoleService;
|
||||
|
||||
private final DeptService deptService;
|
||||
|
||||
private final RoleService roleService;
|
||||
|
||||
private final RoleMenuService roleMenuService;
|
||||
|
||||
private final SmsService smsService;
|
||||
|
||||
private final EmailService mailService;
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
|
||||
private final TokenManager tokenManager;
|
||||
|
||||
private final DictItemService dictItemService;
|
||||
|
||||
private final ClientUserConverter userConverter;
|
||||
|
||||
|
||||
/**
|
||||
* 获取用户分页列表
|
||||
*
|
||||
* @param queryParams 查询参数
|
||||
* @return {@link IPage<UserPageVO>} 用户分页列表
|
||||
*/
|
||||
@Override
|
||||
public IPage<UserPageVO> getUserPage(UserQuery queryParams) {
|
||||
|
||||
// 参数构建
|
||||
int pageNum = queryParams.getPageNum();
|
||||
int pageSize = queryParams.getPageSize();
|
||||
Page<UserPageVO> page = new Page<>(pageNum, pageSize);
|
||||
|
||||
boolean isRoot = SecurityUtils.isRoot();
|
||||
queryParams.setIsRoot(isRoot);
|
||||
|
||||
// 查询数据
|
||||
return this.baseMapper.getUserPage(page, queryParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户表单数据
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return {@link ClientUserForm} 用户表单数据
|
||||
*/
|
||||
@Override
|
||||
public ClientUserForm getUserFormData(Long userId) {
|
||||
return this.baseMapper.getUserFormData(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增用户
|
||||
*
|
||||
* @param userForm 用户表单对象
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean saveUser(ClientUserForm userForm) {
|
||||
|
||||
String username = userForm.getUsername();
|
||||
|
||||
// 实体转换 form->entity
|
||||
ClientUser entity = userConverter.toEntity(userForm);
|
||||
|
||||
// 检查用户名是否已存在
|
||||
long count = this.count(new LambdaQueryWrapper<ClientUser>()
|
||||
.eq(ClientUser::getUsername, username));
|
||||
Assert.isTrue(count == 0, "用户名已存在");
|
||||
|
||||
// 设置默认加密密码
|
||||
String defaultEncryptPwd = passwordEncoder.encode(SystemConstants.DEFAULT_PASSWORD);
|
||||
entity.setPassword(defaultEncryptPwd);
|
||||
// entity.setCreateBy(SecurityUtils.getUserId());
|
||||
|
||||
// 新增用户
|
||||
boolean result = this.save(entity);
|
||||
|
||||
if (result) {
|
||||
// 保存用户角色
|
||||
userRoleService.saveUserRoles(entity.getId(), userForm.getRoleIds());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param userForm 用户表单对象
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean updateUser(Long userId, ClientUserForm userForm) {
|
||||
|
||||
String username = userForm.getUsername();
|
||||
|
||||
// 获取原用户信息
|
||||
ClientUser oldUser = this.getById(userId);
|
||||
Assert.notNull(oldUser, "用户不存在");
|
||||
|
||||
// 检查用户名是否已存在(排除当前用户)
|
||||
long count = this.count(new LambdaQueryWrapper<ClientUser>()
|
||||
.eq(ClientUser::getUsername, username)
|
||||
.ne(ClientUser::getId, userId)
|
||||
);
|
||||
Assert.isTrue(count == 0, "用户名已存在");
|
||||
|
||||
// form -> entity
|
||||
ClientUser entity = userConverter.toEntity(userForm);
|
||||
// entity.setUpdateBy(SecurityUtils.getUserId());
|
||||
|
||||
// 修改用户
|
||||
boolean result = this.updateById(entity);
|
||||
|
||||
if (result) {
|
||||
// 保存用户角色
|
||||
userRoleService.saveUserRoles(entity.getId(), userForm.getRoleIds());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*
|
||||
* @param idsStr 用户ID,多个以英文逗号(,)分割
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean deleteUsers(String idsStr) {
|
||||
Assert.isTrue(StrUtil.isNotBlank(idsStr), "删除的用户数据为空");
|
||||
// 逻辑删除
|
||||
List<Long> ids = Arrays.stream(idsStr.split(","))
|
||||
.map(Long::parseLong)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
boolean result = this.removeByIds(ids);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户名获取认证凭证信息
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return 用户认证凭证信息 {@link SecurityUser}
|
||||
*/
|
||||
@Override
|
||||
public SecurityUser getAuthInfoByUsername(String username) {
|
||||
SecurityUser userAuthInfo = this.baseMapper.getAuthInfoByUsername(username);
|
||||
if (userAuthInfo != null) {
|
||||
Set<String> roles = userAuthInfo.getRoles();
|
||||
// 获取数据权限列表(用于并集策略)
|
||||
List<RoleDataScope> dataScopes = roleService.getRoleDataScopes(roles);
|
||||
userAuthInfo.setDataScopes(dataScopes);
|
||||
}
|
||||
return userAuthInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据手机号获取用户认证信息
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @return 用户认证信息
|
||||
*/
|
||||
@Override
|
||||
public SecurityUser getAuthInfoByMobile(String mobile) {
|
||||
if (StrUtil.isBlank(mobile)) {
|
||||
return null;
|
||||
}
|
||||
SecurityUser userAuthInfo = this.baseMapper.getAuthInfoByMobile(mobile);
|
||||
if (userAuthInfo != null) {
|
||||
Set<String> roles = userAuthInfo.getRoles();
|
||||
// 获取数据权限列表(用于并集策略)
|
||||
List<RoleDataScope> dataScopes = roleService.getRoleDataScopes(roles);
|
||||
userAuthInfo.setDataScopes(dataScopes);
|
||||
}
|
||||
return userAuthInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取导出用户列表
|
||||
*
|
||||
* @param queryParams 查询参数
|
||||
* @return {@link List<UserExportVO>} 导出用户列表
|
||||
*/
|
||||
@Override
|
||||
public List<UserExportVO> listExportUsers(UserQuery queryParams) {
|
||||
|
||||
boolean isRoot = SecurityUtils.isRoot();
|
||||
queryParams.setIsRoot(isRoot);
|
||||
|
||||
List<UserExportVO> exportUsers = this.baseMapper.listExportUsers(queryParams);
|
||||
if (CollectionUtil.isNotEmpty(exportUsers)) {
|
||||
//获取性别的字典项
|
||||
Map<String, String> genderMap = dictItemService.list(
|
||||
new LambdaQueryWrapper<DictItem>().eq(DictItem::getDictCode,
|
||||
DictCodeEnum.GENDER.getValue())
|
||||
).stream()
|
||||
.collect(Collectors.toMap(DictItem::getValue, DictItem::getLabel)
|
||||
);
|
||||
|
||||
exportUsers.forEach(item -> {
|
||||
String gender = item.getGender();
|
||||
if (StrUtil.isBlank(gender)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断map是否为空
|
||||
if (genderMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
item.setGender(genderMap.get(gender));
|
||||
});
|
||||
}
|
||||
return exportUsers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录用户信息
|
||||
*
|
||||
* @return {@link CurrentUserVO} 用户信息
|
||||
*/
|
||||
@Override
|
||||
public CurrentUserVO getCurrentUserInfo() {
|
||||
|
||||
String username = SecurityUtils.getUsername();
|
||||
|
||||
// 获取登录用户基础信息
|
||||
ClientUser user = this.getOne(new LambdaQueryWrapper<ClientUser>()
|
||||
.eq(ClientUser::getUsername, username)
|
||||
.select(
|
||||
ClientUser::getId,
|
||||
ClientUser::getUsername,
|
||||
ClientUser::getNickname,
|
||||
ClientUser::getAvatar,
|
||||
ClientUser::getGender
|
||||
// AppUser::getDeptId
|
||||
)
|
||||
);
|
||||
// entity->Vo
|
||||
CurrentUserVO userInfoVo = userConverter.toCurrentUserVo(user);
|
||||
|
||||
// 性别
|
||||
userInfoVo.setGender(user.getGender());
|
||||
|
||||
// 部门名称
|
||||
// if (user.getDeptId() != null) {
|
||||
// Dept dept = deptService.getById(user.getDeptId());
|
||||
// if (dept != null) {
|
||||
// userInfoVo.setDeptName(dept.getName());
|
||||
// }
|
||||
// }
|
||||
|
||||
// 用户角色集合
|
||||
Set<String> roles = SecurityUtils.getRoles();
|
||||
userInfoVo.setRoles(roles);
|
||||
|
||||
// 用户角色名称集合
|
||||
if (CollectionUtil.isNotEmpty(roles)) {
|
||||
Set<String> roleNames = roleService.list(new LambdaQueryWrapper<Role>()
|
||||
.in(Role::getCode, roles)
|
||||
.select(Role::getName)
|
||||
).stream()
|
||||
.map(Role::getName)
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
userInfoVo.setRoleNames(roleNames);
|
||||
}
|
||||
|
||||
// 用户权限集合
|
||||
if (CollectionUtil.isNotEmpty(roles)) {
|
||||
Set<String> perms = roleMenuService.getRolePermsByRoleCodes(roles);
|
||||
userInfoVo.setPerms(perms);
|
||||
}
|
||||
return userInfoVo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取个人中心用户信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return {@link UserProfileVO} 个人中心用户信息
|
||||
*/
|
||||
@Override
|
||||
public UserProfileVO getUserProfile(Long userId) {
|
||||
return this.baseMapper.getUserProfile(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改个人中心用户信息
|
||||
*
|
||||
* @param formData 表单数据
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
public boolean updateUserProfile(UserProfileForm formData) {
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
|
||||
if (formData.getNickname() == null && formData.getAvatar() == null && formData.getGender() == null) {
|
||||
throw new BusinessException("请修改至少一个字段");
|
||||
}
|
||||
|
||||
return this.update(new LambdaUpdateWrapper<ClientUser>()
|
||||
.eq(ClientUser::getId, userId)
|
||||
.set(formData.getNickname() != null, ClientUser::getNickname, formData.getNickname())
|
||||
.set(formData.getAvatar() != null, ClientUser::getAvatar, formData.getAvatar())
|
||||
.set(formData.getGender() != null, ClientUser::getGender, formData.getGender())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改指定用户密码
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param data 密码修改表单数据
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
public boolean changeUserPassword(Long userId, PasswordUpdateForm data) {
|
||||
|
||||
ClientUser user = this.getById(userId);
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
|
||||
String oldPassword = data.getOldPassword();
|
||||
|
||||
// 校验原密码
|
||||
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
|
||||
throw new BusinessException("原密码错误");
|
||||
}
|
||||
// 新旧密码不能相同
|
||||
if (passwordEncoder.matches(data.getNewPassword(), user.getPassword())) {
|
||||
throw new BusinessException("新密码不能与原密码相同");
|
||||
}
|
||||
|
||||
// 判断新密码和确认密码是否一致
|
||||
if (!Objects.equals(data.getNewPassword(), data.getConfirmPassword())) {
|
||||
throw new BusinessException("新密码和确认密码不一致");
|
||||
}
|
||||
|
||||
String newPassword = data.getNewPassword();
|
||||
boolean result = this.update(new LambdaUpdateWrapper<ClientUser>()
|
||||
.eq(ClientUser::getId, userId)
|
||||
.set(ClientUser::getPassword, passwordEncoder.encode(newPassword))
|
||||
);
|
||||
|
||||
if (result) {
|
||||
// 密码变更后,使当前用户的所有会话失效,强制重新登录
|
||||
tokenManager.invalidateUserSessions(userId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置指定用户密码
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param password 密码重置表单数据
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
public boolean resetUserPassword(Long userId, String password) {
|
||||
boolean result = this.update(new LambdaUpdateWrapper<ClientUser>()
|
||||
.eq(ClientUser::getId, userId)
|
||||
.set(ClientUser::getPassword, passwordEncoder.encode(password))
|
||||
);
|
||||
if (result) {
|
||||
// 管理员重置用户密码后,使该用户的所有会话失效
|
||||
tokenManager.invalidateUserSessions(userId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信验证码(绑定或更换手机号)
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
public boolean sendMobileCode(String mobile) {
|
||||
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
long mobileCount = this.count(new LambdaQueryWrapper<ClientUser>()
|
||||
.eq(ClientUser::getMobile, mobile)
|
||||
.ne(ClientUser::getId, currentUserId)
|
||||
);
|
||||
if (mobileCount > 0) {
|
||||
throw new BusinessException("手机号已被其他账号绑定");
|
||||
}
|
||||
|
||||
// String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000));
|
||||
// TODO 为了方便测试,验证码固定为 123456,实际开发中在配置了厂商短信服务后,可以使用上面的随机验证码
|
||||
String code = "123456";
|
||||
|
||||
Map<String, String> templateParams = new HashMap<>();
|
||||
templateParams.put("code", code);
|
||||
boolean result = smsService.send(mobile, SmsTypeEnum.CHANGE_MOBILE, templateParams);
|
||||
if (result) {
|
||||
// 缓存验证码,5分钟有效,用于更换手机号校验
|
||||
String redisCacheKey = StrUtil.format(RedisConstants.Captcha.MOBILE_CODE, mobile);
|
||||
redisTemplate.opsForValue().set(redisCacheKey, code, 5, TimeUnit.MINUTES);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定或更换手机号
|
||||
*
|
||||
* @param form 表单数据
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
public boolean bindOrChangeMobile(MobileUpdateForm form) {
|
||||
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
ClientUser currentUser = this.getById(currentUserId);
|
||||
|
||||
if (currentUser == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
|
||||
if (!passwordEncoder.matches(form.getPassword(), currentUser.getPassword())) {
|
||||
throw new BusinessException("当前密码错误");
|
||||
}
|
||||
|
||||
// 校验验证码
|
||||
String inputVerifyCode = form.getCode();
|
||||
String mobile = form.getMobile();
|
||||
|
||||
String cacheKey = StrUtil.format(RedisConstants.Captcha.MOBILE_CODE, mobile);
|
||||
|
||||
String cachedVerifyCode = redisTemplate.opsForValue().get(cacheKey);
|
||||
|
||||
if (StrUtil.isBlank(cachedVerifyCode)) {
|
||||
throw new BusinessException("验证码已过期");
|
||||
}
|
||||
if (!inputVerifyCode.equals(cachedVerifyCode)) {
|
||||
throw new BusinessException("验证码错误");
|
||||
}
|
||||
|
||||
long mobileCount = this.count(new LambdaQueryWrapper<ClientUser>()
|
||||
.eq(ClientUser::getMobile, mobile)
|
||||
.ne(ClientUser::getId, currentUserId)
|
||||
);
|
||||
if (mobileCount > 0) {
|
||||
throw new BusinessException("手机号已被其他账号绑定");
|
||||
}
|
||||
|
||||
redisTemplate.delete(cacheKey);
|
||||
|
||||
// 更新手机号码
|
||||
return this.update(
|
||||
new LambdaUpdateWrapper<ClientUser>()
|
||||
.eq(ClientUser::getId, currentUserId)
|
||||
.set(ClientUser::getMobile, mobile)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮箱验证码(绑定或更换邮箱)
|
||||
*
|
||||
* @param email 邮箱
|
||||
*/
|
||||
@Override
|
||||
public void sendEmailCode(String email) {
|
||||
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
long emailCount = this.count(new LambdaQueryWrapper<ClientUser>()
|
||||
.eq(ClientUser::getEmail, email)
|
||||
.ne(ClientUser::getId, currentUserId)
|
||||
);
|
||||
if (emailCount > 0) {
|
||||
throw new BusinessException("邮箱已被其他账号绑定");
|
||||
}
|
||||
|
||||
// String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000));
|
||||
// TODO 为了方便测试,验证码固定为 123456,实际开发中在配置了邮箱服务后,可以使用上面的随机验证码
|
||||
String code = "123456";
|
||||
|
||||
mailService.send(email, "邮箱验证码", "您的验证码为:" + code + ",请在5分钟内使用");
|
||||
// 缓存验证码,5分钟有效,用于更换邮箱校验
|
||||
String redisCacheKey = StrUtil.format(RedisConstants.Captcha.EMAIL_CODE, email);
|
||||
redisTemplate.opsForValue().set(redisCacheKey, code, 5, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改当前用户邮箱
|
||||
*
|
||||
* @param form 表单数据
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
public boolean bindOrChangeEmail(EmailUpdateForm form) {
|
||||
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
|
||||
ClientUser currentUser = this.getById(currentUserId);
|
||||
if (currentUser == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
|
||||
if (!passwordEncoder.matches(form.getPassword(), currentUser.getPassword())) {
|
||||
throw new BusinessException("当前密码错误");
|
||||
}
|
||||
|
||||
// 获取前端输入的验证码
|
||||
String inputVerifyCode = form.getCode();
|
||||
|
||||
// 获取缓存的验证码
|
||||
String email = form.getEmail();
|
||||
String redisCacheKey = StrUtil.format(RedisConstants.Captcha.EMAIL_CODE, email);
|
||||
String cachedVerifyCode = redisTemplate.opsForValue().get(redisCacheKey);
|
||||
|
||||
if (StrUtil.isBlank(cachedVerifyCode)) {
|
||||
throw new BusinessException("验证码已过期");
|
||||
}
|
||||
|
||||
if (!inputVerifyCode.equals(cachedVerifyCode)) {
|
||||
throw new BusinessException("验证码错误");
|
||||
}
|
||||
|
||||
long emailCount = this.count(new LambdaQueryWrapper<ClientUser>()
|
||||
.eq(ClientUser::getEmail, email)
|
||||
.ne(ClientUser::getId, currentUserId)
|
||||
);
|
||||
if (emailCount > 0) {
|
||||
throw new BusinessException("邮箱已被其他账号绑定");
|
||||
}
|
||||
|
||||
redisTemplate.delete(redisCacheKey);
|
||||
|
||||
// 更新邮箱地址
|
||||
return this.update(
|
||||
new LambdaUpdateWrapper<ClientUser>()
|
||||
.eq(ClientUser::getId, currentUserId)
|
||||
.set(ClientUser::getEmail, email)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑手机号
|
||||
*
|
||||
* @param form 表单数据
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
public boolean unbindMobile(PasswordVerifyForm form) {
|
||||
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
ClientUser currentUser = this.getById(currentUserId);
|
||||
|
||||
if (currentUser == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(currentUser.getMobile())) {
|
||||
throw new BusinessException("当前账号未绑定手机号");
|
||||
}
|
||||
|
||||
if (!passwordEncoder.matches(form.getPassword(), currentUser.getPassword())) {
|
||||
throw new BusinessException("当前密码错误");
|
||||
}
|
||||
|
||||
return this.update(new LambdaUpdateWrapper<ClientUser>()
|
||||
.eq(ClientUser::getId, currentUserId)
|
||||
.set(ClientUser::getMobile, null)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑邮箱
|
||||
*
|
||||
* @param form 表单数据
|
||||
* @return true|false
|
||||
*/
|
||||
@Override
|
||||
public boolean unbindEmail(PasswordVerifyForm form) {
|
||||
|
||||
Long currentUserId = SecurityUtils.getUserId();
|
||||
ClientUser currentUser = this.getById(currentUserId);
|
||||
|
||||
if (currentUser == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(currentUser.getEmail())) {
|
||||
throw new BusinessException("当前账号未绑定邮箱");
|
||||
}
|
||||
|
||||
if (!passwordEncoder.matches(form.getPassword(), currentUser.getPassword())) {
|
||||
throw new BusinessException("当前密码错误");
|
||||
}
|
||||
|
||||
return this.update(new LambdaUpdateWrapper<ClientUser>()
|
||||
.eq(ClientUser::getId, currentUserId)
|
||||
.set(ClientUser::getEmail, null)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户选项列表
|
||||
*
|
||||
* @return {@link List<Option<String>>} 用户选项列表
|
||||
*/
|
||||
// @Override
|
||||
// public List<Option<String>> listUserOptions() {
|
||||
// List<AppUser> list = this.list(new LambdaQueryWrapper<AppUser>()
|
||||
// .eq(AppUser::getStatus, 1)
|
||||
// );
|
||||
// return userConverter.toOptions(list);
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user