SpringBoot电脑商城项目--用户注册功能
1. 用户注册功能
1.1 创建用户表
CREATE TABLE t_user (uid INT AUTO_INCREMENT COMMENT '用户id',username VARCHAR(20) NOT NULL UNIQUE COMMENT '用户名',`password` CHAR(32) NOT NULL COMMENT '密码',salt CHAR(36) COMMENT '盐值',phone VARCHAR(20) COMMENT '电话号码',email VARCHAR(30) COMMENT '电子邮箱',gender INT COMMENT '性别:0-女,1-男',avatar VARCHAR(50) COMMENT '头像',is_delete INT COMMENT '是否删除:0-未删除,1-已删除',created_user VARCHAR(20) COMMENT '日志-创建人',created_time DATETIME COMMENT '日志-创建时间',modified_user VARCHAR(20) COMMENT '日志-最后修改执行人',modified_time DATETIME COMMENT '日志-最后修改时间',PRIMARY KEY (uid)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
1.2 创建用户实体类
数据库中有四个字段是所有表都有的,就可以抽取成一个公共类。
package com.cy.store.entity;import lombok.Data;import java.util.Date;@Data
public class BaseEntity {private String createdUser;private Date createdTime;private String modifiedUser;private Date modifiedTime;}
User实体类
package com.cy.store.entity;import lombok.Data;@Data
public class User extends BaseEntity{private Integer uid;private String username;private String password;private String salt;private String phone;private String email;private Integer gender;private String avatar;private Integer isDelete;
}
1.3 持久层(MyBatis)
1.3.1 编写sql语句
1.3.2 设计mapper接口和抽象方法
package com.cy.store.mapper;import com.cy.store.entity.User;
import org.apache.ibatis.annotations.Mapper;/*** MyBatis注解,标记该接口为MyBatis的Mapper接口* 用于自动扫描和注册该接口对应的SQL映射文件或注解中的SQL语句*/
@Mapper
public interface UserMapper {/*** 插入用户数据* @param user* @return*/Integer insert(User user);/*** 根据用户名查询用户数据* @param username* @return*/User findByUsername(String username);
}
1.3.3 编写映射文件
mapper接口对应的映射文件,跟接口名称保持一致,xml映射文件的配置可以在官网直接复制粘贴(MyBatis中文网入门_MyBatis中文网MyBatis中文网)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace:用于指定当前的映射文件和哪个接口进行映射,需要指定接口的文件路径 -->
<mapper namespace="com.cy.store.mapper.UserMapper"><!--1. id: 唯一标识该resultMap,可在当前mapper文件中通过id引用此映射关系2. type: 指定与数据库表映射的实体类类型,这里对应的是User实体类--><resultMap id="UserEntityMap" type="com.cy.store.entity.User"><!-- 主键映射,property对应实体类属性名,column对应数据库列名 --><id property="uid" column="uid"/><result column="is_delete" property="isDelete"></result><result column="create_User" property="createdUser"></result><result column="create_Time" property="createdTime"></result><result column="modified_User" property="modifiedUser"></result><result column="modified_Time" property="modifiedTime"></result></resultMap><!-- id:对应的mapper层接口中的 方法名称useGeneratedKeys:是否开启自增keyProperty 开启自增的字段 --><insert id="insert" useGeneratedKeys="true" keyProperty="uid">insert into t_user(username,password,salt,phone,email,gender,avatar,is_delete,created_user,created_time,modified_user,modified_time)values(#{username},#{password},#{salt},#{phone},#{email},#{gender},#{avatar},#{isDelete},#{createdUser},#{createdTime},#{modifiedUser},#{modifiedTime})</insert><!--查询用户名对应用户信息id: SQL语句的唯一标识,与接口方法名保持一致resultMap: 指定使用的结果映射配置,这里引用UserEntityMap进行数据库字段与实体类属性的自动映射--><select id="findByUsername" resultMap="UserEntityMap">select * from t_user where username = #{username}</select></mapper>
1.3.4 测试
生成mapper测试类
package com.cy.store.mapper;import com.cy.store.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testvoid insert() {User user = new User();user.setUsername("张三");user.setPassword("123456");int i = userMapper.insert(user);System.out.println(i);}@Testvoid findByUsername() {User user = userMapper.findByUsername("张三");System.out.println(user);}
}
重新启动项目,运行后发现报这个错误
是因为没有在yml配置文件中指定mybatis的mapper.xml的文件位置,导致找不到sql语句,在yml进行配置,再重新启动项目测试就OK了
1.4 业务层
1.4.1 规划异常
RuntimeException异常,作为异常的父类,然后再去定义具体的异常类型来继承RuntimeException异常。业务层异常的基类,ServiceException异常。这个异常继承RuntimeException异常。异常机制的建立。
/*** 业务层的异常基类,用于封装和传递业务逻辑中的异常信息。* 继承自 RuntimeException,支持所有异常构造方法。*/
public class ServiceException extends RuntimeException {/*** 无参构造方法,用于创建不带错误信息的异常对象。*/public ServiceException() {super();}/*** 构造方法,接受一个描述错误的字符串参数。** @param message 错误信息*/public ServiceException(String message) {super(message);}/*** 构造方法,接受错误信息和导致异常的可抛出对象。** @param message 错误信息* @param cause 异常原因*/public ServiceException(String message, Throwable cause) {super(message, cause);}/*** 构造方法,接受导致异常的可抛出对象。** @param cause 异常原因*/public ServiceException(Throwable cause) {super(cause);}/*** 受保护的构造方法,允许更详细的异常配置。** @param message 错误信息* @param cause 异常原因* @param enableSuppression 是否启用抑制* @param writableStackTrace 是否生成堆栈跟踪信息*/protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}
}
- 用户名被占用异常
用户在进行注册时可能会产生用户名被占用的错误,抛出一个异常:UsernameDuplicatedException异常。
package com.cy.store.service.ex;/*** 用户名被占用的异常*/
public class UsernameDuplicatedException extends ServiceException{public UsernameDuplicatedException() {super();}public UsernameDuplicatedException(String message) {super(message);}public UsernameDuplicatedException(String message, Throwable cause) {super(message, cause);}public UsernameDuplicatedException(Throwable cause) {super(cause);}protected UsernameDuplicatedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}
}
- 数据新增时报异常
正在执行数据插入操作时,服务器、数据库宕机,处于正在执行插入的过程中所产生的异常InsertException异常
package com.cy.store.service.ex;/*** 插入数据异常* @author zhou* @version 1.0*/
public class InsertException extends ServiceException{public InsertException() {super();}public InsertException(String message) {super(message);}public InsertException(String message, Throwable cause) {super(message, cause);}public InsertException(Throwable cause) {super(cause);}protected InsertException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}
}
1.4.2 设计接口和抽象方法
UserService层
package com.cy.store.service;import com.cy.store.entity.User;public interface UserService {/*** 用户注册* @param user 用户数据*/void reg(User user);}
UserServiceImpl 实现类,其中对密码进行加密操作
package com.cy.store.service.impl;import com.cy.store.entity.User;
import com.cy.store.mapper.UserMapper;
import com.cy.store.service.UserService;
import com.cy.store.service.ex.InsertException;
import com.cy.store.service.ex.UsernameDuplicatedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;import java.util.Date;
import java.util.Locale;
import java.util.UUID;@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;/*** 注册用户* @param user 用户数据*/@Overridepublic void reg(User user) {
// 通过username判断用户是否存在User result = userMapper.findByUsername(user.getUsername());
// 若用户存在,则抛出用户名被占用的异常if (result!=null){throw new UsernameDuplicatedException("用户名被占用");}
// 密码进行加密处理:随机生成一个盐值(随机数),将密码进行md5加密,再进行加盐加密
// (串 + password +串) --- md5算法进行加密,连续加载三次
// 盐值 + password +盐值 --- 盐值就是一个随机的字符串String password = user.getPassword();
// 获取盐值String salt = UUID.randomUUID().toString().toUpperCase();
// 补全数据库表中盐值的记录user.setSalt(salt);
// 将密码和盐值作为一个整体进行加密处理,忽略原有密码强度提升了数据安全性String md5Password = getMD5Password(password, salt);
// 将加密之后的密码重新补全到user对象中user.setPassword(md5Password);// 补全数据user.setIsDelete(0);user.setCreatedUser(user.getUsername());user.setModifiedUser(user.getUsername());Date date = new Date();user.setCreatedTime(date);user.setModifiedTime(date);
// 执行注册业务逻辑Integer insert = userMapper.insert(user);
// 新增失败,则抛出运行时异常if (insert!=1){throw new InsertException("注册时产生未知的异常");}}/*** 定义一个md5加密处理 方法*//*** 使用MD5算法对密码进行加密处理(包含盐值混淆增强安全性)* * @param password 原始密码* @param salt 随机生成的盐值(UUID)* @return 加密后的密码字符串** 方法说明:* 1. DigestUtils.md5DigestAsHex(...) 是 Spring 提供的工具方法,用于将字节数组转换为 MD5 哈希值。* - 它接受一个字节数组作为输入,并返回长度为32位的十六进制字符串表示的哈希值。* 2. (salt + password + salt) 是为了在加密过程中加入盐值,防止彩虹表攻击。* - 盐值加在密码前后,增加密码复杂度。* 3. 循环执行三次加密操作是为了进一步提高密码的安全性,增加破解难度。* - 每次迭代都会用新的中间结果重新计算。* 4. toUpperCase() 将最终的 MD5 结果统一为大写格式存储,确保一致性。*/private String getMD5Password(String password, String salt) {for (int i = 0; i < 3; i++) {// 进行三次 MD5 加密,每次使用盐值包裹原始密码password = DigestUtils.md5DigestAsHex((salt + password + salt).getBytes()).toUpperCase();}return password;}}
创建UserService测试类进行测试
import com.cy.store.entity.User;
import com.cy.store.service.ex.ServiceException;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class UserServiceTest {@Autowiredprivate UserService userService;@Testvoid reg() {try {// 创建用户对象并设置测试数据User user = new User();user.setUsername("lingorm"); // 设置用户名为"李四"user.setPassword("123456"); // 设置密码为"123456"// 调用UserService的注册方法userService.reg(user);// 如果注册成功,输出成功提示System.out.println("新增成功");} catch (ServiceException e) {// 捕获业务异常,输出错误信息和异常类型名称System.out.println(e.getMessage()); // 打印异常消息System.out.println(e.getClass().getSimpleName()); // 打印异常类名}}
}
新增且加密成功✌
在测试时可能会出现这个错误
可能有两个原因
- 测试类没有加@SpringBootTest注解
- 业务impl实现层没有加@Service注解
1.4.3 实现类
1.5 控制层
1.5.1 创建响应
状态码、状态描述信息、数据 ,这部分功能封装一个类中,将这类作为方法返回值,返回给前端浏览器(统一结果集封装)
package com.cy.store.util;import lombok.Data;import java.io.Serializable;/*** 响应结果对象* @param <E>*/
@Data
public class JsonResult<E> implements Serializable {
// 状态码private Integer state;
// 描述信息private String message;
// 数据private E data;public JsonResult() {}public JsonResult(Integer state) {this.state = state;}public JsonResult(Throwable e) {this.message = e.getMessage();}public JsonResult(Integer state, E data) {this.state = state;this.data = data;}
}
1.5.2 设计请求
依据当前的业务功能模块进行请求设计
1.5.3 处理请求
创建UserController类,依赖于业务层的接口
import com.cy.store.entity.User;
import com.cy.store.service.UserService;
import com.cy.store.service.ex.InsertException;
import com.cy.store.service.ex.UsernameDuplicatedException;
import com.cy.store.util.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/users")
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/reg")public JsonResult<Void> reg( User user) {
// 创建JsonResult对象,将结果返回给前端JsonResult<Void> jsonResult = new JsonResult<>();try {userService.reg(user);jsonResult.setState(200);jsonResult.setMessage("注册成功");} catch (UsernameDuplicatedException e) {jsonResult.setState(400);jsonResult.setMessage("用户名被占用");}catch (InsertException e){jsonResult.setState(500);jsonResult.setMessage("注册时产生未知的异常");}return jsonResult;}}
启动项目,通过网址传参测试
1.5.4 控制层优化设计
在控制层抽离一个父类,在这个父类中统一的去处理关于异常的相关操作,编写一个BaseController类,统一处理异常
@ExceptionHandler异常捕获器,捕获异常进行处理并返回
package com.cy.store.controller;import com.cy.store.service.ex.InsertException;
import com.cy.store.service.ex.ServiceException;
import com.cy.store.service.ex.UsernameDuplicatedException;
import com.cy.store.util.JsonResult;
import org.springframework.web.bind.annotation.ExceptionHandler;/*** 控制层基类*/
public class BaseController {//操作成功的状态码public static final int OK = 200;/*** 全局异常处理器,用于捕获并处理业务层抛出的异常。** @param e 捕获的异常对象* @return 返回封装后的 JsonResult 对象,包含状态码和错误信息*/
// 该项目中产生了异常,会被统一拦截到此方法中,这个方法此时就充当了请求处理方法,方法的返回值直接给前端浏览器,返回给前端的数据中,状态码,状态码对应信息,数据@ExceptionHandler(ServiceException.class)public JsonResult<Void> handleException(Throwable e) {// 创建一个 JsonResult 实例,并初始化异常消息JsonResult<Void> result = new JsonResult<>(e);// 判断异常类型,设置对应的状态码和提示信息if (e instanceof UsernameDuplicatedException){// 用户名被占用时返回 400 状态码和相应提示result.setState(400);result.setMessage("用户名被占用");} else if (e instanceof InsertException){// 插入数据失败时返回 500 状态码和相应提示result.setState(500);result.setMessage("注册时产生未知的异常");}// 返回最终的响应结果return result;}}
优化后的UserController层:继承BaseController类
import com.cy.store.entity.User;
import com.cy.store.service.UserService;
import com.cy.store.service.ex.InsertException;
import com.cy.store.service.ex.UsernameDuplicatedException;
import com.cy.store.util.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/users")
public class UserController extends BaseController{@Autowiredprivate UserService userService;@RequestMapping("/reg")public JsonResult<Void> reg( User user) {userService.reg(user);return new JsonResult<>(OK);}}
1.6 前端页面
1. 在register.html中编写发送请求的方法,点击事件来完成,选中对应的按钮($("选择器")),再去添加点击事件,$.ajax()函数发送异步请求
2. JQUery封装了一个函数,称之为$.ajax()函数,通过对象调用ajax()函数,可以异步加载相关请求,依靠的是JavaScript提供的一个对象XHR(XmlHttpResponse),封装了这个对象。
3.ajax()使用方式。
- url:请求路径
- type:请求类型(GET/POST)
- data:向指定请求url提交的数据
- dataType:提交数据类型,例:dataType:"json"
- success:成功响应的方法
- error:失败响应的方法
$.ajax({url:"",type:"",data:"",dataType:"",success: function(){},error: function(){}
});
<script type="text/javascript">
<!-- 监听注册按钮是否被点击,如果被点击则执行一个方法 -->$("#btn-reg").click(function() {// 发送AJAX POST请求进行注册$.ajax({url: "/users/reg", // 请求的后端注册接口地址type: "POST", // 请求类型为POSTdata: $("#form-reg").serialize(), // 序列化表单数据以便提交dataType: "json", // 预期服务器返回的数据类型为JSONsuccess: function(json) {if (json.state == 200) {alert("注册成功"); // 注册成功提示} else {alert("注册失败"); // 注册失败提示}},error: function(xhr) {alert("注册时产生未知的错误!" + xhr.status); // 网络或其他错误处理}});});</script>
重启项目,进行注册,数据库新增成功✌
js代码无法正常被服务器解析执行,体现在点击页面中的按钮没有任何响应,解决方案如下:
- 在项目的maven下清理重新编译
- 在项目的file选项下-cash清理缓存
- 重新去构建项目:build选项下-rebuild选项
- 重启idea
- 重启电脑