Generating the markdown content for the course notes
markdown_content = “”"
用户模块课程笔记
知识点和功能点
用户模块包含以下知识点和功能点:
- 用户注册:注册时需进行重名校验,确保用户名唯一。
- 密码加密:为了安全性,密码在存储时进行加密。
- 用户登录:使用 session 技术存储用户登录信息。
- 权限校验:登录时进行越权校验,确保用户只能修改自己的信息。
- 统一响应对象:项目中贯穿的统一响应格式,简化 API 的返回处理。
- 异常处理:采用枚举化处理异常,介绍 Java 异常体系,并进行统一异常处理。
- 更新个人信息:包括更新个性签名等功能。
- Postman 测试:使用 Postman 工具对接口进行测试,确保接口功能正确。
接口设计
以下是用户模块的接口设计文档,包括各个接口的 URL、请求参数及返回格式。
1. 注册新用户
- 接口地址:
POST /register
- 请求参数:
username
: 用户名(如xiaomu
)password
: 密码(如12345678
)
- 返回字段:
status
: 状态码message
: 信息提示data
: 返回数据,注册时为空
2. 用户登录
- 接口地址:
POST /login
- 请求参数:
username
: 用户名password
: 密码
- 返回字段:
status
: 状态码message
: 信息提示data
: 用户信息,包含 ID、用户名、个性签名、身份、时间等字段
3. 更新个性签名
- 接口地址:
POST /userUpdate
- 请求参数:
signature
: 新的个性签名
- 返回字段:
status
: 状态码message
: 信息提示
4. 退出登录
- 接口地址:
POST /userLogout
- 请求参数: 无
- 返回字段:
status
: 状态码message
: 信息提示
5. 管理员登录
- 接口地址:
POST /adminLogin
- 请求参数:
username
: 管理员用户名password
: 管理员密码
- 返回字段:
status
: 状态码message
: 信息提示data
: 管理员信息,包含管理员的身份验证
接口开发和测试
- 开发阶段:完成接口设计后,进入编码阶段实现各个接口。
- Postman 测试:使用 Postman 工具对接口进行实操测试,确保接口按预期工作。
三.统一API返回对象的实现
1. 统一返回对象结构
在编写用户注册接口时,需要设计一个通用的返回对象,包含以下三个部分:
- status:状态码(整数),用于标识不同的请求状态。
- message:状态信息(字符串),告知前端请求成功或失败的信息。
- data:返回的数据(泛型),可存放不同类型的信息,如用户对象、购物车对象等。
2. 创建通用返回对象 APIResponse
2.1 创建 common
包
用于存放通用类,例如 APIResponse
。
2.2 定义 APIResponse
类
public class APIResponse<T> {
private Integer status; // 状态码
private String message; // 消息
private T data; // 泛型数据
private static final int OK_CODE = 10000;
private static final String OK_MESSAGE = "Success";
// 构造方法
public APIResponse(Integer status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
public APIResponse(Integer status, String message) {
this(status, message, null);
}
public APIResponse() {
this(OK_CODE, OK_MESSAGE);
}
// 成功响应方法
public static <T> APIResponse<T> success() {
return new APIResponse<>();
}
public static <T> APIResponse<T> success(T result) {
APIResponse<T> response = new APIResponse<>();
response.setData(result);
return response;
}
// 失败响应方法
public static <T> APIResponse<T> error(int code, String message) {
return new APIResponse<>(code, message);
}
public static <T> APIResponse<T> error(IMallExceptionEnum ex) {
return new APIResponse<>(ex.getCode(), ex.getMessage());
}
// Getter & Setter
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public T getData() { return data; }
public void setData(T data) { this.data = data; }
// toString 方法
@Override
public String toString() {
return "APIResponse{" +
"status=" + status +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}
3. 创建异常枚举类 IMallExceptionEnum
3.1 创建 exception
包
用于存放与异常相关的类。
3.2 定义 IMallExceptionEnum
枚举
public enum IMallExceptionEnum {
NEED_USERNAME(10001, "用户名不能为空");
private final int code;
private final String message;
IMallExceptionEnum(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() { return code; }
public String getMessage() { return message; }
}
4. 统一API返回对象的使用示例
4.1 成功返回
return APIResponse.success();
或带数据返回:
return APIResponse.success(user);
4.2 失败返回
return APIResponse.error(IMallExceptionEnum.NEED_USERNAME);
用户注册流程总结
1. 定义注册方法
- 方法返回值:
APIResponse
- 参数:
@RequestParam String username
@RequestParam String password
- 添加注解:
@PostMapping("/register")
@ResponseBody
2. 输入校验
- 使用
StringUtils.isEmpty(username)
校验用户名是否为空- 返回
APIResponse.error(ExceptionEnum.NEED_USERNAME)
- 返回
- 使用
StringUtils.isEmpty(password)
校验密码是否为空- 返回
APIResponse.error(ExceptionEnum.NEED_PASSWORD)
- 返回
- 校验密码长度
if (password.length() < 8)
- 返回
APIResponse.error(ExceptionEnum.PASSWORD_TOO_SHORT)
3. 数据库操作
- 在
UserService
定义void register(String username, String password)
- 在
UserMapper
定义User selectByName(String username)
- 在 XML 配置
selectByName
方法:<select id="selectByName" parameterType="String" resultMap="BaseResultMap"> SELECT <include refid="Base_Column_List"/> FROM im_ook_user WHERE username = #{username} </select>
- 查询用户名是否已存在:
User result = userMapper.selectByName(username); if (result != null) { throw new CustomException(ExceptionEnum.USER_ALREADY_EXISTS); }
4. 存储用户信息
- 若用户名不存在,则将新用户信息写入数据库。
这样,我们完成了注册流程,包括:
- 参数校验
- 数据库查询防止重名
- 存入用户信息
完善流程,符合分层设计原则。
课程笔记:统一异常处理与用户注册
1. 创建自定义异常类
在 exception
包中新建 IMookException
类,实现统一异常处理:
- 继承
Exception
- 定义属性
code
(状态码)message
(异常信息)
- 构造函数
- 接收
code
和message
进行初始化 - 另一个构造函数接收
IMookExceptionEnum
枚举,调用前一个构造函数
- 接收
- 提供 Getter 方法
2. 添加异常枚举
在 IMookExceptionEnum
中添加新异常:
NAME_EXISTED(10004, "不允许重名,注册失败")
INSERT_FAILED(10005, "插入失败,请重试")
3. 在 Service 层抛出异常
在 UserService
的 register
方法中:
- 检查用户名是否重复:
if (userExists(username)) { throw new IMookException(IMookExceptionEnum.NAME_EXISTED); }
- 创建
User
对象并设置username
和password
- 使用
userMapper.insertSelective(user)
进行插入,并判断返回值:if (count == 0) { throw new IMookException(IMookExceptionEnum.INSERT_FAILED); }
4. Controller 层处理异常
- 在
UserController
调用register
方法时,声明可能抛出的异常 - 调用
APIResponse.success()
处理成功情况 - 使用
Postman
进行POST
请求测试
5. 测试不同异常情况
使用 Postman
进行测试:
- ✅ 正常注册:返回
success
- ❌ 用户名为空:返回
"用户名不能为空"
- ❌ 密码为空:返回
"密码不能为空"
- ❌ 密码少于 8 位:返回
"密码长度不能少于 8 位"
- ❌ 用户名重复:返回
"不允许重名,注册失败"
6. 统一异常返回格式
问题:
- 目前不同异常返回格式不一致
- 可能暴露敏感信息(如
Internal Server Error
)
解决方案:
- 使用全局异常处理
- 隐藏异常细节,统一返回格式
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(IMookException.class) public APIResponse handleIMookException(IMookException e) { return APIResponse.error(e.getCode(), e.getMessage()); } }
- 这样,所有
IMookException
异常都会被捕获,并返回统一格式的 JSON 响应
课程笔记:全局异常处理
1. 为什么要进行统一异常处理?
- 结构统一:保证前端接收的错误响应格式一致,便于解析。
- 安全性:避免暴露系统内部异常信息,提升安全性。
- 提升维护性:集中管理异常处理逻辑,减少代码重复,提高可读性。
2. 目标
- 在
service
层抛出异常时,能够自动转换为 JSON 格式的通用 API 响应。 - 区分业务异常和系统异常,并通过状态码加以区分:
- 业务异常:状态码 10000-19999
- 系统异常:状态码 20000 及以上
- 统一日志记录,方便排查问题。
3. 代码实现
3.1 定义 GlobalExceptionHandler
- 在
exception
包下创建GlobalExceptionHandler
类。 - 该类负责统一拦截异常,并返回标准化 API 响应。
@RestControllerAdvice
public class GlobalExceptionHandler {
private final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// 处理系统异常(Exception)
@ExceptionHandler(Exception.class)
@ResponseBody
public APIResponse<Object> handleException(Exception e) {
log.error("Default Exception", e);
return APIResponse.error(ExceptionEnum.SYSTEM_ERROR);
}
// 处理业务异常(IMockMoreException)
@ExceptionHandler(IMockMoreException.class)
@ResponseBody
public APIResponse<Object> handleIMockMoreException(IMockMoreException e) {
log.error("Business Exception: {}", e.getMessage(), e);
return APIResponse.error(e.getCode(), e.getMessage());
}
}
3.2 定义异常枚举 ExceptionEnum
- 业务异常和系统异常进行区分,便于前端解析。
public enum ExceptionEnum {
SYSTEM_ERROR(20000, "系统异常"),
USERNAME_DUPLICATE(10001, "用户名已存在"),
USERNAME_EMPTY(10002, "用户名不能为空");
private final int code;
private final String message;
ExceptionEnum(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
3.3 统一 API 响应 APIResponse
- 设计 API 响应格式,确保所有请求返回一致的结构。
public class APIResponse<T> {
private int code;
private String message;
private T data;
public static <T> APIResponse<T> success(T data) {
return new APIResponse<>(200, "成功", data);
}
public static <T> APIResponse<T> error(ExceptionEnum exceptionEnum) {
return new APIResponse<>(exceptionEnum.getCode(), exceptionEnum.getMessage(), null);
}
public static <T> APIResponse<T> error(int code, String message) {
return new APIResponse<>(code, message, null);
}
private APIResponse(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
// Getters and Setters
}
4. 测试效果
4.1 测试前:
- 之前的异常返回结构包含 5 个字段。
4.2 运行 Postman 测试:
- 现在的异常返回仅包含
code
、message
、data
三个字段。 - 确保状态码符合设定规则:
10000-19999
为业务异常20000
及以上为系统异常
示例返回:
{
"code": 10001,
"message": "用户名已存在",
"data": null
}
Java 异常体系概述
Java 的异常体系有一个顶级类,即 Throwable。它位于异常体系的根部,表示可以通过抛出机制处理的错误。
异常体系的结构
-
Throwable 分为两个主要子类:
-
Error:表示 Java 系统内部错误或资源耗尽等严重问题,这些问题是程序通常无法处理的,例如:
-
OutOfMemoryError:内存不足错误。
-
StackOverflowError:栈溢出错误。
-
Exception:表示程序可以捕获和处理的异常,又分为:
-
RuntimeException:运行时异常,如:
-
NullPointerException:空指针异常。
-
ArithmeticException:数学运算异常(如除以零)。
-
其他异常:非运行时异常,如 IOException、SQLException 等,这些异常通常需要显式处理。
Error 和 Exception 的区别
-
Error
-
通常是无法恢复的致命错误,系统难以处理,一般不需要程序捕获和处理。
-
例子:
-
OutOfMemoryError:内存不足错误。
-
StackOverflowError:栈溢出错误。
-
Exception:表示程序可以捕获和处理的异常。
异常处理的意义
- 结构清晰:清晰地将正常逻辑与错误处理逻辑分离,使代码更易读、易维护。
- 程序健壮性:能够及时处理程序运行中的异常情况,避免程序崩溃,提高程序的可靠性。
- 错误定位:异常信息可以帮助开发者快速定位问题根源。
异常处理的常见方式
- try-catch 块
- 用于捕获和处理异常,格式如下:
try
// 可能抛出异常的代码
catch (ExceptionType e)
// 处理异常的代码
finally
// 可选的清理代码,无论是否发生异常都会执行
- 抛出异常(throws)
- 将异常抛给上层调用者处理,格式如下:
public void method() throws ExceptionType
// 可能抛出异常的代码
- 自定义异常
- 根据业务需求自定义异常类,继承 Exception 或 RuntimeException。
课程笔记:密码存储与安全性
一、密码存储的安全性问题
在注册功能中,直接将用户密码以明文形式存储到数据库中是非常危险的。如果数据库被攻破,攻击者将能够直接获取所有用户的明文密码,导致严重的安全问题和名誉损失。
二、MD5 加密的基本原理
为了提高密码存储的安全性,可以使用加密算法对密码进行处理。MD5 是一种常用的哈希算法,它将字符串转换为一个固定长度的哈希值,具有以下特点:
- 不可逆性:无法通过哈希值反推出原始字符串。
- 唯一性:不同的输入字符串通常会产生不同的哈希值。
三、MD5 加密的实现
-
创建工具类
- 创建一个名为
MD5Util
的工具类,用于实现 MD5 加密。 - 使用
MessageDigest
类生成 MD5 哈希值,并通过 Base64 编码将字节数组转换为字符串。
- 创建一个名为
-
代码示例
import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.apache.commons.codec.binary.Base64; public class MD5Util { public static String getMD5String(String strValue) throws NoSuchAlgorithmException { // 创建 MessageDigest 实例,指定算法为 MD5 MessageDigest md = MessageDigest.getInstance("MD5"); // 将字符串转换为字节数组 byte[] bytes = md.digest(strValue.getBytes()); // 使用 Base64 编码 return Base64.encodeBase64String(bytes); } }
-
测试加密方法
- 在
MD5Util
类中添加一个main
方法,测试加密功能。 - 示例代码:
public static void main(String[] args) { try { String originalString = "1234"; String encryptedString = getMD5String(originalString); System.out.println("加密前: " + originalString); System.out.println("加密后: " + encryptedString); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } }
- 在
四、MD5 的局限性
虽然 MD5 加密可以提高密码存储的安全性,但它仍然存在一定的局限性:
- 彩虹表攻击:攻击者可以通过预计算的哈希值表(彩虹表)快速反查原始字符串。
- 破解示例:将加密后的字符串粘贴到 MD5 破解网站(如 MD5在线破解),可能会被快速破解。
五、加盐策略
为了进一步提高密码的安全性,可以使用加盐策略:
- 加盐的定义:在原始密码的基础上,添加一个随机的字符串(盐值),然后进行加密。
- 实现步骤:
- 创建常量类:创建一个名为
Constant
的类,用于存储盐值。public class Constant { public static final String SALT = "aBcDeFgH123!@#"; }
- 修改加密方法:在加密时将盐值添加到原始密码中。
public static String getMD5String(String strValue) throws NoSuchAlgorithmException { // 添加盐值 strValue = strValue + Constant.SALT; // 创建 MessageDigest 实例,指定算法为 MD5 MessageDigest md = MessageDigest.getInstance("MD5"); // 将字符串转换为字节数组 byte[] bytes = md.digest(strValue.getBytes()); // 使用 Base64 编码 return Base64.encodeBase64String(bytes); }
- 创建常量类:创建一个名为
六、加盐后的效果
-
测试加盐后的加密结果
- 修改
main
方法,测试加盐后的加密效果。public static void main(String[] args) { try { String originalString = "1234"; String encryptedString = getMD5String(originalString); System.out.println("加密前: " + originalString); System.out.println("加密后: " + encryptedString); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } }
- 修改
-
破解测试
- 将加盐后的加密结果粘贴到 MD5 破解网站,发现无法破解。
七、密码存储的完善
-
修改注册功能
- 在用户注册时,使用加盐后的 MD5 加密存储密码。
- 示例代码:
user.setPassword(MD5Util.getMD5String(user.getPassword()));
-
验证效果
- 使用 Postman 发送注册请求,验证数据库中存储的密码是否为加盐后的加密结果。
- 示例请求:
{ "username": "mumuxi", "password": "12345678" }
-
数据库验证
- 查询数据库,确认存储的密码为加盐后的加密结果,无法被轻易破解。
登录功能实现与安全性强化
一、登录功能的需求与原理
登录功能是用户接入系统的关键,需要保存用户的身份状态,以便后续访问时识别用户。这可以通过 HttpSession
实现,该机制能够帮助服务器端保存用户信息,使得在用户登录后的一段时间内不需要再次登录。
二、关键代码实现
1. 登录接口代码
@PostMapping("/login")
public APIRestResponse login(
@RequestParam String username,
@RequestParam String password,
HttpSession session
) throws ImoocMallException {
// 校验用户名和密码是否为空
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
throw new ImoocMallException(ExceptionEnum.USERNAME_PASSWORD_CANNOT_BE_EMPTY);
}
User user = userService.login(username, password);
// 不返回密码
user.setPassword(null);
// 用户信息保存到session中
session.setAttribute(Constant.IMOOC_MALL_USER, user);
return APIRestResponse.success(user);
}
2. UserServiceImpl 中的登录方法
@Override
public User login(String username, String password) throws ImoocMallException {
// 将传入的明文密码加密为md5形式
String md5Password = null;
try {
md5Password = MD5Util.getMD5String(password);
} catch (NoSuchAlgorithmException e) {
throw new ImoocMallException(ExceptionEnum.RESPONSE_DATA_ERROR);
}
User user = userMapper.selectLogin(username, md5Password);
if (user == null) {
throw new ImoocMallException(ExceptionEnum.INVALID_USERNAME_PASSWORD);
}
return user;
}
课程笔记:用户模块接口开发
一、更新个人信息接口
1. 接口设计
- 接口路径 :
/user/update
- 请求方式 :POST
- 入参 :
HttpSession
(用于获取用户信息)、signature
(用户的新签名) - 出参 :
APIRestResponse
(更新成功或失败的响应)
2. 代码实现
@PostMapping("/user/update")
public APIRestResponse updateUserinfo(
HttpSession session,
@RequestParam String signature
) throws ImoocMallException {
// 从 session 中获取用户信息
Object obj = session.getAttribute(Constant.IMOOC_MALL_USER);
User currentUser = (User) obj;
// 判断用户是否已登录
if (currentUser == null) {
return APIRestResponse.error(ExceptionEnum.NEED_LOGIN);
}
// 创建新的 User 对象,设置需要更新的字段
User user = new User();
user.setId(currentUser.getId());
user.setPersonalizedSignature(signature);
// 调用 service 层更新用户信息
userService.updateUserInfo(user);
return APIRestResponse.success();
}
3. Service 层实现
@Override
public void updateUserInfo(User user) throws ImoocMallException {
int updateCount = userMapper.updateByPrimaryKeySelective(user);
if (updateCount != 1) {
throw new ImoocMallException(ExceptionEnum.UPDATE_FAILED);
}
}
二、退出登录接口
1. 接口设计
- 接口路径 :
/user/logout
- 请求方式 :POST
- 入参 :
HttpSession
(用于清除用户信息) - 出参 :
APIRestResponse
(登出成功或失败的响应)
2. 代码实现
@PostMapping("/user/logout")
public APIRestResponse logout(HttpSession session) {
// 从 session 中移除用户信息
session.removeAttribute(Constant.IMOOC_MALL_USER);
return APIRestResponse.success();
}
三、管理员登录接口
1. 接口设计
- 接口路径 :
/admin/login
- 请求方式 :POST
- 入参 :
username
(管理员用户名)、password
(管理员密码) - 出参 :
APIRestResponse
(登录成功或失败的响应)
2. 代码实现
@PostMapping("/admin/login")
public APIRestResponse adminLogin(
@RequestParam String username,
@RequestParam String password,
HttpSession session
) throws ImoocMallException {
// 校验用户名和密码是否为空
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
throw new ImoocMallException(ExceptionEnum.USERNAME_PASSWORD_CANNOT_BE_EMPTY);
}
User user = userService.login(username, password);
// 校验是否为管理员
if (!userService.isAdmin(user)) {
return APIRestResponse.error(ExceptionEnum.NEED_ADMIN);
}
// 不返回密码
user.setPassword(null);
// 用户信息保存到 session 中
session.setAttribute(Constant.IMOOC_MALL_USER, user);
return APIRestResponse.success(user);
}
3. Service 层实现
@Override
public boolean isAdmin(User user) {
return user.getRole() == 2;
}
四、测试与验证
- 登录接口测试 :使用 Postman 发送登录请求,验证是否能够成功登录并获取用户信息。
- 更新个人信息接口测试 :在登录成功后,发送更新个人信息请求,验证是否能够成功更新用户签名。
- 退出登录接口测试 :在登录成功后,发送退出登录请求,验证是否能够成功清除 session 中的用户信息。
- 管理员登录接口测试 :使用管理员账号发送登录请求,验证是否能够成功登录并获取管理员权限。