feat: 添加用户登录功能

- 创建 SysUserMapper 数据访问层
- 创建 SysUserService/SysUserServiceImpl 业务逻辑层
- 创建 AuthController 认证控制器
- 实现登录、注册、登出接口
- 添加全局异常处理器
- 配置 Sa-Token 跨域支持
- 使用 BCrypt 密码加密
This commit is contained in:
likingcode
2026-03-01 23:30:56 +00:00
parent 8c6729129c
commit a40c411731
9 changed files with 347 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
package com.innovation.platform.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Sa-Token 配置类
*/
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
/**
* 跨域配置
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}

View File

@@ -0,0 +1,48 @@
package com.innovation.platform.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import com.innovation.platform.common.Result;
import com.innovation.platform.dto.LoginRequest;
import com.innovation.platform.dto.LoginResponse;
import com.innovation.platform.dto.RegisterRequest;
import com.innovation.platform.service.SysUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* 认证控制器
*/
@Tag(name = "认证管理", description = "登录、注册、登出接口")
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final SysUserService sysUserService;
@SaIgnore
@Operation(summary = "用户登录")
@PostMapping("/login")
public Result<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
LoginResponse response = sysUserService.login(request);
return Result.success("登录成功", response);
}
@SaIgnore
@Operation(summary = "用户注册")
@PostMapping("/register")
public Result<Void> register(@Valid @RequestBody RegisterRequest request) {
sysUserService.register(request);
return Result.success("注册成功", null);
}
@Operation(summary = "用户登出")
@PostMapping("/logout")
public Result<Void> logout() {
sysUserService.logout();
return Result.success("登出成功", null);
}
}

View File

@@ -0,0 +1,16 @@
package com.innovation.platform.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 登录请求 DTO
*/
@Data
public class LoginRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
}

View File

@@ -0,0 +1,22 @@
package com.innovation.platform.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 登录响应 DTO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoginResponse {
private String token;
private Long userId;
private String username;
private String realName;
private String avatar;
private Integer roleType;
}

View File

@@ -0,0 +1,30 @@
package com.innovation.platform.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
/**
* 注册请求 DTO
*/
@Data
public class RegisterRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
@NotBlank(message = "真实姓名不能为空")
private String realName;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Pattern(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", message = "邮箱格式不正确")
private String email;
private Integer gender;
private Integer roleType;
}

View File

@@ -0,0 +1,61 @@
package com.innovation.platform.exception;
import com.innovation.platform.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.stream.Collectors;
/**
* 全局异常处理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 业务异常
*/
@ExceptionHandler(RuntimeException.class)
public Result<Void> handleRuntimeException(RuntimeException e) {
log.error("业务异常: {}", e.getMessage(), e);
return Result.error(400, e.getMessage());
}
/**
* 参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValidationException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
log.error("参数校验失败: {}", message);
return Result.error(400, message);
}
/**
* 绑定异常
*/
@ExceptionHandler(BindException.class)
public Result<Void> handleBindException(BindException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
log.error("参数绑定失败: {}", message);
return Result.error(400, message);
}
/**
* 其他异常
*/
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
log.error("系统异常: {}", e.getMessage(), e);
return Result.error(500, "系统异常,请稍后重试");
}
}

View File

@@ -0,0 +1,12 @@
package com.innovation.platform.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.innovation.platform.entity.SysUser;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户 Mapper 接口
*/
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
}

View File

@@ -0,0 +1,37 @@
package com.innovation.platform.service;
import com.innovation.platform.dto.LoginRequest;
import com.innovation.platform.dto.LoginResponse;
import com.innovation.platform.dto.RegisterRequest;
import com.innovation.platform.entity.SysUser;
/**
* 用户服务接口
*/
public interface SysUserService {
/**
* 用户登录
*/
LoginResponse login(LoginRequest request);
/**
* 用户注册
*/
void register(RegisterRequest request);
/**
* 用户登出
*/
void logout();
/**
* 根据用户名查询用户
*/
SysUser getByUsername(String username);
/**
* 根据ID查询用户
*/
SysUser getById(Long id);
}

View File

@@ -0,0 +1,96 @@
package com.innovation.platform.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.innovation.platform.dto.LoginRequest;
import com.innovation.platform.dto.LoginResponse;
import com.innovation.platform.dto.RegisterRequest;
import com.innovation.platform.entity.SysUser;
import com.innovation.platform.mapper.SysUserMapper;
import com.innovation.platform.service.SysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 用户服务实现类
*/
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl implements SysUserService {
private final SysUserMapper sysUserMapper;
@Override
public LoginResponse login(LoginRequest request) {
// 查询用户
SysUser user = getByUsername(request.getUsername());
if (user == null) {
throw new RuntimeException("用户名或密码错误");
}
// 验证密码
if (!BCrypt.checkpw(request.getPassword(), user.getPassword())) {
throw new RuntimeException("用户名或密码错误");
}
// 检查用户状态
if (user.getStatus() != null && user.getStatus() == 0) {
throw new RuntimeException("账号已被禁用");
}
// 登录
StpUtil.login(user.getId());
// 返回登录信息
return LoginResponse.builder()
.token(StpUtil.getTokenValue())
.userId(user.getId())
.username(user.getUsername())
.realName(user.getRealName())
.avatar(user.getAvatar())
.roleType(user.getRoleType())
.build();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void register(RegisterRequest request) {
// 检查用户名是否已存在
if (getByUsername(request.getUsername()) != null) {
throw new RuntimeException("用户名已存在");
}
// 创建用户
SysUser user = new SysUser();
user.setUsername(request.getUsername());
user.setPassword(BCrypt.hashpw(request.getPassword()));
user.setRealName(request.getRealName());
user.setPhone(request.getPhone());
user.setEmail(request.getEmail());
user.setGender(request.getGender());
user.setRoleType(request.getRoleType() != null ? request.getRoleType() : 0);
user.setStatus(1);
sysUserMapper.insert(user);
}
@Override
public void logout() {
StpUtil.logout();
}
@Override
public SysUser getByUsername(String username) {
return sysUserMapper.selectOne(
new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getUsername, username)
);
}
@Override
public SysUser getById(Long id) {
return sysUserMapper.selectById(id);
}
}