chore: 合并 master 分支
This commit is contained in:
@@ -20,6 +20,13 @@ public class BusinessException extends RuntimeException {
|
||||
this.resultCode = errorCode;
|
||||
}
|
||||
|
||||
|
||||
public BusinessException(IResultCode errorCode,String message) {
|
||||
super(message);
|
||||
this.resultCode = errorCode;
|
||||
}
|
||||
|
||||
|
||||
public BusinessException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* 全局系统异常处理器
|
||||
* <p>
|
||||
@@ -219,9 +218,9 @@ public class GlobalExceptionHandler {
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public <T> Result<T> handleBizException(BusinessException e) {
|
||||
log.error("biz exception: {}", e.getMessage());
|
||||
log.error("biz exception", e);
|
||||
if (e.getResultCode() != null) {
|
||||
return Result.failed(e.getResultCode());
|
||||
return Result.failed(e.getResultCode(), e.getMessage());
|
||||
}
|
||||
return Result.failed(e.getMessage());
|
||||
}
|
||||
@@ -239,8 +238,7 @@ public class GlobalExceptionHandler {
|
||||
|| e instanceof AuthenticationException) {
|
||||
throw e;
|
||||
}
|
||||
log.error("unknown exception: {}", e.getMessage());
|
||||
e.printStackTrace();
|
||||
log.error("unknown exception", e);
|
||||
return Result.failed(e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ package com.youlai.boot.common.result;
|
||||
/**
|
||||
* 响应码接口
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2022/2/18
|
||||
* @author Ray.Hao
|
||||
* @since 1.0.0
|
||||
**/
|
||||
public interface IResultCode {
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public enum ResultCode implements IResultCode, Serializable {
|
||||
VOICE_VERIFICATION_CODE_INPUT_ERROR("A0133", "语音校验码输入错误"),
|
||||
|
||||
USER_CERTIFICATE_EXCEPTION("A0140", "用户证件异常"),
|
||||
USER_CERTIFICATE_TYPE_NOT_SELECTED("A0141", "用户证<EFBFBD><EFBFBD>类型未选择"),
|
||||
USER_CERTIFICATE_TYPE_NOT_SELECTED("A0141", "用户证件类型未选择"),
|
||||
MAINLAND_ID_NUMBER_VERIFICATION_ILLEGAL("A0142", "大陆身份证编号校验非法"),
|
||||
|
||||
USER_BASIC_INFORMATION_VERIFICATION_FAILED("A0150", "用户基本信息校验失败"),
|
||||
@@ -67,7 +67,7 @@ public enum ResultCode implements IResultCode, Serializable {
|
||||
REFRESH_TOKEN_INVALID("A0231", "刷新令牌无效或已过期"),
|
||||
|
||||
// 验证码错误
|
||||
USER_VERIFICATION_CODE_ERROR("A0240", "用户验证码错误"),
|
||||
USER_VERIFICATION_CODE_ERROR("A0240", "验证码错误"),
|
||||
USER_VERIFICATION_CODE_ATTEMPT_LIMIT_EXCEEDED("A0241", "用户验证码尝试次数超限"),
|
||||
USER_VERIFICATION_CODE_EXPIRED("A0242", "用户验证码过期"),
|
||||
|
||||
@@ -127,12 +127,14 @@ public enum ResultCode implements IResultCode, Serializable {
|
||||
USER_RESOURCE_NOT_FOUND("A0606", "用户资源不存在"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
USER_UPLOAD_FILE_EXCEPTION("A0700", "用户上传文件异常"),
|
||||
USER_UPLOAD_FILE_TYPE_MISMATCH("A0701", "用户上传文件类型不匹配"),
|
||||
USER_UPLOAD_FILE_TOO_LARGE("A0702", "用户上传文件太大"),
|
||||
USER_UPLOAD_IMAGE_TOO_LARGE("A0703", "用户上传图片太大"),
|
||||
USER_UPLOAD_VIDEO_TOO_LARGE("A0704", "用户上传视频太大"),
|
||||
USER_UPLOAD_COMPRESSED_FILE_TOO_LARGE("A0705", "用户上传压缩文件太大"),
|
||||
UPLOAD_FILE_EXCEPTION("A0700", "上传文件异常"),
|
||||
UPLOAD_FILE_TYPE_MISMATCH("A0701", "上传文件类型不匹配"),
|
||||
UPLOAD_FILE_TOO_LARGE("A0702", "上传文件太大"),
|
||||
UPLOAD_IMAGE_TOO_LARGE("A0703", "上传图片太大"),
|
||||
UPLOAD_VIDEO_TOO_LARGE("A0704", "上传视频太大"),
|
||||
UPLOAD_COMPRESSED_FILE_TOO_LARGE("A0705", "上传压缩文件太大"),
|
||||
|
||||
DELETE_FILE_EXCEPTION("A0710", "删除文件异常"),
|
||||
|
||||
/** 二级宏观错误码 */
|
||||
USER_CURRENT_VERSION_EXCEPTION("A0800", "用户当前版本异常"),
|
||||
|
||||
37
src/main/java/com/youlai/boot/config/CaffeineConfig.java
Normal file
37
src/main/java/com/youlai/boot/config/CaffeineConfig.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package com.youlai.boot.config;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.caffeine.CaffeineCacheManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* caffeine缓存配置
|
||||
*
|
||||
* @author Theo
|
||||
* @since 2025-01-22 17:40:23
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class CaffeineConfig {
|
||||
|
||||
@Value("${spring.cache.caffeine.spec}")
|
||||
private String caffeineSpec;
|
||||
|
||||
/**
|
||||
* 缓存管理器
|
||||
*
|
||||
* @return CacheManager 缓存管理器
|
||||
*/
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
|
||||
Caffeine<Object, Object> caffeineBuilder = Caffeine.from(caffeineSpec);
|
||||
caffeineCacheManager.setCaffeine(caffeineBuilder);
|
||||
return caffeineCacheManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package com.youlai.boot.config;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import jakarta.validation.Validation;
|
||||
import jakarta.validation.Validator;
|
||||
@@ -25,20 +23,22 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import java.math.BigInteger;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* WebMvc 自动装配配置
|
||||
* Web 配置
|
||||
*
|
||||
* @author Ray
|
||||
* @author Ray.Hao
|
||||
* @since 2020/10/16
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
/**
|
||||
* 配置消息转换器
|
||||
*
|
||||
@@ -47,21 +47,26 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
|
||||
ObjectMapper objectMapper = jackson2HttpMessageConverter.getObjectMapper();
|
||||
objectMapper.registerModule(new JavaTimeModule());
|
||||
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
|
||||
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
// 处理 Long 和 BigInteger 类型,避免前端精度丢失问题
|
||||
// 注册 JavaTimeModule(替代手动注册 LocalDateTimeSerializer)
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
// 返回指定字符串格式
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DATE_TIME_FORMATTER));
|
||||
// 反序列化,接受前端传来的格式
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER));
|
||||
objectMapper.registerModule(javaTimeModule);
|
||||
|
||||
// 配置全局日期格式和时区
|
||||
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
|
||||
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
|
||||
|
||||
// 处理 Long/BigInteger 的精度问题
|
||||
SimpleModule simpleModule = new SimpleModule();
|
||||
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
|
||||
simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
|
||||
simpleModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(
|
||||
DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN).withZone(ZoneId.of( "GMT+8"))
|
||||
));
|
||||
objectMapper.registerModule(simpleModule);
|
||||
objectMapper.setDateFormat(new SimpleDateFormat(DatePattern.NORM_DATETIME_PATTERN));
|
||||
|
||||
|
||||
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
|
||||
converters.add(1, jackson2HttpMessageConverter);
|
||||
|
||||
@@ -3,9 +3,11 @@ package com.youlai.boot.core.aspect;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.date.TimeInterval;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.excel.util.StringUtils;
|
||||
import com.aliyun.oss.HttpMethod;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
import com.youlai.boot.common.util.IPUtils;
|
||||
@@ -21,6 +23,7 @@ import org.aspectj.lang.annotation.AfterReturning;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
@@ -37,14 +40,18 @@ import java.util.Objects;
|
||||
* @author Ray.Hao
|
||||
* @since 2024/6/25
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class LogAspect {
|
||||
private final LogService logService;
|
||||
private final HttpServletRequest request;
|
||||
private final CacheManager cacheManager;
|
||||
|
||||
/**
|
||||
* 切点
|
||||
*/
|
||||
@Pointcut("@annotation(com.youlai.boot.common.annotation.Log)")
|
||||
public void logPointcut() {
|
||||
}
|
||||
@@ -72,7 +79,12 @@ public class LogAspect {
|
||||
}
|
||||
|
||||
/**
|
||||
* 保持日志
|
||||
* 保存日志
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param e 异常
|
||||
* @param jsonResult 响应结果
|
||||
* @param logAnnotation 日志注解
|
||||
*/
|
||||
private void saveLog(final JoinPoint joinPoint, final Exception e, Object jsonResult, com.youlai.boot.common.annotation.Log logAnnotation) {
|
||||
String requestURI = request.getRequestURI();
|
||||
@@ -120,8 +132,8 @@ public class LogAspect {
|
||||
log.setExecutionTime(executionTime);
|
||||
// 获取浏览器和终端系统信息
|
||||
String userAgentString = request.getHeader("User-Agent");
|
||||
UserAgent userAgent = UserAgentUtil.parse(userAgentString);
|
||||
if(Objects.nonNull(userAgent)) {
|
||||
UserAgent userAgent = resolveUserAgent(userAgentString);
|
||||
if (Objects.nonNull(userAgent)) {
|
||||
// 系统信息
|
||||
log.setOs(userAgent.getOs().getName());
|
||||
// 浏览器信息
|
||||
@@ -193,4 +205,27 @@ public class LogAspect {
|
||||
return obj instanceof MultipartFile || obj instanceof HttpServletRequest || obj instanceof HttpServletResponse;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析UserAgent
|
||||
*
|
||||
* @param userAgentString UserAgent字符串
|
||||
* @return UserAgent
|
||||
*/
|
||||
public UserAgent resolveUserAgent(String userAgentString) {
|
||||
if (StringUtils.isBlank(userAgentString)) {
|
||||
return null;
|
||||
}
|
||||
// 给userAgentStringMD5加密一次防止过长
|
||||
String userAgentStringMD5 = DigestUtil.md5Hex(userAgentString);
|
||||
//判断是否命中缓存
|
||||
UserAgent userAgent = Objects.requireNonNull(cacheManager.getCache("userAgent")).get(userAgentStringMD5, UserAgent.class);
|
||||
if (userAgent != null) {
|
||||
return userAgent;
|
||||
}
|
||||
userAgent = UserAgentUtil.parse(userAgentString);
|
||||
Objects.requireNonNull(cacheManager.getCache("userAgent")).put(userAgentStringMD5, userAgent);
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.youlai.boot.core.security.exception;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
|
||||
/**
|
||||
* 验证码校验异常
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2025/3/1
|
||||
*/
|
||||
public class CaptchaValidationException extends AuthenticationException {
|
||||
public CaptchaValidationException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,9 @@ package com.youlai.boot.core.security.exception;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.common.util.ResponseUtils;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.InsufficientAuthenticationException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -15,26 +14,35 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 未认证异常处理器
|
||||
* 统一处理 Spring Security 认证失败响应
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
/**
|
||||
* 认证失败处理入口方法
|
||||
*
|
||||
* @param request 触发异常的请求对象(可用于获取请求头、参数等)
|
||||
* @param response 响应对象(用于写入错误信息)
|
||||
* @param authException 认证异常对象(包含具体失败原因)
|
||||
*/
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
|
||||
int status = response.getStatus();
|
||||
if (status == HttpServletResponse.SC_NOT_FOUND) {
|
||||
// 资源不存在
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.USER_RESOURCE_NOT_FOUND);
|
||||
if (authException instanceof BadCredentialsException) {
|
||||
// 用户名或密码错误
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.USER_PASSWORD_ERROR);
|
||||
} else if(authException instanceof InsufficientAuthenticationException){
|
||||
// 请求头缺失Authorization、Token格式错误、Token过期、签名验证失败
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.ACCESS_TOKEN_INVALID);
|
||||
} else {
|
||||
if (authException instanceof BadCredentialsException) {
|
||||
// 用户名或密码错误
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.USER_PASSWORD_ERROR, authException.getMessage());
|
||||
} else {
|
||||
// 登录异常
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.USER_LOGIN_EXCEPTION, authException.getMessage());
|
||||
}
|
||||
// 其他未明确处理的认证异常(如账户被锁定、账户禁用等)
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.USER_LOGIN_EXCEPTION, authException.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ package com.youlai.boot.core.security.extension.sms;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.core.security.exception.CaptchaValidationException;
|
||||
import com.youlai.boot.core.security.model.SysUserDetails;
|
||||
import com.youlai.boot.system.model.dto.UserAuthInfo;
|
||||
import com.youlai.boot.system.service.UserService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
@@ -64,7 +64,7 @@ public class SmsAuthenticationProvider implements AuthenticationProvider {
|
||||
String cachedVerifyCode = (String) redisTemplate.opsForValue().get(RedisConstants.SMS_LOGIN_CODE_PREFIX + mobile);
|
||||
|
||||
if (!StrUtil.equals(inputVerifyCode, cachedVerifyCode)) {
|
||||
throw new BadCredentialsException("验证码错误");
|
||||
throw new CaptchaValidationException("验证码错误");
|
||||
} else {
|
||||
// 验证成功后删除验证码
|
||||
redisTemplate.delete(RedisConstants.SMS_LOGIN_CODE_PREFIX + mobile);
|
||||
|
||||
@@ -188,7 +188,7 @@ public class JwtTokenManager implements TokenManager {
|
||||
*
|
||||
* @param authentication 认证信息
|
||||
* @param ttl 过期时间
|
||||
* @return
|
||||
* @return JWT Token
|
||||
*/
|
||||
private String generateToken(Authentication authentication, int ttl) {
|
||||
|
||||
|
||||
@@ -3,6 +3,13 @@ package com.youlai.boot.shared.file.model;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
/**
|
||||
* 文件信息对象
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Schema(description = "文件对象")
|
||||
@Data
|
||||
public class FileInfo {
|
||||
|
||||
@@ -62,8 +62,10 @@ public class AliyunFileService implements FileService {
|
||||
@SneakyThrows
|
||||
public FileInfo uploadFile(MultipartFile file) {
|
||||
|
||||
// 获取文件名称
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
// 生成文件名(日期文件夹)
|
||||
String suffix = FileUtil.getSuffix(file.getOriginalFilename());
|
||||
String suffix = FileUtil.getSuffix(originalFilename);
|
||||
String uuid = IdUtil.simpleUUID();
|
||||
String fileName = DateUtil.format(LocalDateTime.now(), "yyyyMMdd") + "/" + uuid + "." + suffix;
|
||||
// try-with-resource 语法糖自动释放流
|
||||
@@ -82,7 +84,7 @@ public class AliyunFileService implements FileService {
|
||||
// 获取文件访问路径
|
||||
String fileUrl = "https://" + bucketName + "." + endpoint + "/" + fileName;
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
fileInfo.setName(fileName);
|
||||
fileInfo.setName(originalFilename);
|
||||
fileInfo.setUrl(fileUrl);
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.youlai.boot.shared.file.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;
|
||||
@@ -7,6 +8,7 @@ import com.youlai.boot.shared.file.model.FileInfo;
|
||||
import com.youlai.boot.shared.file.service.FileService;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@@ -23,40 +25,56 @@ 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
|
||||
@Data
|
||||
public class LocalFileService implements FileService {
|
||||
|
||||
@Value("${oss.local.storage-path}")
|
||||
private String storagePath;
|
||||
|
||||
/**
|
||||
* 上传文件方法
|
||||
*
|
||||
* @param file 表单文件对象
|
||||
* @return 文件信息
|
||||
*/
|
||||
@Override
|
||||
public FileInfo uploadFile(MultipartFile file) {
|
||||
// 获取文件名
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
// 获取文件后缀
|
||||
String suffix = FileUtil.getSuffix(originalFilename);
|
||||
// 生成uuid
|
||||
String fileName = IdUtil.simpleUUID()+ "." + suffix;;
|
||||
// 生成文件名(日期文件夹)
|
||||
String suffix = FileUtil.getSuffix(file.getOriginalFilename());
|
||||
String uuid = IdUtil.simpleUUID();
|
||||
String folder = DateUtil.format(LocalDateTime.now(), "yyyyMMdd") + File.separator;
|
||||
String fileName = uuid + "." + 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+ fileName);
|
||||
FileUtil.writeFromStream(inputStream, filePrefix + folder + File.separator + fileName);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("文件上传失败", e);
|
||||
throw new RuntimeException("文件上传失败");
|
||||
}
|
||||
// 获取文件访问路径,因为这里是本地存储,所以直接返回文件的相对路径,需要前端自行处理访问前缀
|
||||
String fileUrl = File.separator +folder+File.separator + fileName;
|
||||
String fileUrl = File.separator + folder + File.separator + fileName;
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
fileInfo.setName(fileName);
|
||||
fileInfo.setName(originalFilename);
|
||||
fileInfo.setUrl(fileUrl);
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param filePath 文件完整URL
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
@Override
|
||||
public boolean deleteFile(String filePath) {
|
||||
//判断文件是否为空
|
||||
|
||||
@@ -5,30 +5,29 @@ import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.shared.file.service.FileService;
|
||||
import com.youlai.boot.common.exception.BusinessException;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.shared.file.model.FileInfo;
|
||||
import com.youlai.boot.shared.file.service.FileService;
|
||||
import io.minio.*;
|
||||
import io.minio.errors.*;
|
||||
import io.minio.http.Method;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* MinIO 文件上传服务类
|
||||
*
|
||||
* @author haoxr
|
||||
* @author Ray.Hao
|
||||
* @since 2023/6/2
|
||||
*/
|
||||
@Component
|
||||
@@ -36,6 +35,7 @@ import java.time.LocalDateTime;
|
||||
@ConfigurationProperties(prefix = "oss.minio")
|
||||
@RequiredArgsConstructor
|
||||
@Data
|
||||
@Slf4j
|
||||
public class MinioFileService implements FileService {
|
||||
|
||||
/**
|
||||
@@ -77,7 +77,7 @@ public class MinioFileService implements FileService {
|
||||
* 上传文件
|
||||
*
|
||||
* @param file 表单文件对象
|
||||
* @return
|
||||
* @return 文件信息
|
||||
*/
|
||||
@Override
|
||||
public FileInfo uploadFile(MultipartFile file) {
|
||||
@@ -85,16 +85,21 @@ public class MinioFileService implements FileService {
|
||||
// 创建存储桶(存储桶不存在),如果有搭建好的minio服务,建议放在init方法中
|
||||
createBucketIfAbsent(bucketName);
|
||||
|
||||
// 生成文件名(日期文件夹)
|
||||
String suffix = FileUtil.getSuffix(file.getOriginalFilename());
|
||||
String uuid = IdUtil.simpleUUID();
|
||||
String fileName = DateUtil.format(LocalDateTime.now(), "yyyyMMdd") + "/" + uuid + "." + suffix;
|
||||
// 文件原生名称
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
// 文件后缀
|
||||
String suffix = FileUtil.getSuffix(originalFilename);
|
||||
// 文件夹名称
|
||||
String dateFolder = DateUtil.format(LocalDateTime.now(), "yyyyMMdd");
|
||||
// 文件名称
|
||||
String fileName = IdUtil.simpleUUID() + "." + suffix;
|
||||
|
||||
// try-with-resource 语法糖自动释放流
|
||||
try (InputStream inputStream = file.getInputStream()) {
|
||||
// 文件上传
|
||||
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.object(fileName)
|
||||
.object(dateFolder + "/"+ fileName)
|
||||
.contentType(file.getContentType())
|
||||
.stream(inputStream, inputStream.available(), -1)
|
||||
.build();
|
||||
@@ -104,23 +109,27 @@ public class MinioFileService implements FileService {
|
||||
String fileUrl;
|
||||
// 未配置自定义域名
|
||||
if (StrUtil.isBlank(customDomain)) {
|
||||
// 获取文件URL
|
||||
GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder()
|
||||
.bucket(bucketName).object(fileName)
|
||||
.bucket(bucketName)
|
||||
.object(dateFolder + "/"+ fileName)
|
||||
.method(Method.GET)
|
||||
.build();
|
||||
|
||||
fileUrl = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs);
|
||||
fileUrl = fileUrl.substring(0, fileUrl.indexOf("?"));
|
||||
} else { // 配置自定义文件路径域名
|
||||
fileUrl = customDomain + '/' + bucketName + "/" + fileName;
|
||||
} else {
|
||||
// 配置自定义文件路径域名
|
||||
fileUrl = customDomain + "/"+ bucketName + "/"+ dateFolder + "/"+ fileName;
|
||||
}
|
||||
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
fileInfo.setName(fileName);
|
||||
fileInfo.setName(originalFilename);
|
||||
fileInfo.setUrl(fileUrl);
|
||||
return fileInfo;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("文件上传失败");
|
||||
log.error("上传文件失败", e);
|
||||
throw new BusinessException(ResultCode.UPLOAD_FILE_EXCEPTION, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,9 +137,8 @@ public class MinioFileService implements FileService {
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param filePath 文件路径 http://localhost:9000/default/20221120/test.jpg
|
||||
*
|
||||
* @return
|
||||
* @param filePath 文件完整路径
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
@Override
|
||||
public boolean deleteFile(String filePath) {
|
||||
@@ -152,7 +160,8 @@ public class MinioFileService implements FileService {
|
||||
minioClient.removeObject(removeObjectArgs);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("文件删除失败", e);
|
||||
log.error("删除文件失败", e);
|
||||
throw new BusinessException(ResultCode.DELETE_FILE_EXCEPTION, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,17 +170,11 @@ public class MinioFileService implements FileService {
|
||||
* PUBLIC桶策略
|
||||
* 如果不配置,则新建的存储桶默认是PRIVATE,则存储桶文件会拒绝访问 Access Denied
|
||||
*
|
||||
* @param bucketName
|
||||
* @return
|
||||
* @param bucketName 存储桶名称
|
||||
* @return 存储桶策略
|
||||
*/
|
||||
private static String publicBucketPolicy(String bucketName) {
|
||||
/**
|
||||
* AWS的S3存储桶策略
|
||||
* Principal: 生效用户对象
|
||||
* Resource: 指定存储桶
|
||||
* Action: 操作行为
|
||||
*/
|
||||
|
||||
// AWS的S3存储桶策略 JSON 格式 https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/example-bucket-policies.html
|
||||
return "{\"Version\":\"2012-10-17\","
|
||||
+ "\"Statement\":[{\"Effect\":\"Allow\","
|
||||
+ "\"Principal\":{\"AWS\":[\"*\"]},"
|
||||
@@ -185,7 +188,7 @@ public class MinioFileService implements FileService {
|
||||
/**
|
||||
* 创建存储桶(存储桶不存在)
|
||||
*
|
||||
* @param bucketName
|
||||
* @param bucketName 存储桶名称
|
||||
*/
|
||||
@SneakyThrows
|
||||
private void createBucketIfAbsent(String bucketName) {
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.List;
|
||||
/**
|
||||
* 日志控制层
|
||||
*
|
||||
* @author Ray
|
||||
* @author Ray.Hao
|
||||
* @since 2.10.0
|
||||
*/
|
||||
@Tag(name = "13.日志接口")
|
||||
|
||||
@@ -25,11 +25,11 @@ public interface UserMapper extends BaseMapper<User> {
|
||||
/**
|
||||
* 获取用户分页列表
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param page 分页参数
|
||||
* @param queryParams 查询参数
|
||||
* @return 用户分页列表
|
||||
*/
|
||||
@DataPermission(deptAlias = "u")
|
||||
@DataPermission(deptAlias = "u", userAlias = "u")
|
||||
Page<UserBO> getUserPage(Page<UserBO> page, UserPageQuery queryParams);
|
||||
|
||||
/**
|
||||
@@ -70,7 +70,7 @@ public interface UserMapper extends BaseMapper<User> {
|
||||
* @param queryParams 查询参数
|
||||
* @return 导出用户列表
|
||||
*/
|
||||
@DataPermission(deptAlias = "u")
|
||||
@DataPermission(deptAlias = "u", userAlias = "u")
|
||||
List<UserExportDTO> listExportUsers(UserPageQuery queryParams);
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package com.youlai.boot.system.model.bo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户持久化对象
|
||||
@@ -67,6 +66,5 @@ public class UserBO {
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date createTime;
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.youlai.boot.system.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -28,6 +28,9 @@ public class DictForm {
|
||||
@Schema(description = "字典编码", example ="gender")
|
||||
private String dictCode;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "字典状态(1-启用,0-禁用)", example = "1")
|
||||
@Range(min = 0, max = 1, message = "字典状态不正确")
|
||||
private Integer status;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.youlai.boot.system.model.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@@ -14,9 +15,11 @@ import lombok.Data;
|
||||
public class EmailUpdateForm {
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
@NotBlank(message = "邮箱不能为空")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "验证码")
|
||||
@NotBlank(message = "验证码不能为空")
|
||||
private String code;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.youlai.boot.system.model.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@@ -14,9 +15,11 @@ import lombok.Data;
|
||||
public class MobileUpdateForm {
|
||||
|
||||
@Schema(description = "手机号码")
|
||||
@NotBlank(message = "手机号码不能为空")
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "验证码")
|
||||
@NotBlank(message = "验证码不能为空")
|
||||
private String code;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.youlai.boot.system.model.form;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 重置密码表单
|
||||
*
|
||||
* @author Ray
|
||||
* @since 2024/8/13
|
||||
*/
|
||||
@Schema(description = "重置密码表单")
|
||||
@Data
|
||||
public class PasswordResetForm {
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "密码")
|
||||
private String password;
|
||||
|
||||
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户分页视图对象
|
||||
@@ -48,6 +48,6 @@ public class UserPageVO {
|
||||
|
||||
@Schema(description="创建时间")
|
||||
@JsonFormat(pattern = "yyyy/MM/dd HH:mm")
|
||||
private Date createTime;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.List;
|
||||
/**
|
||||
* 访问趋势VO
|
||||
*
|
||||
* @author Ray
|
||||
* @author Ray.Hao
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@Schema(description = "访问趋势VO")
|
||||
@@ -24,9 +24,6 @@ public class VisitTrendVO {
|
||||
@Schema(description = "浏览量(PV)")
|
||||
private List<Integer> pvList;
|
||||
|
||||
@Schema(description = "访客数(UV)")
|
||||
private List<Integer> uvList;
|
||||
|
||||
@Schema(description = "IP数")
|
||||
private List<Integer> ipList;
|
||||
|
||||
|
||||
@@ -241,13 +241,13 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements Me
|
||||
|
||||
MenuTypeEnum menuType = menuForm.getType();
|
||||
|
||||
if (menuType == MenuTypeEnum.CATALOG) { // 如果是外链
|
||||
if (menuType == MenuTypeEnum.CATALOG) { // 如果是目录
|
||||
String path = menuForm.getRoutePath();
|
||||
if (menuForm.getParentId() == 0 && !path.startsWith("/")) {
|
||||
menuForm.setRoutePath("/" + path); // 一级目录需以 / 开头
|
||||
}
|
||||
menuForm.setComponent("Layout");
|
||||
} else if (menuType == MenuTypeEnum.EXTLINK) { // 如果是目录
|
||||
} else if (menuType == MenuTypeEnum.EXTLINK) { // 如果是外链
|
||||
|
||||
menuForm.setComponent(null);
|
||||
}
|
||||
|
||||
@@ -50,11 +50,8 @@ import java.util.stream.Collectors;
|
||||
public class NoticeServiceImpl extends ServiceImpl<NoticeMapper, Notice> implements NoticeService {
|
||||
|
||||
private final NoticeConverter noticeConverter;
|
||||
|
||||
private final UserNoticeService userNoticeService;
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
private final SimpMessagingTemplate messagingTemplate;
|
||||
private final OnlineUserService onlineUserService;
|
||||
|
||||
@@ -266,10 +263,9 @@ public class NoticeServiceImpl extends ServiceImpl<NoticeMapper, Notice> impleme
|
||||
}
|
||||
|
||||
/**
|
||||
* 阅读获取通知公告详情
|
||||
*
|
||||
* @param id 通知公告ID
|
||||
* @return
|
||||
* @return NoticeDetailVO 通知公告详情
|
||||
*/
|
||||
@Override
|
||||
public NoticeDetailVO getNoticeDetail(Long id) {
|
||||
|
||||
@@ -67,7 +67,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements Ro
|
||||
.like(Role::getCode, keywords)
|
||||
)
|
||||
.ne(!SecurityUtils.isRoot(), Role::getCode, SystemConstants.ROOT_ROLE_CODE) // 非超级管理员不显示超级管理员角色
|
||||
.orderByDesc(Role::getCreateTime).orderByDesc(Role::getUpdateTime)
|
||||
.orderByAsc(Role::getSort).orderByDesc(Role::getCreateTime).orderByDesc(Role::getUpdateTime)
|
||||
);
|
||||
|
||||
// 实体转换
|
||||
|
||||
@@ -10,29 +10,29 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.common.constant.SystemConstants;
|
||||
import com.youlai.boot.core.security.manager.TokenManager;
|
||||
import com.youlai.boot.common.exception.BusinessException;
|
||||
import com.youlai.boot.common.model.Option;
|
||||
import com.youlai.boot.core.security.manager.TokenManager;
|
||||
import com.youlai.boot.core.security.service.PermissionService;
|
||||
import com.youlai.boot.core.security.util.SecurityUtils;
|
||||
import com.youlai.boot.shared.mail.service.MailService;
|
||||
import com.youlai.boot.shared.sms.enums.SmsTypeEnum;
|
||||
import com.youlai.boot.shared.sms.service.SmsService;
|
||||
import com.youlai.boot.system.converter.UserConverter;
|
||||
import com.youlai.boot.system.enums.DictCodeEnum;
|
||||
import com.youlai.boot.system.mapper.UserMapper;
|
||||
import com.youlai.boot.system.model.bo.UserBO;
|
||||
import com.youlai.boot.system.model.dto.UserAuthInfo;
|
||||
import com.youlai.boot.system.model.dto.UserExportDTO;
|
||||
import com.youlai.boot.system.model.entity.DictData;
|
||||
import com.youlai.boot.system.model.entity.User;
|
||||
import com.youlai.boot.system.model.entity.UserRole;
|
||||
import com.youlai.boot.system.model.form.*;
|
||||
import com.youlai.boot.system.converter.UserConverter;
|
||||
import com.youlai.boot.common.exception.BusinessException;
|
||||
import com.youlai.boot.system.model.vo.UserProfileVO;
|
||||
import com.youlai.boot.core.security.util.SecurityUtils;
|
||||
import com.youlai.boot.system.mapper.UserMapper;
|
||||
import com.youlai.boot.system.model.dto.UserAuthInfo;
|
||||
import com.youlai.boot.system.model.bo.UserBO;
|
||||
import com.youlai.boot.system.model.query.UserPageQuery;
|
||||
import com.youlai.boot.system.model.dto.UserExportDTO;
|
||||
import com.youlai.boot.system.model.vo.UserInfoVO;
|
||||
import com.youlai.boot.system.model.vo.UserPageVO;
|
||||
import com.youlai.boot.core.security.service.PermissionService;
|
||||
import com.youlai.boot.system.service.RoleService;
|
||||
import com.youlai.boot.system.service.UserRoleService;
|
||||
import com.youlai.boot.system.service.UserService;
|
||||
import com.youlai.boot.system.model.vo.UserProfileVO;
|
||||
import com.youlai.boot.system.service.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
@@ -69,6 +69,8 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
|
||||
private final TokenManager tokenManager;
|
||||
|
||||
private final DictDataService dictDataService;
|
||||
|
||||
private final UserConverter userConverter;
|
||||
|
||||
/**
|
||||
@@ -274,7 +276,15 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
*/
|
||||
@Override
|
||||
public List<UserExportDTO> listExportUsers(UserPageQuery queryParams) {
|
||||
return this.baseMapper.listExportUsers(queryParams);
|
||||
List<UserExportDTO> userExportDTOS = this.baseMapper.listExportUsers(queryParams);
|
||||
//获取角色的字典数据
|
||||
List<DictData> list = dictDataService.list(new LambdaQueryWrapper<DictData>().eq(DictData::getDictCode, DictCodeEnum.GENDER.getValue()));
|
||||
Map<String, String> genderMap = list.stream().collect(Collectors.toMap(DictData::getValue, DictData::getLabel));
|
||||
userExportDTOS.forEach(userExportDTO -> {
|
||||
String genderLabel = genderMap.get(userExportDTO.getGender());
|
||||
userExportDTO.setGender(genderLabel);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -441,9 +451,14 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
String redisCacheKey = RedisConstants.SMS_CHANGE_CODE_PREFIX + mobile;
|
||||
String cachedVerifyCode = redisTemplate.opsForValue().get(redisCacheKey);
|
||||
|
||||
if (StrUtil.isBlank(cachedVerifyCode)) {
|
||||
throw new BusinessException("验证码已过期");
|
||||
}
|
||||
if (!inputVerifyCode.equals(cachedVerifyCode)) {
|
||||
throw new BusinessException("验证码错误");
|
||||
}
|
||||
// 验证完成删除验证码
|
||||
redisTemplate.delete(redisCacheKey);
|
||||
|
||||
// 更新手机号码
|
||||
return this.update(
|
||||
@@ -495,9 +510,15 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
||||
String redisCacheKey = RedisConstants.EMAIL_CHANGE_CODE_PREFIX + email;
|
||||
String cachedVerifyCode = redisTemplate.opsForValue().get(redisCacheKey);
|
||||
|
||||
if (StrUtil.isBlank(cachedVerifyCode)) {
|
||||
throw new BusinessException("验证码已过期");
|
||||
}
|
||||
|
||||
if (!inputVerifyCode.equals(cachedVerifyCode)) {
|
||||
throw new BusinessException("验证码错误");
|
||||
}
|
||||
// 验证完成删除验证码
|
||||
redisTemplate.delete(redisCacheKey);
|
||||
|
||||
// 更新邮箱地址
|
||||
return this.update(
|
||||
|
||||
Reference in New Issue
Block a user