用户管理系统 04 实现后端登录功能 Java新手实战 最小架构 期末实训 Java+SpringBoot+Vue3
B站视频教程:https://space.bilibili.com/3493114532596161/lists/6609623?type=season
关注B站"90后小陈老师 ",私信回复"Java"获取资料!
手把手教你实现安全的用户登录功能,掌握企业级开发核心技能
本文是用户管理系统实战系列的第四篇,重点讲解如何实现用户登录功能。作为系统最核心的功能之一,登录模块涉及参数校验、数据查询、安全处理等多个重要知识点。
🎯 本章学习目标
- ✅ 掌握用户登录的业务流程设计
- ✅ 学会参数校验和异常处理
- ✅ 理解Session会话管理机制
- ✅ 掌握用户数据脱敏处理技巧
🏗️ 登录功能架构设计
登录流程全景图
前端请求
↓
Controller层(参数基础校验)
↓
Service层(业务逻辑处理)
├── 参数格式校验
├── 数据库查询验证
├── 密码匹配判断
├── 用户状态检查
└── 数据脱敏处理
↓
Session存储登录状态
↓
返回脱敏后的用户信息
🔧 代码实现详解
1. Controller层实现 - 请求入口
位置 :src/main/java/com/jackson/usermanager/controller/UserController.java
java
package com.jackson.usermanager.controller;
import com.jackson.usermanager.common.Result;
import com.jackson.usermanager.model.domain.User;
import com.jackson.usermanager.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
/**
* 用户登录接口
* @param phone 手机号
* @param pwd 密码
* @param session HTTP会话
* @return 登录结果
*/
@PostMapping("/login")
public Result<User> login(@RequestParam String phone,
@RequestParam String pwd,
HttpSession session) {
// 1. 基础参数校验
if (!StringUtils.hasText(phone) || !StringUtils.hasText(pwd)) {
return Result.error("手机号和密码不能为空");
}
// 2. 调用Service层处理登录逻辑
Result<User> result = userService.login(phone, pwd);
// 3. 登录成功,将用户信息存入Session
if (result.getCode() == 200) {
User user = result.getData();
session.setAttribute("loginUser", user);
session.setMaxInactiveInterval(30 * 60); // 30分钟超时
}
return result;
}
}
2. Service层实现 - 核心业务逻辑
位置 :src/main/java/com/jackson/usermanager/service/UserService.java(接口)
java
package com.jackson.usermanager.service;
import com.jackson.usermanager.common.Result;
import com.jackson.usermanager.model.domain.User;
public interface UserService {
/**
* 用户登录
* @param phone 手机号
* @param pwd 密码
* @return 登录结果
*/
Result<User> login(String phone, String pwd);
}
位置 :src/main/java/com/jackson/usermanager/service/impl/UserServiceImpl.java(实现类)
java
package com.jackson.usermanager.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jackson.usermanager.common.Result;
import com.jackson.usermanager.mapper.UserMapper;
import com.jackson.usermanager.model.domain.User;
import com.jackson.usermanager.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.regex.Pattern;
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
// 手机号正则表达式
private static final String PHONE_REGEX = "^1[3-9]\\d{9}$";
// 密码最小长度
private static final int MIN_PASSWORD_LENGTH = 8;
@Override
public Result<User> login(String phone, String pwd) {
// 1. 参数格式校验
Result<String> validateResult = validateLoginParams(phone, pwd);
if (validateResult.getCode() != 200) {
return Result.error(validateResult.getMessage());
}
// 2. 查询用户信息(先根据手机号查询)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("phone", phone);
User user = userMapper.selectOne(queryWrapper);
// 3. 验证用户是否存在
if (user == null) {
return Result.error("用户不存在");
}
// 4. 验证密码
if (!validatePassword(pwd, user.getPwd())) {
return Result.error("密码错误");
}
// 5. 检查用户状态
if (user.getStatus() != null && user.getStatus() == 1) {
return Result.error("账户已被禁用");
}
// 6. 数据脱敏处理
User safeUser = desensitizeUserInfo(user);
return Result.success("登录成功", safeUser);
}
/**
* 登录参数校验
*/
private Result<String> validateLoginParams(String phone, String password) {
// 手机号格式校验
if (!patternMatches(PHONE_REGEX, phone)) {
return Result.error("手机号格式不正确");
}
// 密码长度校验
if (password.length() < MIN_PASSWORD_LENGTH) {
return Result.error("密码长度不能少于8位");
}
return Result.success("参数校验通过");
}
/**
* 正则校验工具方法
*/
private boolean patternMatches(String regex, String input) {
if (!StringUtils.hasText(input)) {
return false;
}
Pattern pattern = Pattern.compile(regex);
return pattern.matcher(input).matches();
}
/**
* 密码验证(后续会加入加密逻辑)
*/
private boolean validatePassword(String inputPassword, String storedPassword) {
// 暂时使用明文比较,后续会加入加密逻辑
return inputPassword.equals(storedPassword);
}
/**
* 用户信息脱敏
*/
private User desensitizeUserInfo(User user) {
User safeUser = new User();
safeUser.setId(user.getId());
safeUser.setNickName(user.getNickName());
safeUser.setPhone(desensitizePhone(user.getPhone()));
safeUser.setRole(user.getRole());
// 敏感信息不返回:密码、状态等
return safeUser;
}
/**
* 手机号脱敏:138****1234
*/
private String desensitizePhone(String phone) {
if (StringUtils.hasText(phone) && phone.length() == 11) {
return phone.substring(0, 3) + "****" + phone.substring(7);
}
return phone;
}
}
3. Mapper层增强 - 添加查询方法
位置 :src/main/java/com/jackson/usermanager/mapper/UserMapper.java
java
package com.jackson.usermanager.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jackson.usermanager.model.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper extends BaseMapper<User> {
/**
* 根据手机号查询用户信息
*/
@Select("SELECT * FROM user WHERE phone = #{phone} AND status = 0")
User selectByPhone(@Param("phone") String phone);
}
4. 统一返回结果类
位置 :src/main/java/com/jackson/usermanager/common/Result.java
java
package com.jackson.usermanager.common;
import lombok.Data;
import java.io.Serializable;
@Data
public class Result<T> implements Serializable {
private Integer code; // 状态码:200成功,500失败
private String message; // 返回信息
private T data; // 返回数据
// 成功静态方法
public static <T> Result<T> success(String message, T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage(message);
result.setData(data);
return result;
}
public static <T> Result<T> success(T data) {
return success("操作成功", data);
}
public static <T> Result<T> success(String message) {
return success(message, null);
}
// 错误静态方法
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMessage(message);
return result;
}
}
5. 常量类定义
位置 :src/main/java/com/jackson/usermanager/constant/LoginConstants.java
java
package com.jackson.usermanager.constant;
public class LoginConstants {
public static final int MIN_PASSWORD_LENGTH = 8;
public static final int SESSION_TIMEOUT = 30 * 60;
public static final String PHONE_REGEX = "^1[3-9]\\d{9}$";
}
6. 全局异常处理器
位置 :src/main/java/com/jackson/usermanager/handler/GlobalExceptionHandler.java
java
package com.jackson.usermanager.handler;
import com.jackson.usermanager.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result<String> handleException(Exception e) {
log.error("系统异常:", e);
return Result.error("系统异常:" + e.getMessage());
}
}
⚙️ 配置文件更新
application.yml 完整配置
yaml
server:
port: 8080
servlet:
context-path: /api
session:
timeout: 1800 # Session超时时间30分钟(单位:秒)
spring:
application:
name: user-manager
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/user-manager?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
# MyBatis配置
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 日志配置
logging:
level:
com.jackson.usermanager: debug
🧪 功能测试指南
1. 使用Postman测试接口
请求示例:
POST http://localhost:8080/api/user/login
Content-Type: application/x-www-form-urlencoded
phone=13800138000&pwd=12345678
预期响应:
json
{
"code": 200,
"message": "登录成功",
"data": {
"id": 1,
"nickName": "测试用户",
"phone": "138****8000",
"role": 0
}
}
2. 测试用例覆盖场景
| 测试场景 | 输入数据 | 预期结果 |
|---|---|---|
| 正常登录 | 正确手机号+密码 | 登录成功,返回脱敏信息 |
| 手机号为空 | 空手机号+密码 | "手机号和密码不能为空" |
| 密码过短 | 正确手机号+"123" | "密码长度不能少于8位" |
| 用户不存在 | 未注册手机号+密码 | "用户不存在" |
| 密码错误 | 正确手机号+错误密码 | "密码错误" |
🔒 安全注意事项
1. 数据脱敏的重要性
- 保护隐私:防止用户敏感信息泄露
- 安全规范:符合数据安全法律法规要求
- 最佳实践:只返回前端必需的最小信息集
2. Session安全配置
java
// 设置Session超时时间
session.setMaxInactiveInterval(30 * 60);
// 重要操作前验证登录状态
User loginUser = (User) session.getAttribute("loginUser");
if (loginUser == null) {
return Result.error("未登录或登录已过期");
}
🌟 优化亮点总结
1. 代码结构优化
- ✅ 常量提取:将魔法数字提取为常量类
- ✅ 统一返回:标准化接口响应格式
- ✅ 异常处理:全局异常处理器统一处理
2. 业务逻辑优化
- ✅ 分层校验:Controller/Service各司其职
- ✅ 查询优化:先查用户再验证密码,明确错误类型
- ✅ 数据脱敏:保护用户隐私信息
3. 安全性提升
- ✅ Session管理:登录状态维护
- ✅ 参数校验:防止非法输入
- ✅ 错误信息:明确的错误提示
🎯 下一步学习预告
第五篇:用户注册功能实现
- 密码加密存储(BCrypt)
- 手机号唯一性校验
- 注册流程优化
- 异常情况处理
💫 本章总结
通过本章学习,我们完成了:
✅ 核心功能实现
- 完整的登录业务流程 - 从参数校验到结果返回
- 分层架构实践 - Controller/Service/Mapper各司其职
- 数据安全处理 - 参数校验+数据脱敏
- Session会话管理 - 登录状态维护
💡 新手成长要点
- 理解业务流程比记住代码更重要
- 分层设计思想是JavaWeb开发的核心
- 安全意识要从第一个功能开始培养
🚀 立即实践建议
按照文章步骤亲手实现登录功能,并使用Postman进行完整测试!
📌 下篇预告:下一篇我们将实现用户注册功能,重点讲解密码加密和唯一性校验,让你的系统更加安全完善!
💬 互动话题:你在实现登录功能时遇到过哪些问题?或者对哪个技术点比较困惑?欢迎在评论区交流讨论!