feat(user): 新增用户注册、登录和删除功能

- 新增用户实体类和相关控制器、服务类
- 实现用户注册、登录和删除功能
- 添加用户token生成和验证逻辑
-优化密码存储,使用加密算法
This commit is contained in:
ikmkj
2025-06-16 20:55:08 +08:00
parent 355ba0bad9
commit 3eb2edbe85
12 changed files with 250 additions and 15 deletions

View File

@@ -87,6 +87,13 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 密码加密-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.7.1</version> <!-- 使用最新稳定版 -->
</dependency>
<!-- SHA256计算工具 -->
<dependency>

View File

@@ -0,0 +1,47 @@
package com.test.bijihoudaun.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Knife4jConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("API文档")
.version("1.0")
.description("系统API文档")
);
}
@Bean
public GroupedOpenApi commonApi() {
return GroupedOpenApi.builder()
.group("图片接口")
.pathsToMatch("/api/images/**")
.build();
}
@Bean
public GroupedOpenApi tokenApi() {
return GroupedOpenApi.builder()
.group("markdown接口")
.pathsToMatch("/api/markdown/**")
.build();
}
@Bean
public GroupedOpenApi LotteryResultsApi() {
return GroupedOpenApi.builder()
.group("用户接口")
.pathsToMatch("/api/user/**")
.build();
}
}

View File

@@ -0,0 +1,14 @@
package com.test.bijihoudaun.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

View File

@@ -3,13 +3,19 @@ package com.test.bijihoudaun.controller;
import com.test.bijihoudaun.entity.Image;
import com.test.bijihoudaun.service.ImageService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@Tag(name = "markdown接口")
@RestController
@RequestMapping("/api/images")
public class ImageController {
@@ -17,6 +23,12 @@ public class ImageController {
@Autowired
private ImageService imageService;
@Operation(summary = "上传图片")
@Parameters({
@Parameter(name = "userId", description = "用户id", required = true),
@Parameter(name = "markdownId", description = "markdownid", required = true),
@Parameter(name = "file", description = "图片文件", required = true)
})
@PostMapping
public ResponseEntity<Image> uploadImage(
@RequestParam Integer userId,

View File

@@ -2,10 +2,14 @@ package com.test.bijihoudaun.controller;
import com.test.bijihoudaun.entity.MarkdownFile;
import com.test.bijihoudaun.service.MarkdownFileService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@Tag(name = "markdown接口")
@RestController
@RequestMapping("/api/markdown")
public class MarkdownController {
@@ -13,6 +17,13 @@ public class MarkdownController {
@Autowired
private MarkdownFileService markdownFileService;
@Operation(summary = "创建markdown文件")
@Parameters({
@Parameter(name = "userId", description = "用户id",required = true),
@Parameter(name = "title", description = "标题",required = true),
@Parameter(name = "fileName", description = "文件名",required = true),
@Parameter(name = "content", description = "内容",required = true)
})
@PostMapping
public ResponseEntity<MarkdownFile> createMarkdown(
@RequestParam Integer userId,
@@ -26,7 +37,12 @@ public class MarkdownController {
return ResponseEntity.ok(file);
}
@PutMapping("/{id}")
@Operation(summary = "更新Markdown文件")
@Parameters({
@Parameter(name = "id", description = "Markdown文件ID", required = true),
@Parameter(name = "content", description = "Markdown文件内容", required = true)
})
@PostMapping("/{id}")
public ResponseEntity<MarkdownFile> updateMarkdown(
@PathVariable Integer id,
@RequestBody String content) {

View File

@@ -0,0 +1,55 @@
package com.test.bijihoudaun.controller;
import com.test.bijihoudaun.common.response.R;
import com.test.bijihoudaun.entity.User;
import com.test.bijihoudaun.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "用户接口")
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
@Operation(summary = "用户注册")
@Parameters({
@Parameter(name = "username", description = "用户名",required = true),
@Parameter(name = "password", description = "密码",required = true),
@Parameter(name = "email", description = "邮箱",required = true)
})
@PostMapping("/register")
public R<User> register(String username, String password, String email){
return R.success(userService.register(username,password,email));
}
@Operation(summary = "用户登录")
@Parameters({
@Parameter(name = "username", description = "用户名",required = true),
@Parameter(name = "password", description = "密码",required = true)
})
@PostMapping("/login")
public R<User> login(String username, String password){
return R.success(userService.login(username,password));
}
@Operation(summary = "用户删除")
@Parameters({
@Parameter(name = "id", description = "用户id",required = true)
})
@DeleteMapping("/deleteUser")
public R<String> deleteUser(Integer id){
userService.deleteUser(id);
return R.success("删除成功");
}
}

View File

@@ -4,27 +4,37 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Schema(name = "图片实体")
@TableName("image")
public class Image {
@Schema(description = "图片id",implementation = Long.class)
@TableId(type = IdType.AUTO)
private Integer id;
private Integer userId;
private Integer markdownId;
private Long id;
@Schema(description = "图片名称",implementation = Long.class )
private Long userId;
@Schema(description = "图片名称",implementation = Long.class )
private Long markdownId;
@Schema(description = "图片名称",implementation = String.class )
@TableField("original_name")
private String originalName;
@Schema(description = "图片名称",implementation = String.class )
@TableField("stored_name")
private String storedName;
@Schema(description = "图片名称",implementation = String.class )
private String url;
private Integer size;
@Schema(description = "图片大小",implementation = Long.class )
private Long size;
@Schema(description = "图片类型",implementation = String.class )
@TableField("content_type")
private String contentType;

View File

@@ -4,22 +4,31 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Schema(name = "文本实体")
@TableName("markdown_file")
public class MarkdownFile {
@Schema(description = "文本id",implementation = Long.class)
@TableId(type = IdType.AUTO)
private Integer id;
private Integer userId;
private Long id;
@Schema(description = "用户id",implementation = Long.class)
private Long userId;
@Schema(description = "文本标题",implementation = String.class)
private String title;
@Schema(description = "文本内容",implementation = String.class)
@TableField("file_name")
private String fileName;
@Schema(description = "文本内容",implementation = String.class)
private String content;
@Schema(description = "创建时间",implementation = LocalDateTime.class)
private LocalDateTime createdAt;
@Schema(description = "更新时间",implementation = LocalDateTime.class)
private LocalDateTime updatedAt;
}

View File

@@ -3,20 +3,30 @@ package com.test.bijihoudaun.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Schema(name = "用户实体")
@TableName("user")
public class User {
@Schema(description = "用户id",implementation = Long.class)
@TableId(type = IdType.AUTO)
private Integer id;
private Long id;
@Schema(description = "用户名",implementation = String.class)
private String username;
@Schema(description = "密码",implementation = String.class)
private String password;
@Schema(description = "邮箱",implementation = String.class)
private String email;
@Schema(description = "用户创建时间",implementation = String.class)
private LocalDateTime createdAt;
@Schema(description = "用户更新时间",implementation = String.class)
private LocalDateTime updatedAt;
@Schema(description = "用户token",implementation = String.class)
private String token;
@Schema(description = "用户token过期时间",implementation = String.class)
private LocalDateTime tokenEnddata;
}

View File

@@ -26,4 +26,11 @@ public interface UserService extends IService<User> {
* @param id 用户id
*/
void deleteUser(Integer id);
/**
* 查询用户token是否过期
* @param id 用户id
* @return Boolean
*/
Boolean isTokenExpired(Long id,String token);
}

View File

@@ -1,12 +1,13 @@
package com.test.bijihoudaun.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.test.bijihoudaun.entity.User;
import com.test.bijihoudaun.mapper.UserMapper;
import com.test.bijihoudaun.service.UserService;
import com.test.bijihoudaun.util.PasswordUtils;
import com.test.bijihoudaun.util.UuidV7;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -20,6 +21,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
@Override
public User register(String username, String password, String email) {
String encrypt = PasswordUtils.encrypt(password);
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername, username);
if (this.count(queryWrapper) > 0) {
@@ -32,7 +34,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
}
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setPassword(encrypt);
user.setEmail(email);
user.setCreatedAt(LocalDateTime.now());
userMapper.insert(user);
@@ -42,13 +44,32 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
@Override
public User login(String username, String password) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername, username)
.eq(User::getPassword, password);
return userMapper.selectOne(queryWrapper);
queryWrapper.eq(User::getUsername, username);
User user = userMapper.selectOne(queryWrapper);
boolean verify = PasswordUtils.verify(password, user.getPassword());
if (!verify) {
throw new RuntimeException("密码错误");
}
user.setToken(UuidV7.uuidNoHyphen());
// 过期时间:当前时间+3天的时间
user.setTokenEnddata(LocalDateTime.now().plusDays(3));
userMapper.updateById(user);
return user;
}
@Override
public void deleteUser(Integer id) {
userMapper.deleteById(id);
}
@Override
public Boolean isTokenExpired(Long id, String token) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getId, id)
.eq(User::getToken, token);
User user = getOne(queryWrapper);
// 新增过期检查
return user != null && LocalDateTime.now().isBefore(user.getTokenEnddata());
}
}

View File

@@ -0,0 +1,27 @@
package com.test.bijihoudaun.util;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class PasswordUtils {
// 初始化加密器工作因子默认10
private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
/**
* 加密密码
* @param rawPassword 原始密码
* @return 加密后的哈希值包含salt
*/
public static String encrypt(String rawPassword) {
return encoder.encode(rawPassword);
}
/**
* 验证密码
* @param rawPassword 用户输入的原始密码
* @param encodedPassword 数据库存储的加密密码
* @return 是否匹配
*/
public static boolean verify(String rawPassword, String encodedPassword) {
return encoder.matches(rawPassword, encodedPassword);
}
}