Compare commits
8 Commits
ddcb8a1ad3
...
refactor/r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebc351d2d7 | ||
|
|
4bc4ca0809 | ||
|
|
3808449d4d | ||
|
|
caa699a6cd | ||
|
|
86fb565d4b | ||
|
|
e687cdcc83 | ||
|
|
06e4e5597f | ||
|
|
c1144fadcb |
9
.claude/settings.local.json
Normal file
9
.claude/settings.local.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(git mv:*)",
|
||||||
|
"Bash(./mvnw.cmd dependency:resolve:*)",
|
||||||
|
"Bash(findstr:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
20
pom.xml
20
pom.xml
@@ -46,7 +46,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.xiaoymin</groupId>
|
<groupId>com.github.xiaoymin</groupId>
|
||||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.5.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@@ -68,6 +68,24 @@
|
|||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- JWT依赖 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
<version>0.12.6</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-impl</artifactId>
|
||||||
|
<version>0.12.6</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
|
<version>0.12.6</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-data-redis-test</artifactId>
|
<artifactId>spring-boot-starter-data-redis-test</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.bicloud.Interceptor;
|
||||||
|
|
||||||
|
import com.bicloud.common.context.UserContext;
|
||||||
|
import com.bicloud.common.utils.JwtUtils;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT认证拦截器
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class JwtAuthInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private JwtUtils jwtUtils;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
|
// 1. 从请求头获取Token
|
||||||
|
String token = jwtUtils.extractToken(request);
|
||||||
|
|
||||||
|
// 2. 验证Token是否存在
|
||||||
|
if (!StringUtils.hasText(token)) {
|
||||||
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
|
response.getWriter().write("{\"code\":401,\"message\":\"未登录,请先登录!\"}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 验证Token有效性
|
||||||
|
if (!jwtUtils.validateToken(token)) {
|
||||||
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
|
response.getWriter().write("{\"code\":401,\"message\":\"Token无效或已过期!\"}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 解析Token,获取用户信息
|
||||||
|
Long userId = jwtUtils.getUserIdFromToken(token);
|
||||||
|
String username = jwtUtils.getUsernameFromToken(token);
|
||||||
|
|
||||||
|
if (userId == null || username == null) {
|
||||||
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
|
response.getWriter().write("{\"code\":401,\"message\":\"Token解析失败!\"}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 将用户信息存入ThreadLocal
|
||||||
|
UserContext.setUserId(userId);
|
||||||
|
UserContext.setUsername(username);
|
||||||
|
|
||||||
|
log.debug("用户认证成功,userId: {}, username: {}", userId, username);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
||||||
|
// 清除ThreadLocal,防止内存泄漏
|
||||||
|
UserContext.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package com.bicloud.Interceptor;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
拦截器,请求打印请求状态
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class RequestLogInterceptor implements HandlerInterceptor {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(RequestLogInterceptor.class);
|
|
||||||
private static final String START_TIME = "REQ_START_TIME";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
|
||||||
request.setAttribute(START_TIME, System.currentTimeMillis());
|
|
||||||
|
|
||||||
log.info("[REQ] time={}, method={}, uri={}, ip={}",
|
|
||||||
LocalDateTime.now(),
|
|
||||||
request.getMethod(),
|
|
||||||
request.getRequestURI(),
|
|
||||||
request.getRemoteAddr());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
|
|
||||||
Object handler, Exception ex) {
|
|
||||||
Long start = (Long) request.getAttribute(START_TIME);
|
|
||||||
long cost = (start == null) ? -1 : (System.currentTimeMillis() - start);
|
|
||||||
|
|
||||||
if (ex == null) {
|
|
||||||
log.info("[RES] status={}, costMs={}, uri={}",
|
|
||||||
response.getStatus(), cost, request.getRequestURI());
|
|
||||||
} else {
|
|
||||||
log.warn("[RES] status={}, costMs={}, uri={}, ex={}",
|
|
||||||
response.getStatus(), cost, request.getRequestURI(), ex.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
46
src/main/java/com/bicloud/common/context/UserContext.java
Normal file
46
src/main/java/com/bicloud/common/context/UserContext.java
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package com.bicloud.common.context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户上下文 - 使用ThreadLocal存储当前请求的用户信息
|
||||||
|
*/
|
||||||
|
public class UserContext {
|
||||||
|
|
||||||
|
private static final ThreadLocal<Long> USER_ID = new ThreadLocal<>();
|
||||||
|
private static final ThreadLocal<String> USERNAME = new ThreadLocal<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置用户ID
|
||||||
|
*/
|
||||||
|
public static void setUserId(Long userId) {
|
||||||
|
USER_ID.set(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户ID
|
||||||
|
*/
|
||||||
|
public static Long getUserId() {
|
||||||
|
return USER_ID.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置用户名
|
||||||
|
*/
|
||||||
|
public static void setUsername(String username) {
|
||||||
|
USERNAME.set(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户名
|
||||||
|
*/
|
||||||
|
public static String getUsername() {
|
||||||
|
return USERNAME.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除用户信息
|
||||||
|
*/
|
||||||
|
public static void clear() {
|
||||||
|
USER_ID.remove();
|
||||||
|
USERNAME.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main/java/com/bicloud/common/enums/LoginType.java
Normal file
16
src/main/java/com/bicloud/common/enums/LoginType.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package com.bicloud.common.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录类型枚举
|
||||||
|
*/
|
||||||
|
public enum LoginType {
|
||||||
|
/**
|
||||||
|
* 密码登录
|
||||||
|
*/
|
||||||
|
PASSWORD,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 短信验证码登录
|
||||||
|
*/
|
||||||
|
SMS_CODE
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.bicloud.common.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务异常类
|
||||||
|
*/
|
||||||
|
public class BusinessException extends RuntimeException {
|
||||||
|
|
||||||
|
public BusinessException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BusinessException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
121
src/main/java/com/bicloud/common/utils/JwtUtils.java
Normal file
121
src/main/java/com/bicloud/common/utils/JwtUtils.java
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package com.bicloud.common.utils;
|
||||||
|
|
||||||
|
import com.bicloud.config.JwtProperties;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT工具类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class JwtUtils {
|
||||||
|
|
||||||
|
private final JwtProperties jwtProperties;
|
||||||
|
|
||||||
|
private SecretKey secretKey;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
// 初始化密钥
|
||||||
|
this.secretKey = Keys.hmacShaKeyFor(
|
||||||
|
jwtProperties.getSecret().getBytes(StandardCharsets.UTF_8)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成Token
|
||||||
|
*/
|
||||||
|
public String generateToken(Long userId, String username) {
|
||||||
|
Date now = new Date();
|
||||||
|
Date expiryDate = new Date(now.getTime() + jwtProperties.getTokenExpiration() * 1000);
|
||||||
|
|
||||||
|
return Jwts.builder()
|
||||||
|
.subject(String.valueOf(userId))
|
||||||
|
.claim("username", username)
|
||||||
|
.issuedAt(now)
|
||||||
|
.expiration(expiryDate)
|
||||||
|
.signWith(secretKey)
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析Token
|
||||||
|
*/
|
||||||
|
public Claims parseToken(String token) {
|
||||||
|
try {
|
||||||
|
return Jwts.parser()
|
||||||
|
.verifyWith(secretKey)
|
||||||
|
.build()
|
||||||
|
.parseSignedClaims(token)
|
||||||
|
.getPayload();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("解析Token失败: {}", e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从Token中获取用户ID
|
||||||
|
*/
|
||||||
|
public Long getUserIdFromToken(String token) {
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
if (claims == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Long.parseLong(claims.getSubject());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从Token中获取用户名
|
||||||
|
*/
|
||||||
|
public String getUsernameFromToken(String token) {
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
if (claims == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return claims.get("username", String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证Token是否有效
|
||||||
|
*/
|
||||||
|
public boolean validateToken(String token) {
|
||||||
|
try {
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
if (claims == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 检查是否过期
|
||||||
|
Date expiration = claims.getExpiration();
|
||||||
|
return expiration.after(new Date());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("验证Token失败: {}", e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从请求头中提取Token
|
||||||
|
*/
|
||||||
|
public String extractToken(HttpServletRequest request) {
|
||||||
|
String token = request.getHeader(jwtProperties.getTokenHeader());
|
||||||
|
if (StringUtils.hasText(token)) {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
60
src/main/java/com/bicloud/common/utils/PasswordUtils.java
Normal file
60
src/main/java/com/bicloud/common/utils/PasswordUtils.java
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package com.bicloud.common.utils;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码加密工具类
|
||||||
|
*/
|
||||||
|
public class PasswordUtils {
|
||||||
|
|
||||||
|
private static final SecureRandom RANDOM = new SecureRandom();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机盐值
|
||||||
|
*/
|
||||||
|
public static String generateSalt() {
|
||||||
|
byte[] salt = new byte[16];
|
||||||
|
RANDOM.nextBytes(salt);
|
||||||
|
return Base64.getEncoder().encodeToString(salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用MD5加盐加密密码
|
||||||
|
* @param password 原始密码
|
||||||
|
* @param salt 盐值
|
||||||
|
* @return 加密后的密码
|
||||||
|
*/
|
||||||
|
public static String encryptPassword(String password, String salt) {
|
||||||
|
try {
|
||||||
|
// 将密码和盐值拼接
|
||||||
|
String combined = password + salt;
|
||||||
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
byte[] hashBytes = md.digest(combined.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
// 转换为16进制字符串
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : hashBytes) {
|
||||||
|
sb.append(String.format("%02x", b));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException("密码加密失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证密码是否正确
|
||||||
|
* @param inputPassword 输入的密码
|
||||||
|
* @param salt 盐值
|
||||||
|
* @param encryptedPassword 加密后的密码
|
||||||
|
* @return 是否匹配
|
||||||
|
*/
|
||||||
|
public static boolean verifyPassword(String inputPassword, String salt, String encryptedPassword) {
|
||||||
|
String encrypted = encryptPassword(inputPassword, salt);
|
||||||
|
return encrypted.equals(encryptedPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/main/java/com/bicloud/common/utils/RedisConstants.java
Normal file
33
src/main/java/com/bicloud/common/utils/RedisConstants.java
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package com.bicloud.common.utils;
|
||||||
|
|
||||||
|
public class RedisConstants {
|
||||||
|
public static final String LOGIN_CODE_KEY = "login:code:";
|
||||||
|
public static final String GET_CODE_LOCK = "getCodeLock:phone:";
|
||||||
|
public static final String GET_CODE_BLACKLIST_PHONE = "getCodeBlacklist:phone:";
|
||||||
|
public static final String GET_CODE_BLACKLIST_IP_ADDR = "getCodeBlacklist:ipAddr:";
|
||||||
|
public static final String GET_CODE_BLACKLIST_MAC_ADDR = "getCodeBlacklist:macAddr:";
|
||||||
|
public static final Long GET_CODE_LOCK_TTL = 1L;
|
||||||
|
public static final Long REFRESH_BLACKLIST_TTL = 24L;
|
||||||
|
public static final Long LOGIN_CODE_TTL = 2L;
|
||||||
|
public static final String LOGIN_USER_KEY = "login:token:";
|
||||||
|
public static final Long LOGIN_USER_TTL = 36000L;
|
||||||
|
|
||||||
|
public static final Long CACHE_NULL_TTL = 2L;
|
||||||
|
|
||||||
|
public static final Long CACHE_SHOP_TTL = 30L;
|
||||||
|
public static final String CACHE_SHOP_KEY = "cache:shop:";
|
||||||
|
|
||||||
|
public static final String LOCK_SHOP_KEY = "lock:shop:";
|
||||||
|
public static final Long LOCK_SHOP_TTL = 10L;
|
||||||
|
|
||||||
|
public static final String SECKILL_STOCK_KEY = "seckill:stock:";
|
||||||
|
public static final String SECKILL_ORDER_KEY = "seckill:order";
|
||||||
|
public static final String BLOG_LIKED_KEY = "blog:liked:";
|
||||||
|
public static final String FEED_KEY = "feed:";
|
||||||
|
public static final String SHOP_GEO_KEY = "shop:geo:";
|
||||||
|
public static final String USER_SIGN_KEY = "sign:";
|
||||||
|
|
||||||
|
// JWT相关常量
|
||||||
|
public static final String JWT_BLACKLIST_KEY = "jwt:blacklist:";
|
||||||
|
public static final Long JWT_BLACKLIST_TTL = 7L; // 7天,与RefreshToken过期时间一致
|
||||||
|
}
|
||||||
20
src/main/java/com/bicloud/common/utils/RegexPatterns.java
Normal file
20
src/main/java/com/bicloud/common/utils/RegexPatterns.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.bicloud.common.utils;
|
||||||
|
|
||||||
|
public class RegexPatterns {
|
||||||
|
/**
|
||||||
|
* 手机号正则
|
||||||
|
*/
|
||||||
|
public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
|
||||||
|
/**
|
||||||
|
* 邮箱正则
|
||||||
|
*/
|
||||||
|
public static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";
|
||||||
|
/**
|
||||||
|
* 密码正则。4~32位的字母、数字、下划线
|
||||||
|
*/
|
||||||
|
public static final String PASSWORD_REGEX = "^\\w{4,32}$";
|
||||||
|
/**
|
||||||
|
* 验证码正则, 6位数字或字母
|
||||||
|
*/
|
||||||
|
public static final String VERIFY_CODE_REGEX = "^[a-zA-Z\\d]{6}$";
|
||||||
|
}
|
||||||
57
src/main/java/com/bicloud/common/utils/RegexUtils.java
Normal file
57
src/main/java/com/bicloud/common/utils/RegexUtils.java
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package com.bicloud.common.utils;
|
||||||
|
|
||||||
|
import com.github.xiaoymin.knife4j.core.util.StrUtil;
|
||||||
|
|
||||||
|
public class RegexUtils {
|
||||||
|
/**
|
||||||
|
* 是否是无效手机格式
|
||||||
|
* @param phone 要校验的手机号
|
||||||
|
* @return true:符合,false:不符合
|
||||||
|
*/
|
||||||
|
public static boolean isPhoneInvalid(String phone){
|
||||||
|
return mismatch(phone, RegexPatterns.PHONE_REGEX);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 是否是无效邮箱格式
|
||||||
|
* @param email 要校验的邮箱
|
||||||
|
* @return true:符合,false:不符合
|
||||||
|
*/
|
||||||
|
public static boolean isEmailInvalid(String email){
|
||||||
|
return mismatch(email, RegexPatterns.EMAIL_REGEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是无效验证码格式
|
||||||
|
* @param code 要校验的验证码
|
||||||
|
* @return true:符合,false:不符合
|
||||||
|
*/
|
||||||
|
public static boolean isCodeInvalid(String code){
|
||||||
|
return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是有效手机格式
|
||||||
|
* @param phone 要校验的手机号
|
||||||
|
* @return true:有效,false:无效
|
||||||
|
*/
|
||||||
|
public static boolean isPhoneValid(String phone) {
|
||||||
|
return !isPhoneInvalid(phone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是有效邮箱格式
|
||||||
|
* @param email 要校验的邮箱
|
||||||
|
* @return true:有效,false:无效
|
||||||
|
*/
|
||||||
|
public static boolean isEmailValid(String email) {
|
||||||
|
return !isEmailInvalid(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验是否不符合正则格式
|
||||||
|
private static boolean mismatch(String str, String regex){
|
||||||
|
if (StrUtil.isBlank(str)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !str.matches(regex);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/main/java/com/bicloud/config/JwtProperties.java
Normal file
29
src/main/java/com/bicloud/config/JwtProperties.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package com.bicloud.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT配置属性类
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "jwt")
|
||||||
|
public class JwtProperties {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT密钥
|
||||||
|
*/
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token过期时间(秒)
|
||||||
|
*/
|
||||||
|
private Long tokenExpiration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token请求头名称
|
||||||
|
*/
|
||||||
|
private String tokenHeader;
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.bicloud.config;
|
package com.bicloud.config;
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityScheme;
|
||||||
import io.swagger.v3.oas.models.OpenAPI;
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
import io.swagger.v3.oas.models.info.Info;
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
import io.swagger.v3.oas.models.info.License;
|
import io.swagger.v3.oas.models.info.License;
|
||||||
@@ -31,7 +33,7 @@ public class OpenApiConfig {
|
|||||||
|
|
||||||
// 分组1:文献管理接口
|
// 分组1:文献管理接口
|
||||||
@Bean
|
@Bean
|
||||||
public GroupedOpenApi literatureApi() {
|
public GroupedOpenApi documentApi() {
|
||||||
return GroupedOpenApi.builder()
|
return GroupedOpenApi.builder()
|
||||||
.group("bicloud接口") // 分组名称
|
.group("bicloud接口") // 分组名称
|
||||||
.packagesToScan("com.bicloud.controller") // 匹配的接口路径
|
.packagesToScan("com.bicloud.controller") // 匹配的接口路径
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.bicloud.config;
|
package com.bicloud.config;
|
||||||
|
|
||||||
import com.bicloud.Interceptor.RequestLogInterceptor;
|
import com.bicloud.Interceptor.JwtAuthInterceptor;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
@@ -8,15 +8,38 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class WebMvcConfig implements WebMvcConfigurer {
|
public class WebMvcConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
private final RequestLogInterceptor requestLogInterceptor;
|
private final JwtAuthInterceptor jwtAuthInterceptor;
|
||||||
|
|
||||||
public WebMvcConfig(RequestLogInterceptor requestLogInterceptor) {
|
public WebMvcConfig(JwtAuthInterceptor jwtAuthInterceptor) {
|
||||||
this.requestLogInterceptor = requestLogInterceptor;
|
this.jwtAuthInterceptor = jwtAuthInterceptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(InterceptorRegistry registry) {
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
registry.addInterceptor(requestLogInterceptor)
|
// JWT认证拦截器
|
||||||
.addPathPatterns("/**");
|
registry.addInterceptor(jwtAuthInterceptor)
|
||||||
|
.addPathPatterns("/**")
|
||||||
|
.excludePathPatterns(
|
||||||
|
// Swagger/Knife4j 文档路径
|
||||||
|
"/doc.html",
|
||||||
|
"/swagger-ui/**",
|
||||||
|
"/swagger-resources/**",
|
||||||
|
"/v3/api-docs/**",
|
||||||
|
"/webjars/**",
|
||||||
|
"/favicon.ico",
|
||||||
|
// 静态资源路径放行
|
||||||
|
"/img/**",
|
||||||
|
"/video/**",
|
||||||
|
// 用户相关放行路径
|
||||||
|
"/user/code",
|
||||||
|
"/user/register",
|
||||||
|
"/user/login",
|
||||||
|
"/course/cats",
|
||||||
|
"/course/page",
|
||||||
|
"/document/page",
|
||||||
|
"/document/types"
|
||||||
|
|
||||||
|
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package com.bicloud.controller;
|
|||||||
|
|
||||||
import com.bicloud.common.result.PageResult;
|
import com.bicloud.common.result.PageResult;
|
||||||
import com.bicloud.common.result.Result;
|
import com.bicloud.common.result.Result;
|
||||||
import com.bicloud.pojo.dto.LiteratureCreateDto;
|
import com.bicloud.pojo.dto.DocumentCreateDto;
|
||||||
import com.bicloud.pojo.dto.LiteraturePageQueryDto;
|
import com.bicloud.pojo.dto.DocumentPageQueryDto;
|
||||||
import com.bicloud.pojo.vo.DocumentTypeVo;
|
import com.bicloud.pojo.vo.DocumentTypeVo;
|
||||||
import com.bicloud.pojo.vo.LiteratureListVo;
|
import com.bicloud.pojo.vo.DocumentListVo;
|
||||||
import com.bicloud.service.LiteratureService;
|
import com.bicloud.service.DocumentService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -17,28 +17,28 @@ import java.util.List;
|
|||||||
|
|
||||||
@CrossOrigin
|
@CrossOrigin
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/literature")
|
@RequestMapping("/document")
|
||||||
@Tag(name = "文献管理", description = "文献相关接口")
|
@Tag(name = "文献管理", description = "文献相关接口")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class LiteratureController {
|
public class DocumentController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private LiteratureService literatureService;
|
private DocumentService documentService;
|
||||||
|
|
||||||
// 新增文献数据
|
// 新增文献数据
|
||||||
@PostMapping("/add")
|
@PostMapping("/add")
|
||||||
@Operation(summary = "新增文献")
|
@Operation(summary = "新增文献")
|
||||||
public Result<String> addLiterature(@RequestBody LiteratureCreateDto dto) {
|
public Result<String> addDocument(@RequestBody DocumentCreateDto dto) {
|
||||||
literatureService.addLiterature(dto);
|
documentService.addDocument(dto);
|
||||||
return Result.success("新增文献成功");
|
return Result.success("新增文献成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除文献
|
// 删除文献
|
||||||
@DeleteMapping("/delete/{id}") // 通过路径变量接收文献ID
|
@DeleteMapping("/delete/{id}") // 通过路径变量接收文献ID
|
||||||
@Operation(summary = "删除文献")
|
@Operation(summary = "删除文献")
|
||||||
public Result<String> deleteLiterature(@PathVariable Long id) { // @PathVariable绑定路径中的id参数
|
public Result<String> deleteDocument(@PathVariable Long id) { // @PathVariable绑定路径中的id参数
|
||||||
try {
|
try {
|
||||||
literatureService.deleteLiterature(id);
|
documentService.deleteDocument(id);
|
||||||
return Result.success("删除文献成功");
|
return Result.success("删除文献成功");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -49,8 +49,8 @@ public class LiteratureController {
|
|||||||
//分页查询
|
//分页查询
|
||||||
@Operation(summary = "分页查询文献")
|
@Operation(summary = "分页查询文献")
|
||||||
@GetMapping("/page")
|
@GetMapping("/page")
|
||||||
public Result<PageResult<LiteratureListVo>> page(LiteraturePageQueryDto queryDTO) {
|
public Result<PageResult<DocumentListVo>> page(DocumentPageQueryDto queryDTO) {
|
||||||
PageResult<LiteratureListVo> result = literatureService.pageQuery(queryDTO);
|
PageResult<DocumentListVo> result = documentService.pageQuery(queryDTO);
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ public class LiteratureController {
|
|||||||
@GetMapping("/types")
|
@GetMapping("/types")
|
||||||
@Operation(summary = "获取文献分类下拉列表")
|
@Operation(summary = "获取文献分类下拉列表")
|
||||||
public Result<List<DocumentTypeVo>> types() {
|
public Result<List<DocumentTypeVo>> types() {
|
||||||
return Result.success(literatureService.listTypes());
|
return Result.success(documentService.listTypes());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
81
src/main/java/com/bicloud/controller/UserController.java
Normal file
81
src/main/java/com/bicloud/controller/UserController.java
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package com.bicloud.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import com.bicloud.common.result.Result;
|
||||||
|
import com.bicloud.common.utils.JwtUtils;
|
||||||
|
import com.bicloud.pojo.dto.LoginDto;
|
||||||
|
import com.bicloud.pojo.dto.RegisterDto;
|
||||||
|
import com.bicloud.pojo.vo.LoginVo;
|
||||||
|
import com.bicloud.pojo.vo.UserInfoVo;
|
||||||
|
import com.bicloud.service.UserService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@CrossOrigin
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/user")
|
||||||
|
@Tag(name = "用户管理")
|
||||||
|
@Slf4j
|
||||||
|
public class UserController {
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtUtils jwtUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送手机验证码
|
||||||
|
*/
|
||||||
|
@PostMapping("/code")
|
||||||
|
@Operation(summary = "发送验证码")
|
||||||
|
public Result sendCode(@RequestParam("mobile") String mobile, HttpSession session, HttpServletRequest request) {
|
||||||
|
// 发送短信验证码并保存验证码
|
||||||
|
return userService.sendCode(mobile, session, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户注册
|
||||||
|
*/
|
||||||
|
@PostMapping("/register")
|
||||||
|
@Operation(summary = "用户注册")
|
||||||
|
public Result register(@Valid @RequestBody RegisterDto registerDto) {
|
||||||
|
return userService.register(registerDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户登录
|
||||||
|
*/
|
||||||
|
@PostMapping("/login")
|
||||||
|
@Operation(summary = "用户登录")
|
||||||
|
public Result<LoginVo> login(@Valid @RequestBody LoginDto loginDto) {
|
||||||
|
return userService.login(loginDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户登出
|
||||||
|
*/
|
||||||
|
@PostMapping("/logout")
|
||||||
|
@Operation(summary = "用户登出")
|
||||||
|
public Result logout(HttpServletRequest request) {
|
||||||
|
String token = jwtUtils.extractToken(request);
|
||||||
|
userService.logout(token);
|
||||||
|
return Result.success("登出成功!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户信息
|
||||||
|
*/
|
||||||
|
@GetMapping("/info")
|
||||||
|
@Operation(summary = "获取当前用户信息")
|
||||||
|
public Result<UserInfoVo> getUserInfo() {
|
||||||
|
UserInfoVo userInfo = userService.getCurrentUserInfo();
|
||||||
|
return Result.success(userInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
package com.bicloud.mapper;
|
package com.bicloud.mapper;
|
||||||
|
|
||||||
import com.bicloud.pojo.dto.LiteratureCreateDto;
|
import com.bicloud.pojo.dto.DocumentCreateDto;
|
||||||
import com.bicloud.pojo.dto.LiteraturePageQueryDto;
|
import com.bicloud.pojo.dto.DocumentPageQueryDto;
|
||||||
import com.bicloud.pojo.vo.DocumentTypeVo;
|
import com.bicloud.pojo.vo.DocumentTypeVo;
|
||||||
import com.bicloud.pojo.vo.LiteratureListVo;
|
import com.bicloud.pojo.vo.DocumentListVo;
|
||||||
import com.github.pagehelper.Page;
|
import com.github.pagehelper.Page;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface LiteratureMapper {
|
public interface DocumentMapper {
|
||||||
|
|
||||||
// 新增:插入 biology_document_info
|
// 新增:插入 biology_document_info
|
||||||
int insertDocument(LiteratureCreateDto dto);
|
int insertDocument(DocumentCreateDto dto);
|
||||||
|
|
||||||
// 删除:软删除 removed=1
|
// 删除:软删除 removed=1
|
||||||
int softDeleteById(Long id);
|
int softDeleteById(Long id);
|
||||||
|
|
||||||
// 分页查询:返回列表VO(PageHelper自动分页)
|
// 分页查询:返回列表VO(PageHelper自动分页)
|
||||||
Page<LiteratureListVo> pageQuery(LiteraturePageQueryDto queryDTO);
|
Page<DocumentListVo> pageQuery(DocumentPageQueryDto queryDTO);
|
||||||
|
|
||||||
// 获取文献分类
|
// 获取文献分类
|
||||||
List<DocumentTypeVo> listTypes();
|
List<DocumentTypeVo> listTypes();
|
||||||
38
src/main/java/com/bicloud/mapper/UserMapper.java
Normal file
38
src/main/java/com/bicloud/mapper/UserMapper.java
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package com.bicloud.mapper;
|
||||||
|
|
||||||
|
import com.bicloud.pojo.entity.User;
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface UserMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据手机号查询用户
|
||||||
|
*/
|
||||||
|
User selectByMobile(@Param("mobile") String mobile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据邮箱查询用户
|
||||||
|
*/
|
||||||
|
User selectByEmail(@Param("email") String email);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户名查询用户
|
||||||
|
*/
|
||||||
|
User selectByUsername(@Param("username") String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入用户
|
||||||
|
*/
|
||||||
|
int insert(User user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID查询用户
|
||||||
|
*/
|
||||||
|
User selectById(@Param("id") Long id);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import java.time.LocalDateTime;
|
|||||||
* 新增文献(写入 biology_document_info)的入参
|
* 新增文献(写入 biology_document_info)的入参
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class LiteratureCreateDto {
|
public class DocumentCreateDto {
|
||||||
|
|
||||||
private Integer docType; // 对应 biology_document_info.doc_type(类型ID)
|
private Integer docType; // 对应 biology_document_info.doc_type(类型ID)
|
||||||
private String docLabel; // 对应 doc_label(目前是varchar,先按原样存)
|
private String docLabel; // 对应 doc_label(目前是varchar,先按原样存)
|
||||||
@@ -4,7 +4,7 @@ import lombok.Data;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class LiteratureDto {
|
public class DocumentDto {
|
||||||
// 字段名要和前端提交的参数名一致
|
// 字段名要和前端提交的参数名一致
|
||||||
private Long id;
|
private Long id;
|
||||||
private Integer year;
|
private Integer year;
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
* 列表页筛选 + 分页参数
|
* 列表页筛选 + 分页参数
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class LiteraturePageQueryDto {
|
public class DocumentPageQueryDto {
|
||||||
|
|
||||||
private Integer page;
|
private Integer page;
|
||||||
private Integer pageSize;
|
private Integer pageSize;
|
||||||
29
src/main/java/com/bicloud/pojo/dto/LoginDto.java
Normal file
29
src/main/java/com/bicloud/pojo/dto/LoginDto.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package com.bicloud.pojo.dto;
|
||||||
|
|
||||||
|
import com.bicloud.common.enums.LoginType;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录请求DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "登录请求")
|
||||||
|
public class LoginDto {
|
||||||
|
|
||||||
|
@Schema(description = "登录标识(手机号/邮箱/用户名)", example = "13800138000")
|
||||||
|
@NotBlank(message = "登录标识不能为空")
|
||||||
|
private String identifier;
|
||||||
|
|
||||||
|
@Schema(description = "密码(密码登录时必填)", example = "Password123")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Schema(description = "验证码(验证码登录时必填)", example = "123123")
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
@Schema(description = "登录类型:PASSWORD-密码登录,SMS_CODE-短信验证码登录", example = "PASSWORD")
|
||||||
|
@NotNull(message = "登录类型不能为空")
|
||||||
|
private LoginType loginType;
|
||||||
|
}
|
||||||
55
src/main/java/com/bicloud/pojo/dto/RegisterDto.java
Normal file
55
src/main/java/com/bicloud/pojo/dto/RegisterDto.java
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package com.bicloud.pojo.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Pattern;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "用户注册请求DTO")
|
||||||
|
public class RegisterDto {
|
||||||
|
|
||||||
|
@NotBlank(message = "用户名不能为空")
|
||||||
|
@Schema(description = "用户名", example = "张三")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@NotBlank(message = "所属单位不能为空")
|
||||||
|
@Schema(description = "所属单位", example = "某某大学")
|
||||||
|
private String company;
|
||||||
|
|
||||||
|
@NotBlank(message = "职位不能为空")
|
||||||
|
@Schema(description = "职位", example = "研究员")
|
||||||
|
private String job;
|
||||||
|
|
||||||
|
@NotBlank(message = "地址不能为空")
|
||||||
|
@Schema(description = "所在地址", example = "北京市海淀区")
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
@Schema(description = "感兴趣标签", example = "[\"生物信息学\", \"基因组学\"]")
|
||||||
|
private java.util.List<String> interestTags;
|
||||||
|
|
||||||
|
@NotBlank(message = "手机号不能为空")
|
||||||
|
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||||
|
@Schema(description = "手机号", example = "13800138000")
|
||||||
|
private String mobile;
|
||||||
|
|
||||||
|
@NotBlank(message = "验证码不能为空")
|
||||||
|
@Schema(description = "验证码", example = "123123")
|
||||||
|
private String vcode;
|
||||||
|
|
||||||
|
@NotBlank(message = "邮箱不能为空")
|
||||||
|
@Email(message = "邮箱格式不正确")
|
||||||
|
@Schema(description = "邮箱地址", example = "example@example.com")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@NotBlank(message = "密码不能为空")
|
||||||
|
@Pattern(regexp = "^[a-zA-Z\\d]{8,20}$",
|
||||||
|
message = "密码只能由字母和数字组成,长度8-20位")
|
||||||
|
@Schema(description = "密码", example = "Password123")
|
||||||
|
private String pwd;
|
||||||
|
|
||||||
|
@NotBlank(message = "确认密码不能为空")
|
||||||
|
@Schema(description = "确认密码", example = "Password123")
|
||||||
|
private String cpwd;
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import lombok.Data;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
@Data // Lombok注解,自动生成get/set/toString等方法
|
@Data // Lombok注解,自动生成get/set/toString等方法
|
||||||
public class LiteratureInfo {
|
public class DocumentInfo {
|
||||||
private Long id;
|
private Long id;
|
||||||
private Integer year;
|
private Integer year;
|
||||||
private String articleTitle; // 对应表中article_title
|
private String articleTitle; // 对应表中article_title
|
||||||
20
src/main/java/com/bicloud/pojo/entity/User.java
Normal file
20
src/main/java/com/bicloud/pojo/entity/User.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.bicloud.pojo.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class User {
|
||||||
|
private Long id;
|
||||||
|
private String username;
|
||||||
|
private String company;
|
||||||
|
private String job;
|
||||||
|
private String address;
|
||||||
|
private String introduce;
|
||||||
|
private String mobile;
|
||||||
|
private String email;
|
||||||
|
private String pwd;
|
||||||
|
private String salt;
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import lombok.Data;
|
|||||||
* 列表页返回对象
|
* 列表页返回对象
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class LiteratureListVo {
|
public class DocumentListVo {
|
||||||
|
|
||||||
private Long id; // 隐藏id
|
private Long id; // 隐藏id
|
||||||
private Integer year; // 年份
|
private Integer year; // 年份
|
||||||
30
src/main/java/com/bicloud/pojo/vo/LoginVo.java
Normal file
30
src/main/java/com/bicloud/pojo/vo/LoginVo.java
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package com.bicloud.pojo.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录响应VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "登录响应")
|
||||||
|
public class LoginVo {
|
||||||
|
|
||||||
|
@Schema(description = "访问令牌")
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
@Schema(description = "令牌过期时间(秒)", example = "86400")
|
||||||
|
private Long expiresIn;
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Schema(description = "用户名")
|
||||||
|
private String username;
|
||||||
|
}
|
||||||
35
src/main/java/com/bicloud/pojo/vo/UserInfoVo.java
Normal file
35
src/main/java/com/bicloud/pojo/vo/UserInfoVo.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package com.bicloud.pojo.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "用户信息")
|
||||||
|
public class UserInfoVo {
|
||||||
|
|
||||||
|
@Schema(description = "用户ID")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "用户名")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Schema(description = "手机号")
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@Schema(description = "邮箱")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -1,23 +1,23 @@
|
|||||||
package com.bicloud.service;
|
package com.bicloud.service;
|
||||||
|
|
||||||
import com.bicloud.common.result.PageResult;
|
import com.bicloud.common.result.PageResult;
|
||||||
import com.bicloud.pojo.dto.LiteratureCreateDto;
|
import com.bicloud.pojo.dto.DocumentCreateDto;
|
||||||
import com.bicloud.pojo.dto.LiteraturePageQueryDto;
|
import com.bicloud.pojo.dto.DocumentPageQueryDto;
|
||||||
import com.bicloud.pojo.vo.DocumentTypeVo;
|
import com.bicloud.pojo.vo.DocumentTypeVo;
|
||||||
import com.bicloud.pojo.vo.LiteratureListVo;
|
import com.bicloud.pojo.vo.DocumentListVo;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface LiteratureService {
|
public interface DocumentService {
|
||||||
|
|
||||||
// 新增文献(写入 biology_document_info)
|
// 新增文献(写入 biology_document_info)
|
||||||
void addLiterature(LiteratureCreateDto dto);
|
void addDocument(DocumentCreateDto dto);
|
||||||
|
|
||||||
// 删除文献(软删除:removed=1)
|
// 删除文献(软删除:removed=1)
|
||||||
void deleteLiterature(Long id);
|
void deleteDocument(Long id);
|
||||||
|
|
||||||
// 分页查询(返回列表VO)
|
// 分页查询(返回列表VO)
|
||||||
PageResult<LiteratureListVo> pageQuery(LiteraturePageQueryDto queryDTO);
|
PageResult<DocumentListVo> pageQuery(DocumentPageQueryDto queryDTO);
|
||||||
|
|
||||||
// 获取文献分类下拉列表
|
// 获取文献分类下拉列表
|
||||||
List<DocumentTypeVo> listTypes();
|
List<DocumentTypeVo> listTypes();
|
||||||
30
src/main/java/com/bicloud/service/UserService.java
Normal file
30
src/main/java/com/bicloud/service/UserService.java
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package com.bicloud.service;
|
||||||
|
|
||||||
|
import com.bicloud.common.result.Result;
|
||||||
|
import com.bicloud.pojo.dto.LoginDto;
|
||||||
|
import com.bicloud.pojo.dto.RegisterDto;
|
||||||
|
import com.bicloud.pojo.vo.LoginVo;
|
||||||
|
import com.bicloud.pojo.vo.UserInfoVo;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
public interface UserService {
|
||||||
|
Result sendCode(String mobile, HttpSession session, HttpServletRequest request);
|
||||||
|
|
||||||
|
Result register(RegisterDto registerDto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户登录
|
||||||
|
*/
|
||||||
|
Result<LoginVo> login(LoginDto loginDto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户登出
|
||||||
|
*/
|
||||||
|
void logout(String token);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户信息
|
||||||
|
*/
|
||||||
|
UserInfoVo getCurrentUserInfo();
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package com.bicloud.service.impl;
|
package com.bicloud.service.impl;
|
||||||
|
|
||||||
import com.bicloud.common.result.PageResult;
|
import com.bicloud.common.result.PageResult;
|
||||||
import com.bicloud.mapper.LiteratureMapper;
|
import com.bicloud.mapper.DocumentMapper;
|
||||||
import com.bicloud.pojo.dto.LiteratureCreateDto;
|
import com.bicloud.pojo.dto.DocumentCreateDto;
|
||||||
import com.bicloud.pojo.dto.LiteraturePageQueryDto;
|
import com.bicloud.pojo.dto.DocumentPageQueryDto;
|
||||||
import com.bicloud.pojo.vo.DocumentTypeVo;
|
import com.bicloud.pojo.vo.DocumentTypeVo;
|
||||||
import com.bicloud.pojo.vo.LiteratureListVo;
|
import com.bicloud.pojo.vo.DocumentListVo;
|
||||||
import com.bicloud.service.LiteratureService;
|
import com.bicloud.service.DocumentService;
|
||||||
import com.github.pagehelper.Page;
|
import com.github.pagehelper.Page;
|
||||||
import com.github.pagehelper.PageHelper;
|
import com.github.pagehelper.PageHelper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -15,25 +15,25 @@ import org.springframework.stereotype.Service;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class LiteratureServiceImpl implements LiteratureService {
|
public class DocumentServiceImpl implements DocumentService {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private LiteratureMapper literatureMapper;
|
private DocumentMapper documentMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addLiterature(LiteratureCreateDto dto) {
|
public void addDocument(DocumentCreateDto dto) {
|
||||||
// 新库:直接写入 biology_document_info(SQL 在 XML)
|
// 新库:直接写入 biology_document_info(SQL 在 XML)
|
||||||
literatureMapper.insertDocument(dto);
|
documentMapper.insertDocument(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteLiterature(Long id) {
|
public void deleteDocument(Long id) {
|
||||||
// 新库:软删除 removed=1
|
// 新库:软删除 removed=1
|
||||||
literatureMapper.softDeleteById(id);
|
documentMapper.softDeleteById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageResult<LiteratureListVo> pageQuery(LiteraturePageQueryDto queryDTO) {
|
public PageResult<DocumentListVo> pageQuery(DocumentPageQueryDto queryDTO) {
|
||||||
|
|
||||||
// 1) 兜底默认值,避免空指针/非法分页参数
|
// 1) 兜底默认值,避免空指针/非法分页参数
|
||||||
int page = (queryDTO.getPage() == null || queryDTO.getPage() < 1) ? 1 : queryDTO.getPage();
|
int page = (queryDTO.getPage() == null || queryDTO.getPage() < 1) ? 1 : queryDTO.getPage();
|
||||||
@@ -43,7 +43,7 @@ public class LiteratureServiceImpl implements LiteratureService {
|
|||||||
PageHelper.startPage(page, pageSize);
|
PageHelper.startPage(page, pageSize);
|
||||||
|
|
||||||
// 3) 执行 mapper 查询(PageHelper 自动分页 + 自动 count)
|
// 3) 执行 mapper 查询(PageHelper 自动分页 + 自动 count)
|
||||||
Page<LiteratureListVo> p = literatureMapper.pageQuery(queryDTO);
|
Page<DocumentListVo> p = documentMapper.pageQuery(queryDTO);
|
||||||
|
|
||||||
// 4) 取 total + records
|
// 4) 取 total + records
|
||||||
return new PageResult<>(p.getTotal(), p.getResult());
|
return new PageResult<>(p.getTotal(), p.getResult());
|
||||||
@@ -51,7 +51,7 @@ public class LiteratureServiceImpl implements LiteratureService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<DocumentTypeVo> listTypes() {
|
public List<DocumentTypeVo> listTypes() {
|
||||||
return literatureMapper.listTypes();
|
return documentMapper.listTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
333
src/main/java/com/bicloud/service/impl/UserServiceImpl.java
Normal file
333
src/main/java/com/bicloud/service/impl/UserServiceImpl.java
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
package com.bicloud.service.impl;
|
||||||
|
|
||||||
|
import com.bicloud.common.context.UserContext;
|
||||||
|
import com.bicloud.common.enums.LoginType;
|
||||||
|
import com.bicloud.common.exception.BusinessException;
|
||||||
|
import com.bicloud.common.result.Result;
|
||||||
|
import com.bicloud.common.utils.JwtUtils;
|
||||||
|
import com.bicloud.common.utils.PasswordUtils;
|
||||||
|
import com.bicloud.common.utils.RegexUtils;
|
||||||
|
import com.bicloud.config.JwtProperties;
|
||||||
|
import com.bicloud.mapper.UserMapper;
|
||||||
|
import com.bicloud.pojo.dto.LoginDto;
|
||||||
|
import com.bicloud.pojo.dto.RegisterDto;
|
||||||
|
import com.bicloud.pojo.entity.User;
|
||||||
|
import com.bicloud.pojo.vo.LoginVo;
|
||||||
|
import com.bicloud.pojo.vo.UserInfoVo;
|
||||||
|
import com.bicloud.service.UserService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static com.bicloud.common.utils.RedisConstants.*;
|
||||||
|
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class UserServiceImpl implements UserService {
|
||||||
|
@Resource
|
||||||
|
private StringRedisTemplate stringRedisTemplate;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private UserMapper userMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private JwtUtils jwtUtils;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private JwtProperties jwtProperties;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result sendCode(String mobile, HttpSession session, HttpServletRequest request) {
|
||||||
|
// 1.校验手机号
|
||||||
|
if (RegexUtils.isPhoneInvalid(mobile)) {
|
||||||
|
// 2.如果不符合,返回错误信息
|
||||||
|
return Result.error("手机号格式错误!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 获取客户端IP地址
|
||||||
|
String clientIpAddr = request.getRemoteAddr();
|
||||||
|
|
||||||
|
// 4.如果存在锁,直接返回失败
|
||||||
|
String phoneGetCodeLock = stringRedisTemplate.opsForValue().get(GET_CODE_LOCK + mobile);
|
||||||
|
if (phoneGetCodeLock != null) {
|
||||||
|
return Result.error("获取验证码过快,请稍后重试!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5.检查两个黑名单中的次数
|
||||||
|
String blacklistPhoneCount = stringRedisTemplate.opsForValue().get(GET_CODE_BLACKLIST_PHONE + mobile);
|
||||||
|
int getCodePhoneCount = (blacklistPhoneCount != null) ? Integer.parseInt(blacklistPhoneCount) : 0;
|
||||||
|
if (getCodePhoneCount >= 400) {
|
||||||
|
return Result.error("获取验证码次数过多,您的手机号已被限制!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// String blacklistIpAddrCount = stringRedisTemplate.opsForValue().get(GET_CODE_BLACKLIST_IP_ADDR + clientIpAddr);
|
||||||
|
// int getCodeIpAddrCount = (blacklistIpAddrCount != null) ? Integer.parseInt(blacklistIpAddrCount) : 0;
|
||||||
|
// if (getCodeIpAddrCount >= 300) {
|
||||||
|
// return Result.error("获取验证码次数过多,您的IP已被限制!");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 6.更新锁和黑名单
|
||||||
|
stringRedisTemplate.opsForValue().set(GET_CODE_BLACKLIST_PHONE + mobile, String.valueOf(getCodePhoneCount + 1), REFRESH_BLACKLIST_TTL, TimeUnit.HOURS);
|
||||||
|
// stringRedisTemplate.opsForValue().set(GET_CODE_BLACKLIST_IP_ADDR + clientIpAddr, String.valueOf(getCodeIpAddrCount + 1), REFRESH_BLACKLIST_TTL, TimeUnit.HOURS);
|
||||||
|
stringRedisTemplate.opsForValue().set(GET_CODE_LOCK + mobile, "1", GET_CODE_LOCK_TTL, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
// 7.校验通过,生成验证码
|
||||||
|
// String code = RandomUtil.randomNumbers(6);
|
||||||
|
String code = "123123"; //为了方便测试,改成固定值
|
||||||
|
|
||||||
|
// 8.保存验证码到 redis,并上锁
|
||||||
|
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + mobile, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
// 9.发送验证码
|
||||||
|
log.debug("发送短信验证码成功,验证码:{}", code);
|
||||||
|
// 10.返回ok
|
||||||
|
return Result.success("发送短信验证码成功,验证码:"+code);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result register(RegisterDto registerDto) {
|
||||||
|
// 1. 校验两次密码是否一致
|
||||||
|
if (!registerDto.getPwd().equals(registerDto.getCpwd())) {
|
||||||
|
return Result.error("两次密码输入不一致!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 校验验证码
|
||||||
|
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + registerDto.getMobile());
|
||||||
|
if (cacheCode == null) {
|
||||||
|
return Result.error("验证码已过期,请重新获取!");
|
||||||
|
}
|
||||||
|
if (!cacheCode.equals(registerDto.getVcode())) {
|
||||||
|
return Result.error("验证码错误!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查手机号是否已注册
|
||||||
|
User existingUserByPhone = userMapper.selectByMobile(registerDto.getMobile());
|
||||||
|
if (existingUserByPhone != null) {
|
||||||
|
return Result.error("该手机号已被注册!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4.检查邮箱是否已注册
|
||||||
|
User existingUserByEmail = userMapper.selectByEmail(registerDto.getEmail());
|
||||||
|
if (existingUserByEmail != null) {
|
||||||
|
return Result.error("该邮箱已被注册!");
|
||||||
|
}
|
||||||
|
// 检查用户名是否已注册
|
||||||
|
User existingUserByUsername = userMapper.selectByUsername(registerDto.getUsername());
|
||||||
|
if (existingUserByUsername != null) {
|
||||||
|
return Result.error("该用户名已被注册!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 生成盐值
|
||||||
|
String salt = PasswordUtils.generateSalt();
|
||||||
|
|
||||||
|
// 6. 加密密码
|
||||||
|
String encryptedPassword = PasswordUtils.encryptPassword(registerDto.getPwd(), salt);
|
||||||
|
|
||||||
|
// 7. 将感兴趣标签数组转换为字符串
|
||||||
|
String introduce = (registerDto.getInterestTags() != null && !registerDto.getInterestTags().isEmpty())
|
||||||
|
? String.join(",", registerDto.getInterestTags())
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// 8. 创建用户对象
|
||||||
|
User user = new User();
|
||||||
|
user.setUsername(registerDto.getUsername());
|
||||||
|
user.setCompany(registerDto.getCompany());
|
||||||
|
user.setJob(registerDto.getJob());
|
||||||
|
user.setAddress(registerDto.getAddress());
|
||||||
|
user.setIntroduce(introduce);
|
||||||
|
user.setMobile(registerDto.getMobile());
|
||||||
|
user.setEmail(registerDto.getEmail());
|
||||||
|
user.setPwd(encryptedPassword);
|
||||||
|
user.setSalt(salt);
|
||||||
|
user.setCreateTime(LocalDateTime.now());
|
||||||
|
user.setUpdateTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
// 9. 保存用户到数据库
|
||||||
|
int result = userMapper.insert(user);
|
||||||
|
if (result <= 0) {
|
||||||
|
return Result.error("注册失败,请稍后重试!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10. 删除Redis中的验证码
|
||||||
|
stringRedisTemplate.delete(LOGIN_CODE_KEY + registerDto.getMobile());
|
||||||
|
|
||||||
|
log.info("用户注册成功,手机号:{}", registerDto.getMobile());
|
||||||
|
return Result.success("注册成功!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<LoginVo> login(LoginDto loginDto) {
|
||||||
|
String identifier = loginDto.getIdentifier();
|
||||||
|
LoginType loginType = loginDto.getLoginType();
|
||||||
|
|
||||||
|
// 根据登录类型执行不同的登录逻辑
|
||||||
|
Result<User> userResult = null;
|
||||||
|
|
||||||
|
if (loginType == LoginType.PASSWORD) {
|
||||||
|
// 密码登录
|
||||||
|
userResult = loginByPassword(loginDto);
|
||||||
|
} else if (loginType == LoginType.SMS_CODE) {
|
||||||
|
// 验证码登录
|
||||||
|
userResult = loginBySmsCode(loginDto);
|
||||||
|
} else {
|
||||||
|
return Result.error("不支持的登录类型!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查登录结果(code != 200 表示失败)
|
||||||
|
if (userResult.getCode() != 200) {
|
||||||
|
return Result.error(userResult.getMsg());
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = userResult.getData();
|
||||||
|
|
||||||
|
// 生成Token
|
||||||
|
String token = jwtUtils.generateToken(user.getId(), user.getUsername());
|
||||||
|
|
||||||
|
// 构建返回结果
|
||||||
|
LoginVo loginVo = LoginVo.builder()
|
||||||
|
.token(token)
|
||||||
|
.expiresIn(jwtProperties.getTokenExpiration())
|
||||||
|
.userId(user.getId())
|
||||||
|
.username(user.getUsername())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return Result.success(loginVo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码登录
|
||||||
|
*/
|
||||||
|
private Result<User> loginByPassword(LoginDto loginDto) {
|
||||||
|
String identifier = loginDto.getIdentifier();
|
||||||
|
String password = loginDto.getPassword();
|
||||||
|
|
||||||
|
// 1. 校验密码不能为空
|
||||||
|
if (!StringUtils.hasText(password)) {
|
||||||
|
return Result.error("密码不能为空!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 根据登录标识类型查询用户
|
||||||
|
User user = null;
|
||||||
|
if (RegexUtils.isPhoneValid(identifier)) {
|
||||||
|
// 手机号登录
|
||||||
|
user = userMapper.selectByMobile(identifier);
|
||||||
|
} else if (RegexUtils.isEmailValid(identifier)) {
|
||||||
|
// 邮箱登录
|
||||||
|
user = userMapper.selectByEmail(identifier);
|
||||||
|
} else {
|
||||||
|
// 用户名登录
|
||||||
|
user = userMapper.selectByUsername(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 验证用户是否存在
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error("用户不存在!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 验证密码
|
||||||
|
if (!PasswordUtils.verifyPassword(password, user.getSalt(), user.getPwd())) {
|
||||||
|
return Result.error("密码错误!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 短信验证码登录
|
||||||
|
*/
|
||||||
|
private Result<User> loginBySmsCode(LoginDto loginDto) {
|
||||||
|
String identifier = loginDto.getIdentifier();
|
||||||
|
String code = loginDto.getCode();
|
||||||
|
|
||||||
|
// 1. 校验验证码不能为空
|
||||||
|
if (!StringUtils.hasText(code)) {
|
||||||
|
return Result.error("验证码不能为空!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 验证码登录只支持手机号
|
||||||
|
if (!RegexUtils.isPhoneValid(identifier)) {
|
||||||
|
return Result.error("验证码登录仅支持手机号!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 从Redis获取验证码
|
||||||
|
String cacheCode = null;
|
||||||
|
try {
|
||||||
|
cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + identifier);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("从Redis获取验证码失败,手机号:{},错误信息:{}", identifier, e.getMessage());
|
||||||
|
return Result.error("验证码获取失败,请稍后重试!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 验证码不存在或已过期
|
||||||
|
if (cacheCode == null) {
|
||||||
|
return Result.error("验证码已过期,请重新获取!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 验证验证码是否正确
|
||||||
|
if (!cacheCode.equals(code)) {
|
||||||
|
return Result.error("验证码错误!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 根据手机号查询用户
|
||||||
|
User user = null;
|
||||||
|
try {
|
||||||
|
user = userMapper.selectByMobile(identifier);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询用户失败,手机号:{},错误信息:{}", identifier, e.getMessage());
|
||||||
|
return Result.error("查询用户失败,请稍后重试!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error("该手机号未注册!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 验证码验证成功后删除Redis中的验证码(防止重复使用)
|
||||||
|
try {
|
||||||
|
stringRedisTemplate.delete(LOGIN_CODE_KEY + identifier);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("删除Redis验证码失败,手机号:{},错误信息:{}", identifier, e.getMessage());
|
||||||
|
// 删除失败不影响登录,只记录日志
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void logout(String token) {
|
||||||
|
// 简化版:不使用黑名单,由前端删除token即可
|
||||||
|
log.info("用户登出成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserInfoVo getCurrentUserInfo() {
|
||||||
|
// 1. 从上下文获取用户ID
|
||||||
|
Long userId = UserContext.getUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
throw new BusinessException("未登录!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 查询用户信息
|
||||||
|
User user = userMapper.selectById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
throw new BusinessException("用户不存在!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 构建返回结果
|
||||||
|
return UserInfoVo.builder()
|
||||||
|
.id(user.getId())
|
||||||
|
.username(user.getUsername())
|
||||||
|
.phone(user.getMobile())
|
||||||
|
.email(user.getEmail())
|
||||||
|
.createTime(user.getCreateTime())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,6 +16,18 @@ spring:
|
|||||||
connection-timeout: 30000
|
connection-timeout: 30000
|
||||||
maximum-pool-size: 10
|
maximum-pool-size: 10
|
||||||
minimum-idle: 5
|
minimum-idle: 5
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 6379
|
||||||
|
password: 123456
|
||||||
|
database: 2
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
max-active: 10
|
||||||
|
max-idle: 10
|
||||||
|
min-idle: 1
|
||||||
|
time-between-eviction-runs: 10s
|
||||||
|
|
||||||
mybatis:
|
mybatis:
|
||||||
mapper-locations: classpath*:mapper/*.xml
|
mapper-locations: classpath*:mapper/*.xml
|
||||||
@@ -40,3 +52,8 @@ knife4j:
|
|||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
com.genepioneer.mapper: debug
|
com.genepioneer.mapper: debug
|
||||||
|
|
||||||
|
jwt:
|
||||||
|
secret: bicloud-jwt-secret-key-2024-spring-boot-application-secure-token-generation
|
||||||
|
token-expiration: 86400
|
||||||
|
token-header: Authorization
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="com.bicloud.mapper.LiteratureMapper">
|
<mapper namespace="com.bicloud.mapper.DocumentMapper">
|
||||||
|
|
||||||
<!-- 新增文献:写入 biology_document_info -->
|
<!-- 新增文献:写入 biology_document_info -->
|
||||||
<insert id="insertDocument" parameterType="com.bicloud.pojo.dto.LiteratureCreateDto" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insertDocument" parameterType="com.bicloud.pojo.dto.DocumentCreateDto" useGeneratedKeys="true" keyProperty="id">
|
||||||
INSERT INTO biology_document_info
|
INSERT INTO biology_document_info
|
||||||
(doc_type, doc_label, doc_title, doc_icon, doc_desp, doc_info,
|
(doc_type, doc_label, doc_title, doc_icon, doc_desp, doc_info,
|
||||||
doc_collect, position, author, publication, impact_factor,
|
doc_collect, position, author, publication, impact_factor,
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
2) 文献分类名来自 biology_document_type.name(join)
|
2) 文献分类名来自 biology_document_type.name(join)
|
||||||
3) IF范围:impact_factor 是varchar,先用 REGEXP 过滤“纯数字/小数”,再 CAST 比较
|
3) IF范围:impact_factor 是varchar,先用 REGEXP 过滤“纯数字/小数”,再 CAST 比较
|
||||||
-->
|
-->
|
||||||
<select id="pageQuery" resultType="com.bicloud.pojo.vo.LiteratureListVo">
|
<select id="pageQuery" resultType="com.bicloud.pojo.vo.DocumentListVo">
|
||||||
SELECT
|
SELECT
|
||||||
d.id,
|
d.id,
|
||||||
YEAR(d.publish_time) AS year,
|
YEAR(d.publish_time) AS year,
|
||||||
42
src/main/resources/mapper/UserMapper.xml
Normal file
42
src/main/resources/mapper/UserMapper.xml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.bicloud.mapper.UserMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="com.bicloud.pojo.entity.User">
|
||||||
|
<id column="id" property="id"/>
|
||||||
|
<result column="username" property="username"/>
|
||||||
|
<result column="company" property="company"/>
|
||||||
|
<result column="job" property="job"/>
|
||||||
|
<result column="address" property="address"/>
|
||||||
|
<result column="introduce" property="introduce"/>
|
||||||
|
<result column="mobile" property="mobile"/>
|
||||||
|
<result column="email" property="email"/>
|
||||||
|
<result column="pwd" property="pwd"/>
|
||||||
|
<result column="salt" property="salt"/>
|
||||||
|
<result column="create_time" property="createTime"/>
|
||||||
|
<result column="update_time" property="updateTime"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="selectByMobile" resultMap="BaseResultMap">
|
||||||
|
SELECT * FROM users WHERE mobile = #{mobile}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByEmail" resultMap="BaseResultMap">
|
||||||
|
SELECT * FROM users WHERE email = #{email}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByUsername" resultMap="BaseResultMap">
|
||||||
|
SELECT * FROM users WHERE username = #{username}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectById" resultMap="BaseResultMap">
|
||||||
|
SELECT * FROM users WHERE id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insert" parameterType="com.bicloud.pojo.entity.User" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO users (username, company, job, address, introduce, mobile, email, pwd, salt, addtime, updtime)
|
||||||
|
VALUES (#{username}, #{company}, #{job}, #{address}, #{introduce}, #{mobile}, #{email}, #{pwd}, #{salt}, #{createTime}, #{updateTime})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
18
src/main/resources/sql/users.sql
Normal file
18
src/main/resources/sql/users.sql
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
-- 用户表
|
||||||
|
CREATE TABLE IF NOT EXISTS `users` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID',
|
||||||
|
`username` VARCHAR(50) NOT NULL COMMENT '用户名',
|
||||||
|
`company` VARCHAR(100) NOT NULL COMMENT '所属单位',
|
||||||
|
`job` VARCHAR(50) NOT NULL COMMENT '职位',
|
||||||
|
`address` VARCHAR(200) NOT NULL COMMENT '所在地址',
|
||||||
|
`introduce` VARCHAR(500) DEFAULT NULL COMMENT '感兴趣标签',
|
||||||
|
`phone` VARCHAR(11) NOT NULL COMMENT '手机号',
|
||||||
|
`email` VARCHAR(100) NOT NULL COMMENT '邮箱地址',
|
||||||
|
`pwd` VARCHAR(64) NOT NULL COMMENT '密码(加密后)',
|
||||||
|
`salt` VARCHAR(32) NOT NULL COMMENT '盐值',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_phone` (`phone`) COMMENT '手机号唯一索引',
|
||||||
|
UNIQUE KEY `uk_email` (`email`) COMMENT '邮箱唯一索引'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
|
||||||
@@ -1,13 +1,18 @@
|
|||||||
package com.bicloud;
|
package com.bicloud;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
class BicloudApplicationTests {
|
class BicloudApplicationTests {
|
||||||
|
@Resource
|
||||||
|
private StringRedisTemplate stringRedisTemplate;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void contextLoads() {
|
void contextLoads() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user