SpringBoot电脑商城--注册功能

注册功能

  • 用户注册
    • 1. 创建数据库
    • 2. 创建用户的实体类
    • 3. 注册-持久层
    • 4. 注册-业务层
    • 5. 注册-控制层
    • 6. 注册-前端页面

用户注册

1. 创建数据库

  • 选中数据库
  • 创建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. 创建用户的实体类

  • 通过表的结构提取出表的公共字段,放在一个实体类的基类中,起名BaseEntity基类中。
    public class BaseEntity implements Serializable {
        private String createdUser;
        private Date createdTime;
        private String modifiedUser;
        private Date modifiedTime;
    
  • 创建用户的实体类,需要继承BaseEntity基类
    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;
    	//重写getter、setter,equals、hashCode,toString
    

3. 注册-持久层

通过Mybatis来操作数据库,再做Mybatis开发流程。

  • 规划需要执行的Sql语句
    1.用户的注册功能,相当于在做数据的插入操作

    insert into t_user (username, password) values (...)
    

    2.用户在注册的时候,首先要查询当前的用户名是否存在

    select * from t_user where username = ?
    
  • 设计接口和抽象方法
    1.定义Mapper接口。在项目的目录结构下首先创建一个mapper包,在这个包下再根据不同的功能模块来创建mapper接口。创建一个UserMapper的接口,要在接口中定义这两个SQL语句的抽象方法。

    public interface UserMapper {
       //插入用户的数据
       Integer insert(User user);
    
       //根据用户名来查询用户的数据
       User findByUsername(String username);
    }
    

    2.在启动类配置mapper接口文件的位置(也可以在mapper接口上加@Mapper注解,但我们不只有一个mapper接口,添加这个注解会比较方便,不需要对每个Mapper都添加@Mapper注解)

    //指定mapper接口路径位置,项目启动时会自动加载所有接口
    @MapperScan("com.cy.store.mapper")
    
  • 编写映射
    1.定义xml映射文件,与对应的接口进行关联。所有的映射文件需要放在resources目录下,在这个目录下创建一个mapper文件夹,然后在这个文件夹存放mapper的映射文件。
    2.创建接口对应的映射文件,遵循和接口的名称保持一致即可。创建一个UserMapper.xml文件。

    
    DOCTYPE mapper
           PUBLIC "-//mybatis.org//DTD mapper 3.0//EN"
           "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.cy.store.mapper.UserMapper">
    
       
       
       <resultMap id="UserEntityMap" type="com.cy.store.entity.User">
           
           <result column="id_delete" property="idDelete">result>
           <result column="created_user" property="created_user">result>
           <result column="created_time" property="createdTime">result>
           <result column="modified_user" property="modifiedUser">result>
           <result column="modified_time" property="modifiedTime">result>
       resultMap>
    
       
       
       <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>
    
       
       <select id="findByUsername" resultMap="UserEntityMap">
           select * from t_user where username = #{usename}
       select>
    
    mapper>
    

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

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

    4.单元测试:每个独立的层编写完毕后需要编写单元测试方法,来测试当前的功能。在test包结构下创建一个mapper包,在这个包下再创建持久层的功能测试。

    @SpringBootTest //表示标注当前的类是一个测试类,不会随着项目打包发送
    @RunWith(SpringRunner.class) //表示启动这个单元测试类,不写不能运行。需要传递一个参数,必须是SpringRunner的实例类型
    public class UserMapperTests {
    
       @Autowired
       private UserMapper userMapper;
    
       @Test
       public void insert() {
           User user = new User();
           user.setUsername("tim");
           user.setPassword("123");
           Integer rows = userMapper.insert(user);
           System.out.println(rows);
       }
    
       @Test
       public void findByUsername() {
           User user = userMapper.findByUsername("tim");
           System.out.println(user);
       }
    }
    

4. 注册-业务层

  • 业务的定位

    1.业务:一套完整的数据处理过程,通常表现为用户认为的一个功能,但是在开发时对应多项数据操作。在项目中,通过业务控制每个“功能”(例如注册、登录等)的处理流程和相关逻辑。

    2.流程:先做什么,再做什么。例如:注册时,需要先判断用户名是否被占用,再决定是否完成注册。

    3.逻辑:能干什么,不能干什么。例如:注册时,如果用户名被占用,则不允许注册;反之,则允许注册。

    4.业务的主要作用是保障数据安全和数据的完整性、有效性。

  • 规划异常

    1.关于异常

    1.1请列举你认识的不少于十种异常:

    Throwable
    	Error
    		OutOfMemoryError(OOM)
    	Exception
    		SQLException
    		IOException
    			FileNotFoundException
    		RuntimeException
    			NullPointerException
    			ArithmeticException
    			ClassCastException
    			IndexOutOfBoundsException
    				ArrayIndexOutOfBoundsException
    				StringIndexOutOfBoundsException
    

    1.2 异常的处理方式和处理原则:

    异常的处理方式有:捕获处理(try…catch…finally),声明抛出(throw/throws)。如果当前方法适合处理,则捕获处理;如果当前方法不适合处理,则声明抛出。

    2.异常规划

    2.1 为了便于统一管理自定义异常,应先创建com.cy.store.service.ex.ServiceException自定义异常的基类异常,继承自RuntimeException类,并从父类生成子类的五个构造方法。

    public class ServiceException extends RuntimeException{
    //创建一个类继承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);
       }
    }
    

    2.2 当用户进行注册时,可能会因为用户名被占用而导致无法正常注册,此时需要抛出用户名被占用的异常,因此可以设计一个用户名重复的com.cy.store.service.ex.UsernameDuplicateException异常类,继承自ServiceException类,并从父类生成子类的五个构造方法。

    /** 用户名重复的异常 */
    public class UsernameDuplicateException extends ServiceException {
        // Override Methods...
    }
    

    2.3 在用户进行注册时,会执行数据库的INSERT操作,该操作也是有可能失败的。则创建cn.tedu.store.service.ex.InsertException`异常类,继承自ServiceException类,并从父类生成子类的5个构造方法。

    /** 插入数据的异常 */
    public class InsertException extends ServiceException {
        // Override Methods...
    }
    

    2.4 所有的自定义异常,都应是RuntimeException的子孙类异常。项目中目前异常的继承结构是见下。

    RuntimeException
    	-- ServiceException
    		-- UsernameDuplicateException
    		-- InsertException
    
  • 设计接口与抽象方法
    1.在service包下创建一个IUserService接口

    public interface IUserService {
       //用户注册方法
       void reg(User user);
    }
    

    2.实现抽象方法

    @Service //将当前类的对象交给Spring管理,自动创建对象以及对象的维护
    public class UserServiceImpl implements IUserService {
    
       @Autowired
       private UserMapper userMapper;
    
       @Override
       public void reg(User user) {
           //通过User对象获取username
           String username = user.getUsername();
           //调用findByUsername方法判断用户是否被注册
           User result = userMapper.findByUsername(username);
           //判断结果集是否不为null
           if(result != null) {
               throw new UsernameDuplicatedException("用户名被占用!");
           }
    
           //密码加密处理:MD5
           //(串 + password + 串) ---- md5算法进行加密,连续加载三次
           String oldPassword = user.getPassword();
           //获取颜值(随机生成一个盐值)
           String salt = UUID.randomUUID().toString().toUpperCase();
           //补全数据:盐值的记录
           user.setSalt(salt);
           //将密码和盐值作为一个整体进行加密处理
           String md5Password = getMD5Password(oldPassword, salt);
           //将加密之后的密码重新补全设置到user对象中
           user.setPassword(md5Password);
    
           //查看数据库表的信息,我们需要补全数据
           // is_delete设置成0
           user.setIsDelete(0);
           // 4个日志字段信息
           user.setCreatedUser(user.getUsername());
           user.setModifiedUser(user.getUsername());
           Date date = new Date();
           user.setCreatedTime(date);
           user.setModifiedTime(date);
    
           //执行注册业务功能的实现,注册正常的情况下 rows = 1
           Integer rows = userMapper.insert(user);
           if(rows != 1) {
               throw new InsertException("在用户注册过程中产生了未知的错误");
           }
       }
    
       //定义一个md5算法的加密处理
       private String getMD5Password(String password, String salt) {
           //md5加密算法方法的调用(进行三次加密)
           for (int i = 0; i < 3; i++) {
               password = DigestUtils.md5DigestAsHex((salt+password+salt).getBytes()).toUpperCase();
           }
           return password;
       }
    }
    

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

    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class UsersServiceTests {
    
       @Autowired
       private IUserService userService;
    
       @Test
       public void reg() {
           try {
               User user = new User();
               user.setUsername("hxp02");
               user.setPassword("123");
               userService.reg(user);
               System.out.println("OK");
           } catch (ServiceException e) {
               //获取类的对象,再获取类的名称
               System.out.println(e.getClass().getSimpleName());
               //获取异常的具体描述信息
               System.out.println(e.getMessage());
           }
       }
    }
    

5. 注册-控制层

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

    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;
        }
       	...//getter,setter
    
  • 设计请求
    设计用户提交的请求,并设计响应的方式:

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

  • 处理请求
    1.创建com.cy.store.controller.UserController控制器类,在类的声明之前添加@RestController和@RequestMapping(“users”)注解,在类中添加IUserService业务对象并使用@Autowired注解修饰。

    @RestController // @Controller+@ResponseBody
    @RequestMapping("users")
    public class UserController {
       @Autowired
       private IUserService userService;
    
       @RequestMapping("reg")
       //表示此方法的响应结果以JSON格式进行数据的响应给到前端
       public JsonResult<Void> reg(User user) {
           JsonResult<Void> result = new JsonResult<>();
           try {
               userService.reg(user);
               result.setState(200);
               result.setMessage("用户注册成功");
           } catch (UsernameDuplicatedException e) {
               result.setState(4000);
               result.setMessage("用户名被占用");
           } catch (InsertException e) {
               result.setState(5000);
               result.setMessage("注册时产生未知的异常");
           }
           return result;
       }
    }
    

    然后,我们就可以访问了:http://localhost:8080/users/reg?username=hxp&password=123456

  • 控制器层的调整
    每个controller里面,我们都要处理异常,这样太麻烦了,我们可以把异常抽取出来。
    创建提供控制器类的基类com.cy.store.controller.BaseController,在其中定义表示响应成功的状态码及统一处理异常的方法。

    @ExceptionHandler注解用于统一处理方法抛出的异常。当我们使用这个注解时,需要定义一个异常的处理方法,再给这个方法加上@ExceptionHandler注解,这个方法就会处理类中其他方法(被@RequestMapping注解)抛出的异常。@ExceptionHandler注解中可以添加参数,参数是某个异常类的class,代表这个方法专门处理该类异常。

    //控制层类的基类
    public class BaseController {
        //操作成功的状态码
        public static final int OK = 200;
    
        //请求处理方法,这个方法的返回值就是需要传递给前端的数据
        //自动将异常对象传递给此方法的参数列表上
        //当前项目中产生了异常,被统一拦截到地方法中,这个方法此时就充当请求处理方法,方法的返回值直接给到前端
        @ExceptionHandler(ServiceException.class) //用于统一处理抛出的异常
        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;
        }
    
    }
    

    简化UserController控制器类中的用户注册reg()方法的代码。

    @RequestMapping("reg")
    public JsonResult<Void> reg(User user) {
        userService.reg(user);
        return new JsonResult<>(OK);
    }
    

6. 注册-前端页面

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

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

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

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

	},
	error: function() {

	}
});

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

  • url:标识请求的url地址,不能带参。如:http://localhost:8080/users/reg
  • type:请求类型(如get和post请求的类型)
  • data:向指定的请求url地址提交的数据。如:data:“user=hxp&pwd=123”
  • dataType:提交的数据的类型,一般指定为json
  • success:当服务器正常响应客户端时,会自动调用success参数的方法,并且将服务器返回的数据以参数的形式传递给这个方法的参数上。
  • error:当服务器未正常响应客户端时,会自动调用error参数的方法,并且将服务器返回的数据以参数的形式传递给这个方法的参数上。

5.在web下的register.html页面中body标签内部的最后,添加script标签用于编写JavaScript程序。请求的url中需要添加项目的访问名称。

<script type="text/javascript">
	//监听注册按钮是否被点击,如果被点击,执行方法
	$("#btn-reg").click(function () { //btn-reg为注册按钮的id
		//发送ajax异步请求,完成用户的注册
		$.ajax({
			url: "/users/reg",
			type: "post",
			//序列化表单中提交的参数,user=hxp&pwd=123
			data: $("#form-reg").serialize(),
			dataType: "json",
			success: function (json) {
				if(json.state == 200) {
					alert("注册成功")
				} else {
					alert("注册失败");
				}
			},
			error: function (xhr) {
				alert("注册时产生未知的错误!" + xhr.status);
			}
		});
	});
</script>

完成后启动项目,打开浏览器访问http://localhost:8080/web/register.html页面并进行注册。

6.由于JavaScript代码和idea兼容性不是特别好,写出来的js代码可能无法正常被服务器解析执行,体现在点击页面中的按钮没有任何的响应。解决:

  • 在项目的maven下clear清理项目-install重新部署
  • 在项目的file选项下-cash清理缓存
  • 重新的去构建项目:build选项下-rebuild选项
  • 重启idea或重启电脑

你可能感兴趣的:(项目,spring,后端,java,intellij-idea,mysql)