第25周JavaSpringboot实战-电商项目 3.用户模块

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. 参数校验
  2. 数据库查询防止重名
  3. 存入用户信息

完善流程,符合分层设计原则。

课程笔记:统一异常处理与用户注册

1. 创建自定义异常类

exception 包中新建 IMookException 类,实现统一异常处理:

  • 继承 Exception
  • 定义属性
    • code(状态码)
    • message(异常信息)
  • 构造函数
    • 接收 codemessage 进行初始化
    • 另一个构造函数接收 IMookExceptionEnum 枚举,调用前一个构造函数
  • 提供 Getter 方法

2. 添加异常枚举

IMookExceptionEnum 中添加新异常:

  • NAME_EXISTED(10004, "不允许重名,注册失败")
  • INSERT_FAILED(10005, "插入失败,请重试")

3. 在 Service 层抛出异常

UserServiceregister 方法中:

  1. 检查用户名是否重复:
    if (userExists(username)) {
        throw new IMookException(IMookExceptionEnum.NAME_EXISTED);
    }
    
  2. 创建 User 对象并设置 usernamepassword
  3. 使用 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 测试:

  • 现在的异常返回仅包含 codemessagedata 三个字段。
  • 确保状态码符合设定规则:
    • 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 加密的实现
  1. 创建工具类

    • 创建一个名为 MD5Util 的工具类,用于实现 MD5 加密。
    • 使用 MessageDigest 类生成 MD5 哈希值,并通过 Base64 编码将字节数组转换为字符串。
  2. 代码示例

    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);
        }
    }
    
  3. 测试加密方法

    • 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在线破解),可能会被快速破解。
五、加盐策略

为了进一步提高密码的安全性,可以使用加盐策略:

  • 加盐的定义:在原始密码的基础上,添加一个随机的字符串(盐值),然后进行加密。
  • 实现步骤
    1. 创建常量类:创建一个名为 Constant 的类,用于存储盐值。
      public class Constant {
          public static final String SALT = "aBcDeFgH123!@#";
      }
      
    2. 修改加密方法:在加密时将盐值添加到原始密码中。
      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);
      }
      
六、加盐后的效果
  1. 测试加盐后的加密结果

    • 修改 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();
          }
      }
      
  2. 破解测试

    • 将加盐后的加密结果粘贴到 MD5 破解网站,发现无法破解。
七、密码存储的完善
  1. 修改注册功能

    • 在用户注册时,使用加盐后的 MD5 加密存储密码。
    • 示例代码:
      user.setPassword(MD5Util.getMD5String(user.getPassword()));
      
  2. 验证效果

    • 使用 Postman 发送注册请求,验证数据库中存储的密码是否为加盐后的加密结果。
    • 示例请求:
      {
          "username": "mumuxi",
          "password": "12345678"
      }
      
  3. 数据库验证

    • 查询数据库,确认存储的密码为加盐后的加密结果,无法被轻易破解。

登录功能实现与安全性强化

一、登录功能的需求与原理

登录功能是用户接入系统的关键,需要保存用户的身份状态,以便后续访问时识别用户。这可以通过 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 中的用户信息。
  • 管理员登录接口测试 :使用管理员账号发送登录请求,验证是否能够成功登录并获取管理员权限。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值