集成mysql,重构用户逻辑

This commit is contained in:
luke 2025-06-06 22:17:14 +08:00
parent e132958d1b
commit 3a6df99b65
25 changed files with 422 additions and 82 deletions

28
pom.xml
View File

@ -29,7 +29,7 @@
<jackson-databind.version>2.13.4.2</jackson-databind.version>
<jackson-core.version>2.13.4</jackson-core.version>
<lombok.version>1.18.24</lombok.version>
<lombok.version>1.18.30</lombok.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
<hutool-all.version>5.7.20</hutool-all.version>
<guava.version>31.0.1-jre</guava.version>
@ -40,6 +40,8 @@
<poi.version>5.2.3</poi.version>
<poi-ooxml.version>5.2.3</poi-ooxml.version>
<mysql-connector-java.version>8.0.33</mysql-connector-java.version>
<mybatis-plus-boot-starter.version>3.5.5</mybatis-plus-boot-starter.version>
</properties>
<dependencyManagement>
<dependencies>
@ -181,6 +183,19 @@
<version>${poi-ooxml.version}</version>
</dependency>
<!-- MySQL JDBC 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
</dependency>
<!-- MyBatis-Plus 启动器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
@ -271,6 +286,7 @@
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
@ -303,6 +319,16 @@
<artifactId>poi-ooxml</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- MyBatis-Plus 启动器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -1,6 +1,8 @@
package com.knowledge.base.application.filter;
import com.knowledge.base.domain.user.service.UserService;
import com.knowledge.base.infrastructure.north.controller.UserController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.*;
@ -15,6 +17,9 @@ import java.io.IOException;
@Component
public class AuthFilter implements Filter {
@Autowired
private UserService userService;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
@ -27,7 +32,7 @@ public class AuthFilter implements Filter {
// 需要鉴权的路径前缀
if (path.startsWith("/search")) {
String token = request.getHeader("Authorization");
if (!UserController.isValidToken(token)) {
if (!userService.isValidToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Invalid or expired token");
return;

View File

@ -1,4 +1,4 @@
package com.knowledge.base.domain.enums;
package com.knowledge.base.domain.common.enums;
/**
* @author Luke.ye
@ -7,7 +7,7 @@ package com.knowledge.base.domain.enums;
public enum DocTypeEnum {
MARKDOWN("markdown", "markdown"),
HTML("html", "html"),
HTML("xlsx", "excel"),
PDF("pdf", "pdf"),
WORD("word", "word"),

View File

@ -1,6 +1,6 @@
package com.knowledge.base.domain.iface;
package com.knowledge.base.domain.doc.iface;
import com.knowledge.base.domain.enums.DocTypeEnum;
import com.knowledge.base.domain.common.enums.DocTypeEnum;
/**
* @author Luke.ye

View File

@ -1,6 +1,6 @@
package com.knowledge.base.domain.impl;
package com.knowledge.base.domain.doc.impl;
import com.knowledge.base.domain.iface.DocumentImporter;
import com.knowledge.base.domain.doc.iface.DocumentImporter;
import com.knowledge.base.infrastructure.util.LocalCacheUtil;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetRequest;

View File

@ -1,4 +1,4 @@
package com.knowledge.base.domain.impl;
package com.knowledge.base.domain.doc.impl;
/**
* @author Luke.ye

View File

@ -1,6 +1,6 @@
package com.knowledge.base.domain.impl;
package com.knowledge.base.domain.doc.impl;
import com.knowledge.base.domain.enums.DocTypeEnum;
import com.knowledge.base.domain.common.enums.DocTypeEnum;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

View File

@ -1,6 +1,6 @@
package com.knowledge.base.domain.impl;
package com.knowledge.base.domain.doc.impl;
import com.knowledge.base.domain.enums.DocTypeEnum;
import com.knowledge.base.domain.common.enums.DocTypeEnum;
import org.springframework.stereotype.Component;
import org.apache.pdfbox.pdmodel.PDDocument;

View File

@ -1,6 +1,6 @@
package com.knowledge.base.domain.impl;
package com.knowledge.base.domain.doc.impl;
import com.knowledge.base.domain.enums.DocTypeEnum;
import com.knowledge.base.domain.common.enums.DocTypeEnum;
import org.springframework.stereotype.Component;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;

View File

@ -1,6 +1,6 @@
package com.knowledge.base.domain.schedule;
import com.knowledge.base.domain.iface.DocumentImporter;
import com.knowledge.base.domain.doc.iface.DocumentImporter;
import com.knowledge.base.infrastructure.config.DynamicConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -0,0 +1,18 @@
package com.knowledge.base.domain.user.repository.iface;
import com.knowledge.base.domain.user.repository.po.User;
import java.util.Optional;
/**
* @author Luke.ye
* @date 2025/6/6 12:20
*/
public interface UserRepository {
Optional<User> findByUsername(String username);
void save(User user);
Optional<User> findById(Long id);
}

View File

@ -0,0 +1,18 @@
package com.knowledge.base.domain.user.repository.iface;
import com.knowledge.base.domain.user.repository.po.UserToken;
import java.util.Optional;
/**
* @author Luke.ye
* @date 2025/6/6 12:20
*/
public interface UserTokenRepository {
void save(UserToken token);
Optional<UserToken> findByToken(String token);
void deleteByToken(String token);
void deleteByUserId(Long userId);
}

View File

@ -0,0 +1,27 @@
package com.knowledge.base.domain.user.repository.po;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* @author Luke.ye
* @date 2025/6/6 12:16
*/
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime addTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,28 @@
package com.knowledge.base.domain.user.repository.po;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("user_token")
public class UserToken {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private String token;
private Long loginAt;
private Long expiredAt;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime addTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,20 @@
package com.knowledge.base.domain.user.service;
import com.knowledge.base.domain.user.repository.po.User;
import com.knowledge.base.domain.user.repository.po.UserToken;
import java.util.Optional;
public interface UserService {
Optional<User> findByUsername(String username);
boolean verifyPassword(User user, String plainText);
boolean registerUser(String username, String plainPassword);
UserToken createToken(Long userId, long expireMs);
Optional<UserToken> findToken(String token);
void removeToken(String token);
boolean isValidToken(String token);
Optional<User> findById(Long id);
}

View File

@ -0,0 +1,78 @@
package com.knowledge.base.domain.user.service;
import cn.hutool.crypto.digest.SM3;
import com.knowledge.base.domain.user.repository.iface.UserRepository;
import com.knowledge.base.domain.user.repository.iface.UserTokenRepository;
import com.knowledge.base.domain.user.repository.po.User;
import com.knowledge.base.domain.user.repository.po.UserToken;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Optional;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final UserTokenRepository tokenRepository;
@Override
public Optional<User> findByUsername(String username) {
return userRepository.findByUsername(username);
}
@Override
public boolean verifyPassword(User user, String plainText) {
return user.getPassword().equalsIgnoreCase(new SM3().digestHex(plainText));
}
@Override
public boolean registerUser(String username, String plainPassword) {
if (userRepository.findByUsername(username).isPresent()) {
return false;
}
User user = new User();
user.setUsername(username);
user.setPassword(new SM3().digestHex(plainPassword));
userRepository.save(user);
return true;
}
@Override
public UserToken createToken(Long userId, long expireMs) {
String token = UUID.randomUUID().toString().replace("-", "");
long now = System.currentTimeMillis();
UserToken userToken = new UserToken();
userToken.setUserId(userId);
userToken.setToken(token);
userToken.setLoginAt(now);
userToken.setExpiredAt(now + expireMs);
tokenRepository.save(userToken);
return userToken;
}
@Override
public Optional<UserToken> findToken(String token) {
return tokenRepository.findByToken(token);
}
@Override
public void removeToken(String token) {
tokenRepository.deleteByToken(token);
}
@Override
public boolean isValidToken(String token) {
return tokenRepository.findByToken(token)
.filter(t -> System.currentTimeMillis() < t.getExpiredAt())
.isPresent();
}
@Override
public Optional<User> findById(Long id) {
return userRepository.findById(id);
}
}

View File

@ -1,6 +1,6 @@
package com.knowledge.base.infrastructure.north.controller;
import com.knowledge.base.domain.iface.DocumentImporter;
import com.knowledge.base.domain.doc.iface.DocumentImporter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

View File

@ -1,62 +1,51 @@
package com.knowledge.base.infrastructure.north.controller;
import cn.hutool.extra.spring.SpringUtil;
import com.knowledge.base.domain.user.repository.po.User;
import com.knowledge.base.domain.user.repository.po.UserToken;
import com.knowledge.base.domain.user.service.UserService;
import com.knowledge.base.infrastructure.config.ConstantConfig;
import com.knowledge.base.infrastructure.config.DynamicConfig;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
import java.util.Optional;
/**
* @author Luke.ye
* @date 2025/5/25 10:02
*/
@RestController
@RequestMapping("/api/v1/user")
@RequiredArgsConstructor
public class UserController {
@Autowired
private DynamicConfig dynamicConfig;
private static final Map<String, String> USER_DB = Map.of(
"admin", "Admin@654321",
"lukeye", "lukeye"
);
// token -> timestamp
private static final Map<String, Long> tokenStore = new ConcurrentHashMap<>();
// token -> username
private static final Map<String, String> userMap = new ConcurrentHashMap<>();
private final UserService userService;
private final DynamicConfig dynamicConfig;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody Map<String, String> body, HttpServletResponse response) {
String username = body.getOrDefault("username", "").trim();
String password = body.getOrDefault("password", "").trim();
if (!USER_DB.containsKey(username) || !USER_DB.get(username).equals(password)) {
Optional<User> userOpt = userService.findByUsername(username);
if (userOpt.isEmpty() || !userService.verifyPassword(userOpt.get(), password)) {
return ResponseEntity.status(401).body(Map.of("message", "用户名或密码错误"));
}
String token = UUID.randomUUID().toString().replace("-", "");
tokenStore.put(token, System.currentTimeMillis());
userMap.put(token, username);
User user = userOpt.get();
long expireMs = dynamicConfig.getTokenExpireTime();
UserToken token = userService.createToken(user.getId(), expireMs);
Cookie cookie = new Cookie( ConstantConfig.COOKIE_KEY, token);
Cookie cookie = new Cookie(ConstantConfig.COOKIE_KEY, token.getToken());
cookie.setPath("/");
cookie.setDomain(dynamicConfig.getCookieDomainName());
cookie.setHttpOnly(true); // 建议生产环境为 true这里为兼容前端 JS 读写可设为 false
cookie.setMaxAge((int)(dynamicConfig.getTokenExpireTime() / 1000));
cookie.setHttpOnly(true);
cookie.setMaxAge((int) (expireMs / 1000));
response.addCookie(cookie);
return ResponseEntity.ok(Map.of(
"token", token,
"username", username
"token", token.getToken(),
"username", user.getUsername()
));
}
@ -67,57 +56,46 @@ public class UserController {
HttpServletResponse response
) {
String token = headerToken != null ? headerToken : cookieToken;
if (token != null && tokenStore.containsKey(token)) {
tokenStore.remove(token);
userMap.remove(token);
if (token != null) {
userService.removeToken(token);
}
// 删除 Cookie
Cookie cookie = new Cookie( ConstantConfig.COOKIE_KEY, "");
Cookie cookie = new Cookie(ConstantConfig.COOKIE_KEY, "");
cookie.setPath("/");
cookie.setDomain(dynamicConfig.getCookieDomainName());
cookie.setMaxAge(0); // 立即失效
cookie.setHttpOnly(true);
cookie.setMaxAge(0);
response.addCookie(cookie);
return ResponseEntity.ok(Map.of("msg", "已登出"));
}
@GetMapping("/check")
public ResponseEntity<?> check(
@CookieValue(value = "auth_token", required = false) String cookieToken,
@CookieValue(value = ConstantConfig.COOKIE_KEY, required = false) String cookieToken,
@RequestHeader(value = "Authorization", required = false) String headerToken
) {
String token = headerToken != null ? headerToken : cookieToken;
if (isValidToken(token)) {
Map<String, Object> result = new HashMap<>();
result.put("code", 0);
result.put("msg", "校验成功");
return ResponseEntity.ok(result);
if (userService.isValidToken(token)) {
return ResponseEntity.ok(Map.of("code", 0, "msg", "校验成功"));
}
Map<String, Object> result = new HashMap<>();
result.put("code", 401);
result.put("msg", "未授权");
return ResponseEntity.status(401).body(result);
return ResponseEntity.status(401).body(Map.of("code", 401, "msg", "未授权"));
}
public static boolean isValidToken(String token) {
if (token == null || !tokenStore.containsKey(token)) return false;
long loginAt = tokenStore.get(token);
long now = System.currentTimeMillis();
long expireMs = SpringUtil.getBean(DynamicConfig.class).getTokenExpireTime();
if (now - loginAt > expireMs) {
tokenStore.remove(token);
userMap.remove(token);
return false;
@GetMapping("/username")
public ResponseEntity<?> getUsernameByToken(
@CookieValue(value = ConstantConfig.COOKIE_KEY, required = false) String cookieToken,
@RequestHeader(value = "Authorization", required = false) String headerToken
) {
String token = headerToken != null ? headerToken : cookieToken;
Optional<UserToken> userTokenOpt = userService.findToken(token);
if (userTokenOpt.isPresent()) {
Long userId = userTokenOpt.get().getUserId();
Optional<User> userOpt = userService.findById(userId);
if (userOpt.isPresent()) {
return ResponseEntity.ok(Map.of("username", userOpt.get().getUsername()));
}
}
return true;
}
public static Optional<String> getUsername(String token) {
return Optional.ofNullable(userMap.get(token));
return ResponseEntity.status(401).body(Map.of("msg", "无效token"));
}
}

View File

@ -0,0 +1,9 @@
package com.knowledge.base.infrastructure.repository.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.knowledge.base.domain.user.repository.po.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

View File

@ -0,0 +1,9 @@
package com.knowledge.base.infrastructure.repository.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.knowledge.base.domain.user.repository.po.UserToken;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserTokenMapper extends BaseMapper<UserToken> {
}

View File

@ -0,0 +1,43 @@
package com.knowledge.base.infrastructure.repository.persistence;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.knowledge.base.domain.user.repository.iface.UserRepository;
import com.knowledge.base.domain.user.repository.po.User;
import com.knowledge.base.infrastructure.repository.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* @author Luke.ye
* @date 2025/6/6 12:21
*/
@Repository
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepository {
private final UserMapper userMapper;
@Override
public Optional<User> findByUsername(String username) {
return Optional.ofNullable(
userMapper.selectOne(
new QueryWrapper<User>()
.eq("username", username)
)
);
}
@Override
public void save(User user) {
userMapper.insert(user);
}
@Override
public Optional<User> findById(Long id) {
return Optional.ofNullable(userMapper.selectById(id));
}
}

View File

@ -0,0 +1,50 @@
package com.knowledge.base.infrastructure.repository.persistence;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.knowledge.base.domain.user.repository.iface.UserTokenRepository;
import com.knowledge.base.domain.user.repository.po.UserToken;
import com.knowledge.base.infrastructure.repository.mapper.UserTokenMapper;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public class UserTokenRepositoryImpl implements UserTokenRepository {
private final UserTokenMapper tokenMapper;
public UserTokenRepositoryImpl(UserTokenMapper tokenMapper) {
this.tokenMapper = tokenMapper;
}
@Override
public void save(UserToken token) {
tokenMapper.insert(token);
}
@Override
public Optional<UserToken> findByToken(String token) {
return Optional.ofNullable(
tokenMapper.selectOne(
new QueryWrapper<UserToken>()
.eq("token", token)
)
);
}
@Override
public void deleteByToken(String token) {
tokenMapper.delete(
new QueryWrapper<UserToken>()
.eq("token", token)
);
}
@Override
public void deleteByUserId(Long userId) {
tokenMapper.delete(
new QueryWrapper<UserToken>()
.eq("user_id", userId)
);
}
}

View File

@ -27,3 +27,13 @@ markdown.path=/Users/admin/Desktop/ahnx-share-src-public/public
pdf.path=/Users/admin/Desktop/ahnx-share-src-public/public
word.path=/Users/admin/Desktop/ahnx-share-src-public/public
excel.path=/Users/admin/Desktop/ahnx-share-src-public/public
# mysql
spring.datasource.url=jdbc:mysql://localhost:3306/kbase?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=lukeye@6
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# mybatis-plus
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.global-config.db-config.logic-delete-field=deleted
mybatis-plus.global-config.db-config.id-type=auto

View File

@ -26,4 +26,15 @@ exclude.file.path.prefix=D:/02-documents/01-ahnx-share-src-public/public
markdown.path=D:/02-documents/01-ahnx-share-src-public/public
pdf.path=D:/02-documents/01-ahnx-share-src-public/public
word.path=D:/02-documents/01-ahnx-share-src-public/public
excel.path=D:/02-documents/01-ahnx-share-src-public/public
excel.path=D:/02-documents/01-ahnx-share-src-public/public
# mysql
spring.datasource.url=jdbc:mysql://localhost:3306/kbase?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=lukeye
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# mybatis-plus
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.global-config.db-config.logic-delete-field=deleted
mybatis-plus.global-config.db-config.id-type=auto

View File

@ -28,4 +28,14 @@ word.path=/app/import-data
excel.path=/app/import-data
# 相对路径裁剪
exclude.file.path.prefix=/app/import-data
exclude.file.path.prefix=/app/import-data
# mysql
spring.datasource.url=jdbc:mysql://host.docker.internal:3306/kbase?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=lukeye
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# mybatis-plus
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.global-config.db-config.logic-delete-field=deleted
mybatis-plus.global-config.db-config.id-type=auto