springboot电脑商城项目 2.用户注册功能

2.用户注册

1.创建数据表

  1. 选中数据库

    user store
    
  2. 创建t_user表

    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;
    

2.创建用户实体类

  1. 通过表的结构提取出表的公共字段,放在一个实体类的基类中。

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class BaseEntity {
        private String createdUser;
        private Date createdTime;
        private String modifiedUser;
        private Date modifiedTime;
    }
    
  2. 创建用户实体类,继承BaseEntity

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User extends BaseEntity implements Serializable {
        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;
    }
    

3.注册-持久层

通过Mybatis来操作数据库。作mybatis开发的流程

3.1规划所需要执行的sql语句

  1. 用户的注册功能,相当于数据的插入操作

    insert into t_user (username,password) values(值列表)
    
  2. 用户注册时首先要查询当前用户名是否存在,如果存在则不能进行注册。相当于是一条查询语句

    select * from t_user where username = ?
    

3.2设计接口和抽象方法

  1. 定义Mapper接口。在项目的目录结构下创建一个mapper包,在这个包下再根据不同的功能模块来创建mapper接口。创建一个UserMapper接口。要在接口中定义这两个操作的接口
@Mapper
@Repository
public interface UserMapper {

    Integer insert(User user);

    User findByUsername(String username);

    User findAllUser();
}

2.在启动类配置mapper接口文件的位置,这里有两种方法

  1. 直接在接口上添加注解

    @Mapper
    @Repository
    public interface UserMapper {
    
        /**
         * 插入用户数据
         * @param user
         * @return:受影响的行数
         */
        Integer insert(User user);
    
        /**
         * 根据用户名来查询
         * @param username
         * @return  如果找到返回对象,没找到返回null
         */
        User findByUsername(String username);
    
        User findAllUser();
    }
    
  2. 在启动类配置mapper接口文件的位置

    @SpringBootApplication
    @MapperScan("com.jiabowen.store.mapper")
    public class StoreApplication {
        public static void main(String[] args) {
            SpringApplication.run(StoreApplication.class, args);
        }
    }
    

3.将mapper文件位置注册到properties对应的配置文件中

3.单元测试:每个独立的层编写完后,需要编写单元测试方法。在test包结构下创建一个mapper包,在这个包下创建持久层测试

3.3编写映射

1.定义xml映射文件,与对应接口进行关联。所有的映射文件需要放置在resources目录下,在这个目录下创建一个mapper文件夹,在这个文件夹下存放 Mapper的映射文件

2.创建接口所对应的映射文件,遵循接口名和文件名保持一致即可。创建一。个UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace属性:用于指定当前映射文件和哪个接口进行映射,需要指定接口的文件路径,需要标注包的完成路径接口 -->
<mapper namespace="com.jiabowen.store.mapper.UserMapper">
</mapper>

3.配置接口中的方法对应上sql语句。

<resultMap id="UserEntityMap" type="com.jiabowen.store.entity.User">
    <result column="uid" property="uid"></result>
    <result column="is_delete" property="isDelete"></result>
    <result column="created_user" property="createdUser"></result>
    <result column="created_time" property="createdTime"></result>
    <result column="modified_user" property="modifiedUser"></result>
    <result column="modified_time" property="modifiedTime"></result>
</resultMap>

<!--声明数据库字段-->
<sql id="User_field">
    username,password,salt,phone,email,gender,
    avatar,is_delete,created_user,created_time,modified_user,modified_time
</sql>

<!--实体类属性-->
<sql id="User_insert">
    #{username},#{password},#{salt},#{phone},#{email},#{gender},
    #{avatar},#{isDelete},#{createdUser},#{createdTime},#{modifiedUser},#{modifiedTime}
</sql>

<select id="findAllUser" resultType="java.util.List">
    SELECT * FROM t_user
</select>

<!--
        useGeneratedKeys属性:表示开启哪个字段的值自增(主键设置为递增)
        keyProperty属性:表示表中的哪个字段作为主键进行自增
-->
<insert id="insert" parameterType="com.jiabowen.store.entity.User" useGeneratedKeys="true" keyProperty="uid" >
    INSERT INTO t_user
    (<include refid="User_field"></include>)
    value
    (<include refid="User_insert"></include>)
</insert>

<!--select语句在执行的时候,查询的结果是一个对象,多个对象-->
<!--
        resultType:表示查询的结果集类型,只需要指定对应映射类的类型,并包含完整包接口:resultType = "com.jiabowen.entity.User"
        resyltMap:当表的资源和类的对象的属性字段名称不一致时,自定义查询结果集的映射规则
-->
<select id="findByUsername" resultMap="UserEntityMap">
    select
    <include refid="User_field"></include>
    from
    t_user
    where
    username = #{username}
</select

4.将mapper文件的位置注册到properties对应的配置文件中

mybatis.mapper-locations=classpath:mapper/*.xml

5.单元测试;每个独立的层编写完后,编写单元测试方法,来测试当前功能

//@SpringBootTest:表示标注当前类是一个测试类,不会随同项目一块打包发送、
@SpringBootTest
//@RunWith(SpringRunner.class):表示启动这个单元测试类,需要传递一个参数,必须是SpringRunner的实例类型
@RunWith(SpringRunner.class)
public class UserMapperTest {

    @Autowired
    UserMapper userMapper;

    /**
     * 单元测试方法:
     * 1.必须被@Test注解修饰
     * 2.返回值类型必须是void
     * 3.方法的参数列表不指定任何类型
     * 4.方法的访问修饰符必须是public
     */
    @Test
    public void insert(){
        User user = new User();
        user.setUsername("lalala");
        user.setPassword("123456");
        user.setCreatedTime(new Date());
        user.setIsDelete(1);
        Integer insert = userMapper.insert(user);
        System.out.println(insert);
    }

    @Test
    public void findByUsername(){
        User admin = userMapper.findByUsername("lalala");
        System.out.println(admin);
    }
}

6.解决自动注入创建Bean时爆红(不改也问题不大)

4.注册-业务层

4.1规划异常:将错误控制在范围之内

1.RunntimeException异常,作为这类异常的子类,然后再去定义其具体的异常类型来继承这个异常。业务层异常的基类,ServiceException,这个异常继承RunntimeException异常。异常机制的建立。

public class ServiceException extends RuntimeException {
    public ServiceException() {
        super();
    }

    public ServiceException(String message) {
        super(message);
    }

    public ServiceException(String message, Throwable cause) {
        super(message, cause);
    }

    public ServiceException(Throwable cause) {
        super(cause);
    }

    protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

根据业务层不同的业务功能来详细的定义具体的异常的类型,统一去继承ServicException异常类

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);
    }
}

2.用户在注册时,可能会产生用户名被占用的错误,抛出一个异常:

UsernameDuplicatedException(用户名被占用)异常

// 通过user参数来获取传递过来的username
String username = user.getUsername();
//调用findByUsername(username)判断用户是否被注册过
User result = userMapper.findByUsername(username);
//判断结果集是否为空,如果不为空则抛出异常
if (result != null){
    throw new UsernameDuplicatedException("用户名被占用");
}

3.正在执行数据插入操作的时候,服务器,数据库宕机。处于正在执行插入过程中所产生的异常InsertException

//执行注册业务功能的实现(row == 1)
Integer rows = userMapper.insert(user);
if (rows != 1){
    throw new UsernameDuplicatedException("用户在注册过程中产生了未知异常");
}

4.2设计接口和抽象方法

1.在service包下创建一个接口IUserService接口(I开头)

/**
 * 用户模块业务层接口
 */
public interface IUserService {
    /**
     * 用户注册方法
     * @param user:用户的数据对象
     */
    void reg(User user);
}

2.创建一个实现类UserServiceImpl,需要实现这个接口,实现抽象方法

@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public void reg(User user) {
        // 通过user参数来获取传递过来的username
        String username = user.getUsername();
        //调用findByUsername(username)判断用户是否被注册过
        User result = userMapper.findByUsername(username);
        //判断结果集是否为空,如果不为空则抛出异常
        if (result != null){
            throw new UsernameDuplicatedException("用户名被占用");
        }

        /**
         * 密码加密处理:
         */
        String oldPassword = user.getPassword();
        //获取盐值(随即生成一个盐值)
        String salt = UUID.randomUUID().toString().toUpperCase();
        //将密码和盐值整体加密处理
        String password = getMD5Password(user.getPassword(), salt);
        //将加密后的密码补全到user对象中
        user.setPassword(password);


        //补全数据:isDelete设置为0
        user.setIsDelete(0);
        //补全数据:四个日志信息
        user.setCreatedUser(username);
        user.setModifiedUser(username);
        Date date = new Date();
        user.setCreatedTime(date);
        user.setModifiedTime(date);

        //执行注册业务功能的实现(row == 1)
        Integer rows = userMapper.insert(user);
        if (rows != 1){
            throw new UsernameDuplicatedException("用户在注册过程中产生了未知异常");
        }
    }

    private String getMD5Password(String password,String salt){
        for (int i = 0; i < 3; i++) {
            password = DigestUtils.md5DigestAsHex((salt + password + salt).getBytes()).toUpperCase();
        }
        return password;
    }
}

3.在单元测试包下创建一个UserServiceTests类,在这个类中添加单元测试的功能

@Test
public void reg(){
    try {
        User user = new User();
        user.setUsername("lili");
        user.setPassword("123456");
        userService.reg(user);
        System.out.println("OK");
    } catch (ServiceException e) {
        //获取类的对象,类的名称
        System.out.println(e.getClass().getSimpleName());
        //获取异常的具体描述信息
        System.out.println(e.getMessage());
    }
}

5.注册-控制层

5.1创建响应

状态码、状态描述信息、数据。将这部分功能封装在一个类中,将这类作为方法返回值,返回给前端浏览器

/**
 * Json格式的数据进行响应
 */
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;
    }
}

5.2设计请求

依据当前的业务功能模块进行请求的设计

请求路径:/user/reg
请求参数:User user
请求类型:POST
响应结果:JsonResult<void>

5.3处理请求

创建控制层对应的类UserConttoller类。依赖于业务层的接口。

5.4控制层优化设计

在控制层抽离一个父类,在这个父类中统一去处理关于异常的相关操作。编写一个BaseController类,统一处理异常

/**
 * 控制层基类
 */
public class BaseController {

    /**
     * 操作成功的状态码
     */
    public static final int OK = 200;

    //请求处理方法,这个方法的返回值就是需要传递给前端的数据
    //自动将异常对象传递到此方法的参数列表上
    //当前项目中产生了异常,被统一拦截在此方法中,这个方法此时就是充当请求处理方法,方法的返回值直接返回给前端
    public JsonResult<Void> handleException(Throwable e){
        JsonResult<Void> result = new JsonResult<>(e);
        if (e instanceof UsernameDuplicatedException){
            result.setState(4000);
            result.setMessage("用户名已被占用");
        }else if (e instanceof InsertException){
            result.setState(5000);
            result.setMessage("注册时产生未知异常");
        }
        return result;
    }
}

重写reg(User user)

@RestController//@Controller + @ResponseBody
@RequestMapping("users")
public class UserController extends BaseController {

    @Autowired
    private IUserService iUserService;

    /**
     * 重新构建reg()
     */
    @RequestMapping("reg")
    public JsonResult<Void> reg(User user){
        iUserService.reg(user);
        return new JsonResult<>(OK);
    }
}

6.注册-前端页面

1.在register页面中编写发送请求的方法,点击事件来完成。选中对应的按钮($("选择器")),再去添加点击的事件,$.ajax()函数发送异步请求。

2.JQuery封装了一个函数,称之为$.ajax函数,通过对象($)调用ajax()函数,可以异步加载相关请求。依靠的是Javascript提供的对象XHL(XmlHttpResponse),封装了这个对象。

3.ajax()使用方式。需要传递一个方法体,作为方法的参数来使用,一对大括号称之为方法体。ajax接收多个参数,参数与参数之间要求使用“,”分割,每一组参数之间使用“:”分割,参数组成部分是一个参数的名称(不能随意的定义)。参数的声明顺序没有要求。 语法结构

$.ajax({
	url:"",
    type:"",
    data:"",
    dataType:"",
    success:function(){
        
    },
    error:function(){
        
    }
});

4.ajax()函数参数的含义

参数 功能描述
url 标识请求地址,不能包含参数列表部分内容。例如:url:"'localhost:8080/users/reg'
type 请求类型(get和post请求的类型)。例如:type:"POST"
data 向指定的请求url地址提交的数据。例如:data:"username=tom&pwd=123"
dataType 提交的数据类型。数据类型一般会指定为json类型。dataType:"json"
success 当服务器正常响应客户端时,会自动调用success参数的方法,并将服务器返回的数据以参数的形式传递给这个方法的参数上
error 当服务器未正常响应客户端时,会自动调用error参数的方法,并将服务器返回的数据以参数的形式传递给这个方法的参数上
<script>
    //1.监听注册按钮是否被点击,如果被点击可以执行一个方法
    $("#btn-reg").click(function () {
    //2.发送ajax
    $.ajax({
        url: "/users/reg",
        type: "POST",
        data: $("#form-reg").serialize(),
        dataType: "JSON",
        success: function (json) {
            if (json.state == 200){
                alert("注册成功")
                console.log(json)
            }else{
                alert("注册失败")
            }
        },
        error: function (xhr) {
            alert("注册时产生未知错误" + xhr.status)
        }
    });
});

</script>

5.js代码可以独立声明在一个.js文件中,或声明在一个script标签中

6.js代码无法正常被服务器解析执行

  • 在项目maven下clear清理项目-install重新部署
  • 在项目的file选项下-cash清理缓存
  • 重启idea
  • 重启电脑
posted @ 2022-02-02 00:26  贾博文  阅读(246)  评论(0)    收藏  举报