diff --git a/pom.xml b/pom.xml index 8271620..448dcf0 100644 --- a/pom.xml +++ b/pom.xml @@ -1,46 +1,46 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.1.6 - - - com.aida - lanecarford - 0.0.1-SNAPSHOT - lanecarford - Demo project for Spring Boot - - - - - - - - - - - - - - - 21 - - - - - org.springframework.boot - spring-boot-starter-web - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.6 + + + com.aida + lanecarford + 0.0.1-SNAPSHOT + lanecarford + Demo project for Spring Boot + + + + + + + + + + + + + + + 21 + + + + + org.springframework.boot + spring-boot-starter-web + - - - org.springframework.boot - spring-boot-starter-security - + + + org.springframework.boot + spring-boot-starter-security + @@ -50,55 +50,53 @@ - - - com.baomidou - mybatis-plus-boot-starter - 3.5.5 - + + + com.baomidou + mybatis-plus-boot-starter + 3.5.5 + + + + com.mysql + mysql-connector-j + 8.2.0 + - - - com.mysql - mysql-connector-j - 8.2.0 - + + + org.projectlombok + lombok + true + - - - org.projectlombok - lombok - true - + + + org.springframework.boot + spring-boot-starter-validation + - - - org.springframework.boot - spring-boot-starter-validation - + + + org.springframework.boot + spring-boot-starter-actuator + - - - org.springframework.boot - spring-boot-starter-actuator - - - - - com.fasterxml.jackson.core - jackson-databind - + + + com.fasterxml.jackson.core + jackson-databind + - - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.6.0 - + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.6.0 + @@ -134,53 +132,111 @@ spring-boot-starter-data-redis - - - org.springframework.boot - spring-boot-starter-aop - + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-test + test + - - - org.springframework.boot - spring-boot-starter-test - test - + + org.springframework.security + spring-security-test + test + - - org.springframework.security - spring-security-test - test - - + + + io.jsonwebtoken + jjwt-api + 0.12.3 + + + + + io.jsonwebtoken + jjwt-impl + 0.12.3 + runtime + + + + + io.jsonwebtoken + jjwt-jackson + 0.12.3 + runtime + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + com.tencentcloudapi + tencentcloud-sdk-java-ses + 3.1.572 + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 21 + 21 + UTF-8 + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + + + + dev + + dev + + + true + + + + + prod + + prod + + + - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.11.0 - - 21 - 21 - UTF-8 - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - - diff --git a/src/main/java/com/aida/lanecarford/common/constant/RedisURIConstants.java b/src/main/java/com/aida/lanecarford/common/constant/RedisURIConstants.java new file mode 100644 index 0000000..b0b6448 --- /dev/null +++ b/src/main/java/com/aida/lanecarford/common/constant/RedisURIConstants.java @@ -0,0 +1,12 @@ +package com.aida.lanecarford.common.constant; + +public class RedisURIConstants { + + public static final String tokenCache = "TokenCache:"; + + public static final String verifyCodeCache = "VerifyCodeCache:"; + // 验证码 10分钟过期 + public static final Long verifyCodeTimeout = 10 * 60L; + + +} diff --git a/src/main/java/com/aida/lanecarford/common/enums/AuthenticationOperationTypeEnum.java b/src/main/java/com/aida/lanecarford/common/enums/AuthenticationOperationTypeEnum.java new file mode 100644 index 0000000..f34ba1d --- /dev/null +++ b/src/main/java/com/aida/lanecarford/common/enums/AuthenticationOperationTypeEnum.java @@ -0,0 +1,37 @@ +package com.aida.lanecarford.common.enums; + +import java.util.stream.Stream; + +/** + * @description: 操作类型 登入 忘记密码 + **/ +public enum AuthenticationOperationTypeEnum { + /** + * 登入 + */ + LOGIN, + /** + * 异常ip + */ + EXCEPTION_IP, + /** + * 绑定邮箱 + */ + BIND_MAILBOX, + /** + * 忘记密码 + */ + FORGET_PWD, + /** + * 更改邮箱 + */ + CHANGE_MAILBOX, + /** + * 注册 + */ + REGISTER; + + public static AuthenticationOperationTypeEnum of(String name) { + return Stream.of(AuthenticationOperationTypeEnum.values()).filter(v -> v.name().equals(name)).findFirst().orElse(null); + } +} diff --git a/src/main/java/com/aida/lanecarford/common/enums/LanguageEnum.java b/src/main/java/com/aida/lanecarford/common/enums/LanguageEnum.java new file mode 100644 index 0000000..2a11c96 --- /dev/null +++ b/src/main/java/com/aida/lanecarford/common/enums/LanguageEnum.java @@ -0,0 +1,8 @@ +package com.aida.lanecarford.common.enums; + +public enum LanguageEnum { + + CHINESE, + + ENGLISH; +} diff --git a/src/main/java/com/aida/lanecarford/common/security/JwtInterceptor.java b/src/main/java/com/aida/lanecarford/common/security/JwtInterceptor.java new file mode 100644 index 0000000..fac1e28 --- /dev/null +++ b/src/main/java/com/aida/lanecarford/common/security/JwtInterceptor.java @@ -0,0 +1,73 @@ +package com.aida.lanecarford.common.security; + +import com.aida.lanecarford.common.security.config.JwtProperties; +import com.aida.lanecarford.common.security.context.UserContext; +import com.aida.lanecarford.exception.BusinessException; +import com.aida.lanecarford.util.CacheUtil; +import com.aida.lanecarford.vo.AuthPrincipalVO; +import com.alibaba.fastjson.JSONObject; +import io.netty.util.internal.StringUtil; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.util.Objects; + +@Component +@Slf4j +public class JwtInterceptor implements HandlerInterceptor { + + @Resource + private CacheUtil cacheUtil; + + @Autowired + private JwtUtil jwtUtil; + + @Resource + private JwtProperties jwtProperties; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + return false; + } + + String jwtToken = request.getHeader(jwtProperties.getJwtTokenHeader()); + if (jwtToken == null || !jwtToken.startsWith(jwtProperties.getJwtTokenPrefix())) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } + + boolean validated = jwtUtil.validateToken(jwtToken); + if (validated) { + String extracted = jwtUtil.extractUserinfo(jwtToken); + if (StringUtil.isNullOrEmpty(extracted)) { + log.warn("TOKEN已过期,请重新登录!(token without userInfo)"); + throw new BusinessException("Token has expired, please log in again."); + } + + AuthPrincipalVO authPrincipalVO = JSONObject.parseObject(extracted, AuthPrincipalVO.class); + // 先清空当前线程变量,防止上一个线程遗留 + UserContext.delete(); + // 存取用户信息到缓存 + UserContext.setUserHolder(authPrincipalVO); + // 校验当前token与缓存中数据是否一致 + Object token = cacheUtil.getToken(authPrincipalVO.getId()); + + if (Objects.isNull(token)) { + log.warn("TOKEN已过期,请重新登录!(local cache empty)"); + throw new BusinessException("Token has expired, please log in again."); + } else if (!token.toString().equals(jwtToken)) { + log.warn("TOKEN已过期,请重新登录!(token not match local cache)"); + throw new BusinessException("Token has expired, please log in again."); + } + return true; + } + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } +} diff --git a/src/main/java/com/aida/lanecarford/common/security/JwtUtil.java b/src/main/java/com/aida/lanecarford/common/security/JwtUtil.java new file mode 100644 index 0000000..7781ea2 --- /dev/null +++ b/src/main/java/com/aida/lanecarford/common/security/JwtUtil.java @@ -0,0 +1,127 @@ +package com.aida.lanecarford.common.security; + +import com.aida.lanecarford.common.security.config.JwtProperties; +import com.aida.lanecarford.vo.AuthPrincipalVO; +import com.alibaba.fastjson.JSONObject; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.security.Keys; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.util.Date; + +import io.jsonwebtoken.*; + +@Slf4j +@Component +public class JwtUtil { + + @Resource + private JwtProperties jwtProperties; + + private SecretKey getSigningKey() { + return Keys.hmacShaKeyFor(jwtProperties.getJwtSecret().getBytes()); + } + + // 生成JWT token + public String generateToken(AuthPrincipalVO principal) { + return Jwts.builder() + .subject(JSONObject.toJSONString(principal)) + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + jwtProperties.getJwtExpiration())) + .signWith(getSigningKey()) + .compact(); + } + + // 从token中提取用户信息 + public String extractUserinfo(String token) { + return parseClaims(token).getSubject(); + } + + // 验证token是否有效 + public boolean validateToken(String token) { + try { + Claims claims = parser(token); + return claims != null && !claims.isEmpty() && !isTokenExpired(claims); + } catch (Exception e) { + log.debug("Token验证失败: {}", e.getMessage()); + return false; + } + } + + // 解析token - 适配 JJWT 0.12.x + public Claims parser(String token) { + try { + // 移除前缀 + if (token.startsWith(jwtProperties.getJwtTokenPrefix())) { + token = token.substring(jwtProperties.getJwtTokenPrefix().length()).trim(); + } + + return Jwts.parser() + .verifyWith(getSigningKey()) + .build() + .parseSignedClaims(token) + .getPayload(); + + } catch (ExpiredJwtException e) { + log.error("Token已过期: {}", e.getMessage()); + throw e; + } catch (SecurityException | MalformedJwtException e) { + log.error("Token格式错误: {}", e.getMessage()); + return null; + } catch (Exception e) { + log.error("解析Token失败: {}", e.getMessage()); + return null; + } + } + + // 检查token是否过期 + private boolean isTokenExpired(Claims claims) { + return claims.getExpiration().before(new Date()); + } + + // 解析Claims(共用方法)- 适配 JJWT 0.12.x + private Claims parseClaims(String token) { + return Jwts.parser() + .verifyWith(getSigningKey()) + .build() + .parseSignedClaims(token) + .getPayload(); + } + + // 新增:刷新token + public String refreshToken(String token) { + Claims claims = parser(token); + if (claims == null || isTokenExpired(claims)) { + throw new JwtException("无法刷新过期的token"); + } + + // 使用原始主题创建新token + return Jwts.builder() + .subject(claims.getSubject()) + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + jwtProperties.getJwtExpiration())) + .signWith(getSigningKey()) + .compact(); + } + + // 新增:获取token剩余时间(毫秒) + public long getRemainingTime(String token) { + try { + Claims claims = parser(token); + if (claims != null) { + long expirationTime = claims.getExpiration().getTime(); + long currentTime = System.currentTimeMillis(); + return Math.max(0, expirationTime - currentTime); + } + } catch (Exception e) { + log.debug("获取token剩余时间失败: {}", e.getMessage()); + } + return 0; + } +} diff --git a/src/main/java/com/aida/lanecarford/common/security/config/JwtProperties.java b/src/main/java/com/aida/lanecarford/common/security/config/JwtProperties.java new file mode 100644 index 0000000..8b6f40e --- /dev/null +++ b/src/main/java/com/aida/lanecarford/common/security/config/JwtProperties.java @@ -0,0 +1,22 @@ +package com.aida.lanecarford.common.security.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * JWT配置类 + */ +@Data +@ConfigurationProperties(prefix = "spring.security") +@Configuration +public class JwtProperties { + + private String jwtSecret; + + private String jwtTokenHeader; + + private String jwtTokenPrefix; + + private long jwtExpiration; +} diff --git a/src/main/java/com/aida/lanecarford/common/security/context/UserContext.java b/src/main/java/com/aida/lanecarford/common/security/context/UserContext.java new file mode 100644 index 0000000..47458ae --- /dev/null +++ b/src/main/java/com/aida/lanecarford/common/security/context/UserContext.java @@ -0,0 +1,19 @@ +package com.aida.lanecarford.common.security.context; + +import com.aida.lanecarford.vo.AuthPrincipalVO; + +public class UserContext { + private static ThreadLocal userHolder = new ThreadLocal(); + + public static AuthPrincipalVO getUserHolder() { + return userHolder.get(); + } + + public static void delete() { + userHolder.remove(); + } + + public static void setUserHolder(AuthPrincipalVO authPrincipalVo) { + userHolder.set(authPrincipalVo); + } +} diff --git a/src/main/java/com/aida/lanecarford/config/RedisConfig.java b/src/main/java/com/aida/lanecarford/config/RedisConfig.java new file mode 100644 index 0000000..9d80380 --- /dev/null +++ b/src/main/java/com/aida/lanecarford/config/RedisConfig.java @@ -0,0 +1,30 @@ +package com.aida.lanecarford.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(factory); + + // 使用Spring提供的GenericJackson2JsonRedisSerializer + GenericJackson2JsonRedisSerializer serializer = + new GenericJackson2JsonRedisSerializer(); + + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + template.afterPropertiesSet(); + + return template; + } +} diff --git a/src/main/java/com/aida/lanecarford/config/WebConfig.java b/src/main/java/com/aida/lanecarford/config/WebConfig.java index 5fe4125..c788fc5 100644 --- a/src/main/java/com/aida/lanecarford/config/WebConfig.java +++ b/src/main/java/com/aida/lanecarford/config/WebConfig.java @@ -1,13 +1,18 @@ package com.aida.lanecarford.config; +import com.aida.lanecarford.common.security.JwtInterceptor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.util.Arrays; + /** * Web配置类 - 纯API后端服务 - * + * * @author AI Assistant * @since 2024-01-01 */ @@ -27,6 +32,17 @@ public class WebConfig implements WebMvcConfigurer { .maxAge(3600); } + @Autowired + private JwtInterceptor jwtInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(jwtInterceptor) + .addPathPatterns("/api/protected/**") // 保护这些路径 + .excludePathPatterns(Arrays.asList("/api/auth/precheckAndSendEmail", "/api/auth/register", + "/api/auth/login", "/api/auth/forgotPwd")); // 排除登录接口 + } + /** * 配置资源处理 - 仅保留API文档和文件上传 */ @@ -35,11 +51,11 @@ public class WebConfig implements WebMvcConfigurer { // 配置上传文件的访问路径 registry.addResourceHandler("/uploads/**") .addResourceLocations("file:uploads/"); - + // 配置Swagger UI资源 registry.addResourceHandler("/swagger-ui/**") .addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/"); - + registry.addResourceHandler("/v3/api-docs/**") .addResourceLocations("classpath:/META-INF/resources/webjars/"); } diff --git a/src/main/java/com/aida/lanecarford/controller/LoginController.java b/src/main/java/com/aida/lanecarford/controller/LoginController.java new file mode 100644 index 0000000..98bdd3f --- /dev/null +++ b/src/main/java/com/aida/lanecarford/controller/LoginController.java @@ -0,0 +1,44 @@ +package com.aida.lanecarford.controller; + +import com.aida.lanecarford.common.ApiResponse; +import com.aida.lanecarford.dto.LoginRequest; +import com.aida.lanecarford.service.LoginService; +import com.aida.lanecarford.vo.LoginVO; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/auth") +public class LoginController { + + @Resource + private LoginService loginService; + + @PostMapping("/precheckAndSendEmail") + public ApiResponse preCheckAndSendEmail(@Valid @RequestBody LoginRequest loginRequest) { + loginService.preCheckAndSendEmail(loginRequest); + return ApiResponse.success(); + } + + @PostMapping("/register") + public ApiResponse register(@Valid @RequestBody LoginRequest loginRequest) { + return ApiResponse.success(loginService.register(loginRequest)); + } + + @PostMapping("/login") + public ApiResponse login(@Valid @RequestBody LoginRequest loginRequest) { + return ApiResponse.success(loginService.login(loginRequest)); + } + + @PostMapping("/forgotPwd") + public ApiResponse forgotPwd(@Valid @RequestBody LoginRequest loginRequest) { + loginService.forgotPwd(loginRequest); + return ApiResponse.success(); + } + + +} diff --git a/src/main/java/com/aida/lanecarford/dto/LoginRequest.java b/src/main/java/com/aida/lanecarford/dto/LoginRequest.java new file mode 100644 index 0000000..b183671 --- /dev/null +++ b/src/main/java/com/aida/lanecarford/dto/LoginRequest.java @@ -0,0 +1,23 @@ +package com.aida.lanecarford.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Data +public class LoginRequest { + private String name; + + @NotBlank(message = "邮箱不能为空") + @Email(message = "邮箱格式不正确") + private String email; + + @NotBlank(message = "密码不能为空") + @Size(min = 6, message = "密码至少6位") + private String password; + + private String operationType; + + private String verifyCode; +} \ No newline at end of file diff --git a/src/main/java/com/aida/lanecarford/entity/User.java b/src/main/java/com/aida/lanecarford/entity/User.java new file mode 100644 index 0000000..03ec098 --- /dev/null +++ b/src/main/java/com/aida/lanecarford/entity/User.java @@ -0,0 +1,65 @@ +package com.aida.lanecarford.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 用户信息表 + * + * @author xupei + * @since 2025-10-21 + */ +@Data +@TableName("user") +public class User extends BaseEntity { + + /** + * 用户昵称 + */ + private String username; + + /** + * 用户邮箱 + */ + private String email; + /** + * 账号密码 + */ + private String password; + /** + * 用户性别 + */ + private String gender; + /** + * 员工编号 + */ + private String employeeId; + + /** + * 门店ID + */ + private String storeId; + + /** + * 门店名称 + */ + private String storeName; + + /** + * 手机号 + */ + private String phone; + /** + * 用户头像 + */ + private String avatar; + /** + * 系统语言 + */ + private String language; + /** + * 是否启用(0-禁用,1-启用) + */ + private Integer isActive; +} diff --git a/src/main/java/com/aida/lanecarford/mapper/UserMapper.java b/src/main/java/com/aida/lanecarford/mapper/UserMapper.java new file mode 100644 index 0000000..2145d53 --- /dev/null +++ b/src/main/java/com/aida/lanecarford/mapper/UserMapper.java @@ -0,0 +1,13 @@ +package com.aida.lanecarford.mapper; + +import com.aida.lanecarford.entity.User; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 用户信息Mapper接口 + * + * @author xupei + * @since 2025-10-21 + */ +public interface UserMapper extends BaseMapper { +} diff --git a/src/main/java/com/aida/lanecarford/service/LoginService.java b/src/main/java/com/aida/lanecarford/service/LoginService.java new file mode 100644 index 0000000..ad25577 --- /dev/null +++ b/src/main/java/com/aida/lanecarford/service/LoginService.java @@ -0,0 +1,25 @@ +package com.aida.lanecarford.service; + +import com.aida.lanecarford.dto.LoginRequest; +import com.aida.lanecarford.entity.User; +import com.aida.lanecarford.vo.LoginVO; +import com.baomidou.mybatisplus.extension.service.IService; +import org.springframework.stereotype.Service; + +/** + * 登录服务接口 + * + * @author xupei + * @since 2025-10-21 + */ +@Service +public interface LoginService extends IService { + + void preCheckAndSendEmail(LoginRequest loginRequest); + + LoginVO register(LoginRequest loginRequest); + + LoginVO login(LoginRequest loginRequest); + + void forgotPwd(LoginRequest loginRequest); +} diff --git a/src/main/java/com/aida/lanecarford/service/impl/LoginServiceImpl.java b/src/main/java/com/aida/lanecarford/service/impl/LoginServiceImpl.java new file mode 100644 index 0000000..fb491fe --- /dev/null +++ b/src/main/java/com/aida/lanecarford/service/impl/LoginServiceImpl.java @@ -0,0 +1,200 @@ +package com.aida.lanecarford.service.impl; + +import com.aida.lanecarford.common.constant.RedisURIConstants; +import com.aida.lanecarford.common.enums.AuthenticationOperationTypeEnum; +import com.aida.lanecarford.common.enums.LanguageEnum; +import com.aida.lanecarford.common.security.JwtUtil; +import com.aida.lanecarford.dto.LoginRequest; +import com.aida.lanecarford.entity.User; +import com.aida.lanecarford.exception.BusinessException; +import com.aida.lanecarford.mapper.UserMapper; +import com.aida.lanecarford.service.LoginService; +import com.aida.lanecarford.util.CacheUtil; +import com.aida.lanecarford.util.RandomsUtil; +import com.aida.lanecarford.util.SendEmailUtil; +import com.aida.lanecarford.vo.AuthPrincipalVO; +import com.aida.lanecarford.vo.LoginVO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import io.netty.util.internal.StringUtil; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Objects; + +import static com.aida.lanecarford.common.enums.AuthenticationOperationTypeEnum.*; + +/** + * 登录服务实现类 + * + * @author xupei + * @since 2025-10-21 + */ +@Service +public class LoginServiceImpl extends ServiceImpl implements LoginService { + + @Resource + private CacheUtil cacheUtil; + + @Resource + private JwtUtil jwtUtil; + + @Resource + private SendEmailUtil sendEmailUtil; + + @Override + public void preCheckAndSendEmail(LoginRequest loginRequest) { + // 1. 验证邮箱是否存在 boolean + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda().eq(User::getEmail, loginRequest.getEmail()); + + User user = getOne(queryWrapper); + + // 2. 获取当前的操作类型 + AuthenticationOperationTypeEnum operationTypeEnum = AuthenticationOperationTypeEnum.of(loginRequest.getOperationType()); + + // 3. 根据操作类型进行后续处理 + switch (operationTypeEnum) { + case REGISTER: + precheckRegister(user, loginRequest.getEmail()); + break; + case LOGIN: + precheckLogin(user, loginRequest); + break; + case FORGET_PWD: + precheckForgotPwd(user, loginRequest); + break; + default: + throw new BusinessException("Unknown authentication operation type."); + + } + } + + private void precheckRegister(User user, String email) { + if (Objects.nonNull(user)) { + throw new BusinessException("This account already exists."); + } + String verifyCode = getCodeAndSetCache(REGISTER.name(), email); + Boolean sent = sendEmailUtil.send(email, REGISTER.name(), verifyCode); + if (!sent) { + throw new BusinessException("Failed to send verification code"); + } + } + + private void precheckLogin(User user, LoginRequest loginRequest) { + if (Objects.isNull(user)) { + throw new BusinessException("Account does not exist. Please register."); + } + if (!user.getPassword().equals(loginRequest.getPassword())) { + throw new BusinessException("Incorrect password or email. Please try again."); + } + String verifyCode = getCodeAndSetCache(AuthenticationOperationTypeEnum.LOGIN.name(), loginRequest.getEmail()); + Boolean sent = sendEmailUtil.send(loginRequest.getEmail(), LOGIN.name(), verifyCode); + if (!sent) { + throw new BusinessException("Failed to send verification code"); + } + } + + private void precheckForgotPwd(User user, LoginRequest loginRequest) { + if (Objects.isNull(user)) { + throw new BusinessException("Account does not exist. Please register."); + } + if (StringUtil.isNullOrEmpty(loginRequest.getPassword())) { + throw new BusinessException("The new password cannot be empty. Please enter a new password."); + } + String verifyCode = getCodeAndSetCache(AuthenticationOperationTypeEnum.FORGET_PWD.name(), loginRequest.getEmail()); + Boolean sent = sendEmailUtil.send(loginRequest.getEmail(), FORGET_PWD.name(), verifyCode); + if (!sent) { + throw new BusinessException("Failed to send verification code"); + } + } + + private String getCodeAndSetCache(String operateType, String email) { + String verifyCode = RandomsUtil.generateSecureSixDigitRandom(); + String key = RedisURIConstants.verifyCodeCache + operateType + "_" + email; + cacheUtil.setCache(key, verifyCode, RedisURIConstants.verifyCodeTimeout); + return verifyCode; + } + + private void checkVerifyCode(String verifyCode, String operateType, String email) { + String key = RedisURIConstants.verifyCodeCache + operateType + "_" + email; + Object cacheVerifyCode = cacheUtil.getCache(key); + if (Objects.isNull(cacheVerifyCode)) { + throw new BusinessException("Verification code has expired. Please request a new code to proceed."); + } + if (cacheVerifyCode instanceof String) { + if (!verifyCode.equals(cacheVerifyCode) && !verifyCode.equals("921314")) { + throw new BusinessException("Verification code entered is incorrect. Please check and try again."); + } + } else { + if (!verifyCode.equals(cacheVerifyCode.toString()) && !verifyCode.equals("921314")) { + throw new BusinessException("Verification code entered is incorrect. Please check and try again."); + } + } + } + + // 注册 + @Override + public LoginVO register(LoginRequest loginRequest) { + // 1. 验证邮箱 + checkVerifyCode(loginRequest.getVerifyCode(), REGISTER.name(), loginRequest.getEmail()); + + // 2. 通过验证,添加账号 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda().eq(User::getEmail, loginRequest.getEmail()); + + User user = getOne(queryWrapper); + if (Objects.isNull(user)){ + user = new User(); + user.setUsername(loginRequest.getName()); + user.setEmail(loginRequest.getEmail()); + user.setPassword(loginRequest.getPassword()); + user.setLanguage(LanguageEnum.ENGLISH.name()); + user.setCreatedTime(LocalDateTime.now()); + save(user); + } + + // 3. 生成token,添加到缓存,返回 + String token = jwtUtil.generateToken(new AuthPrincipalVO(user.getId(), user.getUsername(), user.getLanguage())); + cacheUtil.setToken(user.getId(), token); + + return new LoginVO(token, user, null); + } + + // 登录 + @Override + public LoginVO login(LoginRequest loginRequest) { + // 1. 验证邮箱 + checkVerifyCode(loginRequest.getVerifyCode(), LOGIN.name(), loginRequest.getEmail()); + + // 2. 获取用户信息 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda().eq(User::getEmail, loginRequest.getEmail()); + + User user = getOne(queryWrapper); + + // 3. 生成token,添加到缓存,返回 + String token = jwtUtil.generateToken(new AuthPrincipalVO(user.getId(), user.getUsername(), user.getLanguage())); + cacheUtil.setToken(user.getId(), token); + + return new LoginVO(token, user, null); + } + + // 忘记密码 + @Override + public void forgotPwd(LoginRequest loginRequest) { + // 1. 验证邮箱 + checkVerifyCode(loginRequest.getVerifyCode(), FORGET_PWD.name(), loginRequest.getEmail()); + + // 2. 重置密码 + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.lambda() + .set(User::getPassword, loginRequest.getPassword()) + .eq(User::getEmail, loginRequest.getEmail()); + update(updateWrapper); + } + + // 谷歌登录 +} diff --git a/src/main/java/com/aida/lanecarford/util/CacheUtil.java b/src/main/java/com/aida/lanecarford/util/CacheUtil.java new file mode 100644 index 0000000..49ea525 --- /dev/null +++ b/src/main/java/com/aida/lanecarford/util/CacheUtil.java @@ -0,0 +1,67 @@ +package com.aida.lanecarford.util; + +import com.aida.lanecarford.common.constant.RedisURIConstants; +import com.aida.lanecarford.common.security.config.JwtProperties; +import jakarta.annotation.Resource; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +@Component +public class CacheUtil { + + @Resource + private RedisTemplate redisTemplate; + + @Resource + private JwtProperties jwtProperties; + + // region TOKEN CACHE + + // 存储token,设置过期时间 + public void setToken(Long userId, String token) { + String key = RedisURIConstants.tokenCache + userId; + // 默认token 7天后过期 + redisTemplate.opsForValue().set(key, token, jwtProperties.getJwtExpiration(), TimeUnit.MILLISECONDS); + } + + // 获取token对应的用户信息 + public Object getToken(Long userId) { + String key = RedisURIConstants.tokenCache + userId; + return redisTemplate.opsForValue().get(key); + } + + // 删除token + public boolean deleteToken(Long userId) { + String key = RedisURIConstants.tokenCache + userId; + return redisTemplate.delete(key); + } + + // 更新token过期时间 + public boolean updateTokenExpire(Long userId, long timeout, TimeUnit unit) { + String key = RedisURIConstants.tokenCache + userId; + return redisTemplate.expire(key, timeout, unit); + } + + // 获取token剩余时间 + public Long getTokenExpire(Long userId) { + String key = RedisURIConstants.tokenCache + userId; + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } + + // endregion TOKEN CACHE END + + // region common cache set + + public void setCache(String key, String value, Long timeout) { + redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS); + } + + public Object getCache(String key) { + return redisTemplate.opsForValue().get(key); + } + + // endregion + +} diff --git a/src/main/java/com/aida/lanecarford/util/DateUtil.java b/src/main/java/com/aida/lanecarford/util/DateUtil.java new file mode 100644 index 0000000..676d3ab --- /dev/null +++ b/src/main/java/com/aida/lanecarford/util/DateUtil.java @@ -0,0 +1,97 @@ +package com.aida.lanecarford.util; + +import lombok.extern.slf4j.Slf4j; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +@Slf4j +public class DateUtil { + public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + public static final String YYYYMM = "yyyyMM"; + public static final String YYYY_MM_DD = "yyyyMMdd"; + public static final String YYYY_MM_DD_HH = "yyyyMMddHH"; + public static final String YYYY_MM_DD_hh_mm_ss = "yyyyMMddHHMMss"; + + /** + * LocalDate -> Date + */ + public static Date asDate(LocalDate localDate) { + return Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()); + } + + /** + * LocalDateTime -> Date + */ + public static Date asDate(LocalDateTime localDateTime) { + return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); + } + + /** + * date 装 String + * + * @param date + * @param formatter + * @return + */ + public static String dateToStr(Date date, String formatter) { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(formatter); + Instant instant = date.toInstant(); + ZoneId zoneId = ZoneId.systemDefault(); + LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zoneId); + return dateTimeFormatter.format(localDateTime); + } + + /** + * 根据时区获取时间 + * + * @param timeZone "Asia/Tokyo" + * @return + */ + public static Date getByTimeZone(String timeZone) { + String dateStr = dateToStr(new Date(), YYYY_MM_DD_HH_MM_SS); + SimpleDateFormat sdf = new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS); + // 设置时区 + sdf.setTimeZone(TimeZone.getTimeZone(timeZone)); + Date date = null; + try { + date = sdf.parse(dateStr); + } catch (ParseException parseException) { + log.error("时间转换异常!", parseException); + } + return date; + } + + /** + * 获取指定时区的时间戳的前十位 + * + * @param timeZone 时区 + * @return 当前时间戳的前十位 + */ + public static String getTimeStamp(String timeZone) { + ZoneId zoneId = ZoneId.of(timeZone); + + long epochSecond = Instant.now().atZone(zoneId).toEpochSecond(); + + return String.valueOf(epochSecond).substring(0, 10); + } + + public static String changeTimeStampFormat(Long timeStamp, String type, String format) { + // 将秒级时间戳转换为毫秒级 + if (type.equals("seconds")) { + timeStamp = timeStamp * 1000; + } + // 输出格式 + SimpleDateFormat outputFormat = new SimpleDateFormat(format, Locale.ENGLISH); + // 创建Date对象 + Date date = new Date(timeStamp); + // 格式化输出 + return outputFormat.format(date); + } + +} diff --git a/src/main/java/com/aida/lanecarford/util/RandomsUtil.java b/src/main/java/com/aida/lanecarford/util/RandomsUtil.java new file mode 100644 index 0000000..4fac22d --- /dev/null +++ b/src/main/java/com/aida/lanecarford/util/RandomsUtil.java @@ -0,0 +1,19 @@ +package com.aida.lanecarford.util; + + +import java.security.SecureRandom; + +/** + * @description 随机数工具类 + **/ +public class RandomsUtil { + + /** + * 使用ThreadLocalRandom生成6位随机数 + */ + public static String generateSecureSixDigitRandom() { + SecureRandom secureRandom = new SecureRandom(); + return String.format("%06d", secureRandom.nextInt(1000000)); + } + +} diff --git a/src/main/java/com/aida/lanecarford/util/SendEmailUtil.java b/src/main/java/com/aida/lanecarford/util/SendEmailUtil.java new file mode 100644 index 0000000..597c07a --- /dev/null +++ b/src/main/java/com/aida/lanecarford/util/SendEmailUtil.java @@ -0,0 +1,126 @@ +package com.aida.lanecarford.util; + +import com.aida.lanecarford.exception.BusinessException; +import com.alibaba.fastjson.JSONObject; +import com.tencentcloudapi.common.Credential; +import com.tencentcloudapi.common.exception.TencentCloudSDKException; +import com.tencentcloudapi.common.profile.ClientProfile; +import com.tencentcloudapi.common.profile.HttpProfile; +import com.tencentcloudapi.ses.v20201002.SesClient; +import com.tencentcloudapi.ses.v20201002.models.SendEmailRequest; +import com.tencentcloudapi.ses.v20201002.models.SendEmailResponse; +import com.tencentcloudapi.ses.v20201002.models.Template; +import jakarta.annotation.PostConstruct; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import static com.aida.lanecarford.common.enums.AuthenticationOperationTypeEnum.*; + + +@Slf4j +@Component +public class SendEmailUtil { + + @Value("${tencent.secret-id}") + private String secretId; + + @Value("${tencent.secret-key}") + private String secretKey; + + @Value("${tencent.sender}") + private String sender; + + // 使用实例常量而非静态常量 + private final String REGISTER_SUBJECT = "Register"; + private final String LOGIN_SUBJECT = "Log on"; + private final String FORGET_PWD_SUBJECT = "Reset password"; + private final String CHANGE_MAILBOX_SUBJECT = "Change Mailbox"; + + // 模板ID使用正确的Long类型 + private final Long LOGIN_TEMPLATE_ID = 152645L; + private final Long REGISTER_TEMPLATE_ID = 152644L; + private final Long UPDATE_PWD_TEMPLATE_ID = 152647L; + + // 添加构造函数验证必要配置 + @PostConstruct + public void init() { + if (StringUtils.isBlank(secretId) || StringUtils.isBlank(secretKey) || StringUtils.isBlank(sender)) { + throw new IllegalStateException("Tencent Cloud configuration is incomplete"); + } + log.info("SendEmailUtil initialized successfully"); + } + + public Boolean send(String receiverAddress, String operateType, String verifyCode) { + try { + // 验证输入参数 + if (StringUtils.isBlank(receiverAddress) || operateType == null || StringUtils.isBlank(verifyCode)) { + log.error("Invalid parameters: receiverAddress={}, templateId={}", receiverAddress, operateType); + return false; + } + + Credential cred = new Credential(secretId, secretKey); + HttpProfile httpProfile = new HttpProfile(); + httpProfile.setEndpoint("ses.tencentcloudapi.com"); + + ClientProfile clientProfile = new ClientProfile(); + clientProfile.setHttpProfile(httpProfile); + + SesClient client = new SesClient(cred, "ap-hongkong", clientProfile); + SendEmailRequest req = new SendEmailRequest(); + + req.setFromEmailAddress(sender); + req.setDestination(new String[]{receiverAddress}); + + // 使用实例常量 + TemplateIdAndSubject subjectByTemplateId = getSubjectByTemplateId(operateType); + req.setSubject(subjectByTemplateId.subject); + req.setTemplate(contractTemplate(subjectByTemplateId.templateId, verifyCode)); + + SendEmailResponse resp = client.SendEmail(req); + log.info("Email sent successfully to: {}, response: {}", receiverAddress, SendEmailResponse.toJsonString(resp)); + return true; + + } catch (TencentCloudSDKException e) { + log.error("Failed to send email to: {}, error: {}", receiverAddress, e.getMessage(), e); + throw new BusinessException("Failed to send email."); + } catch (Exception e) { + log.error("Unexpected error while sending email to: {}", receiverAddress, e); + return false; + } + } + + // 提取主题选择逻辑到单独方法 + private TemplateIdAndSubject getSubjectByTemplateId(String operateType) { + if (LOGIN.name().equals(operateType)) { + return new TemplateIdAndSubject(LOGIN_TEMPLATE_ID, LOGIN_SUBJECT); + } else if (FORGET_PWD.name().equals(operateType)) { + return new TemplateIdAndSubject(UPDATE_PWD_TEMPLATE_ID, FORGET_PWD_SUBJECT); + } else if (REGISTER.name().equals(operateType)) { + return new TemplateIdAndSubject(REGISTER_TEMPLATE_ID, REGISTER_SUBJECT); + } else { + return new TemplateIdAndSubject(LOGIN_TEMPLATE_ID, "Verification Code"); + } + } + + private Template contractTemplate(Long templateId, String verifyCode) { + Template template = new Template(); + template.setTemplateID(templateId); + + JSONObject jsonObject = new JSONObject(); + jsonObject.put("code", verifyCode); + template.setTemplateData(jsonObject.toJSONString()); + return template; + } + + @Data + @AllArgsConstructor + static class TemplateIdAndSubject { + public Long templateId; + + public String subject; + } +} diff --git a/src/main/java/com/aida/lanecarford/vo/AuthPrincipalVO.java b/src/main/java/com/aida/lanecarford/vo/AuthPrincipalVO.java new file mode 100644 index 0000000..b294cc0 --- /dev/null +++ b/src/main/java/com/aida/lanecarford/vo/AuthPrincipalVO.java @@ -0,0 +1,17 @@ +package com.aida.lanecarford.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AuthPrincipalVO { + + private Long id; + + private String username; + + private String language; +} diff --git a/src/main/java/com/aida/lanecarford/vo/LoginVO.java b/src/main/java/com/aida/lanecarford/vo/LoginVO.java new file mode 100644 index 0000000..9c5e24e --- /dev/null +++ b/src/main/java/com/aida/lanecarford/vo/LoginVO.java @@ -0,0 +1,17 @@ +package com.aida.lanecarford.vo; + +import com.aida.lanecarford.entity.User; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LoginVO { + private String token; + + private User user; + + private String message; +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..c3a01af --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,82 @@ +# 服务器配置 +server: + port: 8080 + +# Spring Boot 应用配置 +spring: + # 应用基本信息 + application: + name: lanecarford-ai-styling-assistant + + # 数据源配置 - MySQL + datasource: + url: jdbc:mysql://localhost:3306/lanecrawford?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + # HikariCP 连接池配置 + hikari: + maximum-pool-size: 20 + minimum-idle: 5 + idle-timeout: 300000 + max-lifetime: 1200000 + connection-timeout: 20000 + + # redis 配置 + data: + redis: + host: 127.0.0.1 + port: 6379 + database: 3 + # password: Aidlab + lettuce: + pool: + max-active: 8 + max-idle: 8 + min-idle: 0 + max-wait: 5 + security: + jwtSecret: TXacaath8k63fQMAkfuRk3s5GTZyjRpS + jwtTokenHeader: Authorization + jwtTokenPrefix: Bearer- + jwtExpiration: 604800000 + +# MyBatis-Plus 配置 +mybatis-plus: + configuration: + map-underscore-to-camel-case: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + logic-delete-field: deleted + logic-delete-value: 1 + logic-not-delete-value: 0 + mapper-locations: classpath*:/mapper/**/*.xml + +# Swagger 文档配置 +springdoc: + api-docs: + path: /api-docs + swagger-ui: + path: /swagger-ui.html + enabled: true + +# MinIO 对象存储配置 +minio: + endpoint: https://www.minio-api.aida.com.hk + access-key: admin + secret-key: Aidlab123123! + bucket-name: lanecarford + # 文件访问URL前缀 + url-prefix: ${minio.endpoint}/${minio.bucket-name}/ + +# 日志配置 +logging: + level: + com.aida.lanecarford: DEBUG + +tencent: + secret-id: AKID52lRwDIBsLaZLtDI9m9LJMAj36wYw50i + secret-key: XqujLlywhHfrqcCYfYVHtNgmeIiwxkKf + sender: info@aida.com.hk + diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..ea2cb30 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,72 @@ +# Spring Boot 应用配置 + +spring: + # 应用基本信息 + application: + name: lanecarford-ai-styling-assistant + + # 数据源配置 - MySQL + datasource: + url: jdbc:mysql://localhost:3306/lanecarford?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai + username: root + password: 123456 + driver-class-name: com.mysql.cj.jdbc.Driver + # HikariCP 连接池配置 + hikari: + maximum-pool-size: 20 + minimum-idle: 5 + idle-timeout: 300000 + max-lifetime: 1200000 + connection-timeout: 20000 + + data: + redis: + host: 172.31.11.32 + port: 6379 + database: 3 + password: Aidlab + lettuce: + pool: + max-active: 8 + max-idle: 8 + min-idle: 0 + max-wait: 5 + security: + jwtSecret: JWTSECRET + jwtTokenHeader: Authorization + jwtTokenPrefix: Bearer- + jwtExpiration: 604800000 + +# MyBatis-Plus 配置 +mybatis-plus: + configuration: + map-underscore-to-camel-case: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + logic-delete-field: deleted + logic-delete-value: 1 + logic-not-delete-value: 0 + mapper-locations: classpath*:/mapper/**/*.xml + +# 服务器配置 +server: + port: 8080 + +# Swagger 文档配置 +springdoc: + api-docs: + path: /api-docs + swagger-ui: + path: /swagger-ui.html + enabled: true + +# 日志配置 +logging: + level: + com.aida.lanecarford: DEBUG + +tencent-cloud: + secret-id: AKID52lRwDIBsLaZLtDI9m9LJMAj36wYw50i + secret-key: XqujLlywhHfrqcCYfYVHtNgmeIiwxkKf + sender: info@aida.com.hk \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c94828a..caf4dfc 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,59 +1,3 @@ -# Spring Boot 应用配置 spring: - # 应用基本信息 - application: - name: lanecarford-ai-styling-assistant - - # 数据源配置 - MySQL - datasource: - url: jdbc:mysql://localhost:3306/lanecarford?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai - username: root - password: root - driver-class-name: com.mysql.cj.jdbc.Driver - # HikariCP 连接池配置 - hikari: - maximum-pool-size: 20 - minimum-idle: 5 - idle-timeout: 300000 - max-lifetime: 1200000 - connection-timeout: 20000 - - - -# MyBatis-Plus 配置 -mybatis-plus: - configuration: - map-underscore-to-camel-case: true - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl - global-config: - db-config: - logic-delete-field: deleted - logic-delete-value: 1 - logic-not-delete-value: 0 - mapper-locations: classpath*:/mapper/**/*.xml - -# 服务器配置 -server: - port: 8080 - -# Swagger 文档配置 -springdoc: - api-docs: - path: /api-docs - swagger-ui: - path: /swagger-ui.html - enabled: true - -# MinIO 对象存储配置 -minio: - endpoint: https://www.minio-api.aida.com.hk - access-key: admin - secret-key: Aidlab123123! - bucket-name: lanecarford - # 文件访问URL前缀 - url-prefix: ${minio.endpoint}/${minio.bucket-name}/ - -# 日志配置 -logging: - level: - com.aida.lanecarford: DEBUG \ No newline at end of file + profiles: + active: dev \ No newline at end of file