• SpringBoot项目--电脑商城【用户注册】


    1.创建数据表

    1.1 创建t_user表

    1. CREATE TABLE t_user (
    2. uid INT AUTO_INCREMENT COMMENT '用户id',
    3. username VARCHAR(20) NOT NULL UNIQUE COMMENT '用户名',
    4. `password` CHAR(32) NOT NULL COMMENT '密码',
    5. salt CHAR(36) COMMENT '盐值',
    6. phone VARCHAR(20) COMMENT '电话号码',
    7. email VARCHAR(30) COMMENT '电子邮箱',
    8. gender INT COMMENT '性别:0-女,1-男',
    9. avatar VARCHAR(50) COMMENT '头像',
    10. is_delete INT COMMENT '是否删除:0-未删除,1-已删除',
    11. created_user VARCHAR(20) COMMENT '日志-创建人',
    12. created_time DATETIME COMMENT '日志-创建时间',
    13. modified_user VARCHAR(20) COMMENT '日志-最后修改执行人',
    14. modified_time DATETIME COMMENT '日志-最后修改时间',
    15. PRIMARY KEY (uid)
    16. ) ENGINE=INNODB DEFAULT CHARSET=utf8;

    1. 2 创建用户的实体类

    将来任何一张表都有以下四个字段:

    created_user VARCHAR(20) COMMENT ‘创建人’,

    created_time DATETIME COMMENT ‘创建时间’,

    modified_user VARCHAR(20) COMMENT ‘修改人’,

    modified_time DATETIME COMMENT ‘修改时间’,

    所以为了开发方便可以把这四个字段作为整个实体类

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

    1. /**
    2. * 实体类的基类
    3. */
    4. @Data
    5. @AllArgsConstructor
    6. @NoArgsConstructor
    7. public class BaseEntity implements Serializable {
    8. private String createdUser;
    9. private Date createdTime;
    10. private String modifiedUser;
    11. private Date modifiedTime;
    12. }

    2.创建用户的实体类,并使其继承BaseEntity基类

    1. /**
    2. * 用户实体类
    3. */
    4. @Data
    5. @AllArgsConstructor
    6. @NoArgsConstructor
    7. public class User extends BaseEntity implements Serializable {
    8. private Integer uid;//用户id
    9. private String username;//用户名
    10. private String password;//密码
    11. private String salt;//盐值
    12. private String phone;//电话号码
    13. private String email;//电子邮箱
    14. private Integer gender;//性别:0-女,1-男
    15. private String avatar;//头像
    16. private Integer isDelete;//是否删除:0-未删除,1-已删除
    17. }

    注意点:

    • 实体类User因为要在网络中以流的形式传输,所以需要serialize序列化(但因为其继承的父类BaseEntity已经实现序列化,所以就不需要再写implements Serializable)

    • ssm框架开发项目的时候需要在实体类上面加@Component然后spring才能自动进行对象的创建维护,而springboot不再需要,因为springboot遵循的原则是约定大于配置,如果字段名称相同那就可以自动完成字段的初始化

    2.  持久层[Mapper]

    a. 规划需要执行的SQL语句

    1.用户的注册功能,从后端持久层来看相当于在做数据的插入操作

    inser into t_user (username)
    

    2.在用户的注册时首先要去查询当前的用户名是否存在,如果存在则不能进行注册,相当于是一条查询语句

    select * from t_user where username=?
    

    b.设计接口和抽象方法及实现

    1.定义Mapper接口.在项目的目录结构下首先创建一个mapper包,在这个包下再根据不同的功能模块来创建mapper接口.注册功能需要在mapper包下创建UserMapper接口然后定义上述两个SQL语句的抽象方法

    1. /**
    2. * 用户模块的持久层接口
    3. */
    4. //@Mapper
    5. public interface UserMapper {
    6. /**
    7. * 插入用户的数据
    8. *
    9. * @param user
    10. * @return:受到影响的行数
    11. */
    12. Integer insert(User user);
    13. /**
    14. * 根据用户名来查询用户数据
    15. * @param username
    16. * @return:如果找到对应的用户则返回这个用户的数据,如果没有则返回null
    17. */
    18. User findByUsername(String username);
    19. }

    2.ssm框架开发项目的时候需要在mapper接口上加@Mapper用于自动生成相应的接口实现类,在springboot也可以这样,但是后续会有很多mapper接口,每个接口分别加@Mapper太麻烦了,所以在启动类类里面指定当前项目的mapper接口在哪,然后项目启动的时候会自动加载所有的接口

    1. //MapperScan注解指定当前项目中Mapper接口路径的位置,在项目启动的时候自动加载所有的接口
    2. @MapperScan("com.example.mycomputerstore.mapper")

    c. 编写映射


    1.定义xml映射文件,与对应的接口进行关联.所有的映射文件都属于资源文件,需要放在resources目录下,为了管理方便我们在resources目录下创建一个mapper文件夹,然后在这个文件夹里存放mapper的映射文件

    2.创建接口的映射文件,需要和接口的名称保持一致.如UserMapper.xml

    UserMapper.xml的配置在Mybatis官网

    1. "1.0" encoding="UTF-8" ?>
    2. mapper
    3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    5. <mapper namespace="com.example.mycomputerstore.mapper.UserMapper">
    6. mapper>

    3.将配置接口的方法对应到SQL语句上

    insert into () values (),因为values后面插入的值是动态值,mybatis规定需要用占位符来占位,并给占位符起一个变量的名字,且变量的名字需要在占位符#{}内部
    创建t_user表时uid INT AUTO_INCREMENT COMMENT ‘用户id’,中的AUTO_INCREMENT表示主键uid自增,所以需要useGeneratedKeys和keyProperty

    1. <resultMap id="UserEntityMap" type="com.example.mycomputerstore.entity.User">
    2. <id column="uid" property="uid">id>
    3. <result column="is_delete" property="isDelete">result>
    4. <result column="created_User" property="createdUser">result>
    5. <result column="created_time" property="createdTime">result>
    6. <result column="modified_user" property="modifiedUser">result>
    7. <result column="modified_time" property="modifiedTime">result>
    8. resultMap>
    9. <insert id="insert" useGeneratedKeys="true" keyProperty="uid">
    10. INSERT INTO t_user (
    11. username,password,salt,phone,email,gender,avatar,is_delete,
    12. created_user,created_time,modified_user,modified_time
    13. ) VALUES (
    14. /*动态值,需要用占位符进行占位,需要给每个占位符起个专门的名字*/
    15. #{username},#{password},#{salt},#{phone},#{email},#{gender},#{avatar},#{isDelete},
    16. #{createdUser},#{createdTime},#{modifiedUser},#{modifiedTime}
    17. )
    18. insert>

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

    在application.properties文件中增添:

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

    d. 单元测试

    每个独立的层编写完毕后需要编写单元测试方法来测试当前的功能:在test包结构下创建一个mapper包,在这个包下再创建持久层的功能测试,单元测试方法是独立运行,不用启动整个项目,提高了代码的测试效率

    1. @SpringBootTest//表示标注当前的类是一个测试类,不会随同一块打包
    2. //RunWith:表示启动这个单元测试类,需要传递一个参数,必须是SpringBoot的实例对象
    3. @RunWith(SpringRunner.class)
    4. public class UserMapperTests {
    5. //idea检测功能,接口是不能直接创建Bean(动态代理来解决)
    6. @Autowired
    7. private UserMapper userMapper;
    8. /**
    9. * 单元测试
    10. * 1.返回值必须是void
    11. * 2.必须是public
    12. */
    13. @Test
    14. public void insert(){
    15. User user = new User();
    16. user.setUsername("tim");
    17. user.setPassword("123");
    18. Integer rows = userMapper.insert(user);
    19. System.out.println(rows);
    20. }
    21. @Test
    22. public void findByUsername() {
    23. User user = userMapper.findByUsername("张三");
    24. System.out.println(user);
    25. }
    26. }

    3. 业务层[Service]

    a. 业务层的核心功能:

    • 接受前端从控制器流转过来的数据
    • 结合真实的注册业务来完成功能业务逻辑的调转和流程

    所以这里要考虑到真实的业务场景,如果只考虑业务场景的话不完整,因为在整个业务执行的过程中会产生很多问题,从java角度来讲这些都是属于异常,所以在业务开发的时候就要规划相关的异常,以便把项目的错误控制在一定范围内

    b. service下的目录结构:

    service包下创建ex包用来写异常类
    service包下创建impl包用来写接口的实现类
    接口直接写在service包下,不再需要接口包

    c. 规划异常

    1.为什么会有异常:

    比如,用户在进行注册时可能会产生用户名被占用的错误,这时需要抛出一个异常

    2.怎么处理异常:

    异常不能用RuntimeException,太笼统了,开发者没办法第一时间定位到具体的错误类型上,我们可以定义具体的异常类型来继承这个异常.
    正常的开发中异常又要分等级,可能是在业务层产生异常,可能是在控制层产生异常,所以可以创建一个业务层异常的基类,起名ServiceException异常,并使其继承RuntimeException异常
    后期开发业务层时具体的异常可以再继承业务层的异常ServiceException


    3.处理异常的具体步骤:

    步骤一:在ex包下创建ServiceException类作为业务层异常的基类:

    1. /**
    2. * 因为整个业务的异常只有一种情况下才会产生:只有运行时才会产生,不运行不会产生
    3. * 业务异常的基类:throw new ServiceException("业务层产生未知的异常")
    4. */
    5. public class ServiceException extends RuntimeException{
    6. public ServiceException() {
    7. super();
    8. }
    9. public ServiceException(String message) {
    10. super(message);
    11. }
    12. public ServiceException(String message, Throwable cause) {
    13. super(message, cause);
    14. }
    15. public ServiceException(Throwable cause) {
    16. super(cause);
    17. }
    18. protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
    19. super(message, cause, enableSuppression, writableStackTrace);
    20. }
    21. }

    步骤二:后期再根据业务层不同的功能来详细定义具体的异常类型,并统一的继承ServiceException异常基类:

    • 用户在进行注册时可能会产生用户名被占用的错误,这时需要抛出一个UsernameDuplicatedException异常
    1. /**
    2. * 用户名被占用的异常
    3. */
    4. public class UsernameDuplicatedException extends ServiceException {
    5. //alt+insert---生成override methods
    6. public UsernameDuplicatedException() {
    7. super();
    8. }
    9. public UsernameDuplicatedException(String message) {
    10. super(message);
    11. }
    12. public UsernameDuplicatedException(String message, Throwable cause) {
    13. super(message, cause);
    14. }
    15. public UsernameDuplicatedException(Throwable cause) {
    16. super(cause);
    17. }
    18. protected UsernameDuplicatedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
    19. super(message, cause, enableSuppression, writableStackTrace);
    20. }
    21. }
    • 正在执行数据插入操作的时候,服务器宕机或数据库宕机.这种情况是处于正在执行插入的过程中所产生的异常,起名InsertException异常
    1. /**
    2. * 数据在插入的异常
    3. */
    4. public class InsertException extends ServiceException {
    5. public InsertException() {
    6. super();
    7. }
    8. public InsertException(String message) {
    9. super(message);
    10. }
    11. public InsertException(String message, Throwable cause) {
    12. super(message, cause);
    13. }
    14. public InsertException(Throwable cause) {
    15. super(cause);
    16. }
    17. protected InsertException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
    18. super(message, cause, enableSuppression, writableStackTrace);
    19. }
    20. }

    d. 设计接口和抽象方法

    1.在service包下创建IUserService接口(接口命名的默认规则:I+业务名字+层的名字)

    1. /**用户模块业务层接口*/
    2. public interface IUserService {
    3. /**
    4. * 用户注册方法
    5. * @param user 用户的数据对象
    6. */
    7. void reg(User user);
    8. }

    2.创建一个实现UserServiceImpl类,需要实现IUserService接口,并且实现抽象的方法

    因为要将这个实现类交给spring管理,所以需要在类上加@Service

    1. /**
    2. * 用户模块业务层实现类
    3. */
    4. @Service//将当前类的对象交给Spring来管理,自动创建对象以及对象的维护
    5. public class UserServiceImpl implements IUserService {
    6. @Autowired
    7. private UserMapper userMapper;
    8. @Override
    9. public void reg(User user) {
    10. //通过user参数来获取传递过来的username
    11. String username = user.getUsername();
    12. //调用findByUsername(username) 判断用户是否被注册过
    13. User result = userMapper.findByUsername(username);
    14. //判断结果集是否为null
    15. if(result!=null){
    16. //如果不为null则抛出用户名被占用的异常
    17. throw new UsernameDuplicatedException("用户名被占用");
    18. }
    19. //密码加密处理的实现:md5算法的形式:57hwdowhdow
    20. //串【盐值】+passsword+串【盐值】----md5算法进行加密,连续加载三次
    21. //盐值:随机的字符串
    22. String oldpassword = user.getPassword();
    23. //获取盐值(随机生成一个盐值)
    24. String salt = UUID.randomUUID().toString().toUpperCase();
    25. //补全数据:盐值的记录
    26. user.setSalt(salt);
    27. //将密码和盐值作为一个整体进行加密处理,忽略原有密码强度提升了数据安全性
    28. String md5Password = getMD5Password(oldpassword, salt);
    29. //将加密后的密码重新补全设置到user中
    30. user.setPassword(md5Password);
    31. //补全数据:is_delete设置为0
    32. user.setIsDelete(0);
    33. //补全数据:4个日志字段信息
    34. user.setCreatedUser(user.getUsername());
    35. user.setModifiedUser(user.getUsername());
    36. Date date = new Date();
    37. user.setCreatedTime(date);
    38. user.setModifiedTime(date);
    39. //执行注册业务功能的实现
    40. Integer rows = userMapper.insert(user);
    41. //执行插入成功rows=1
    42. if(rows!=1){
    43. throw new InsertException("用户注册过程产生了未知的异常");
    44. }
    45. }
    46. }

    md5加密算法以后可能还要多次用到,为了方便在UserServiceImpl类里面单独写一个getMD5Password方法

    1. /**
    2. * 定义一个md5算法的加密处理
    3. */
    4. private String getMD5Password(String password, String salt) {
    5. //MD5加密算法的调用(进行三次加密)
    6. for(int i=0;i<3;i++){
    7. password=DigestUtils.md5DigestAsHex((salt+password+salt).getBytes()).toUpperCase();
    8. }
    9. //返回之后的密码
    10. return password;
    11. }

    e.单元测试

    1. @SpringBootTest//表示标注当前的类是一个测试类,不会随同一块打包
    2. //RunWith:表示启动这个单元测试类,需要传递一个参数,必须是SpringBoot的实例对象
    3. @RunWith(SpringRunner.class)
    4. public class UserServiceTests {
    5. //idea检测功能,接口是不能直接创建Bean(动态代理来解决)
    6. @Autowired
    7. private IUserService userService;
    8. /**
    9. * 单元测试
    10. * 1.返回值必须是void
    11. * 2.必须是public
    12. */
    13. @Test
    14. public void insert(){
    15. try {
    16. User user = new User();
    17. user.setUsername("smy");
    18. user.setPassword("123");
    19. userService.reg(user);
    20. System.out.println("ok");
    21. } catch (SecurityException e){
    22. //获取类的对象,在获取类的名称
    23. System.out.println(e.getClass().getSimpleName());
    24. //获取异常的具体描述信息
    25. System.out.println(e.getMessage());
    26. }
    27. }
    28. }

    4.控制层[controller]

    4.1 创建响应

    状态码,状态描述信息,数据是所有控制层对应的方法都涉及到的操作,所以把这部分功能封装到一个类JsonResult中,将这个类作为方法的返回值返回给前端浏览器:

    1. /**
    2. * Json格式的数据进行响应
    3. */
    4. @Data
    5. public class JsonResult implements Serializable {
    6. /**
    7. * 状态码
    8. */
    9. private Integer state;
    10. /**
    11. * 描述信息
    12. */
    13. private String message;
    14. /**
    15. * 数据
    16. */
    17. private E data;
    18. public JsonResult() {
    19. }
    20. public JsonResult(Integer state) {
    21. this.state = state;
    22. }
    23. public JsonResult(Throwable e) {
    24. this.message = e.getMessage();
    25. }
    26. public JsonResult(Integer state, E data) {
    27. this.state = state;
    28. this.data = data;
    29. }
    30. public Integer getState() {
    31. return state;
    32. }
    33. public void setState(Integer state) {
    34. this.state = state;
    35. }
    36. public String getMessage() {
    37. return message;
    38. }
    39. public void setMessage(String message) {
    40. this.message = message;
    41. }
    42. public E getData() {
    43. return data;
    44. }
    45. public void setData(E data) {
    46. this.data = data;
    47. }
    48. }

    4.2 设计请求

    接下来该向后端服务器发送请求以把用户数据插入到数据库,设计发送请求模块的第一步就是设计相关的请求

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

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

    4.3 处理请求

    创建一个控制层对应的UserController类,依赖于业务层的接口.编写完成后启动主服务验证一下

    1. //@RequestMapping("users")
    2. @RestController//RestController=@RequestMapping+@RequestBody
    3. @RequestMapping("/users")
    4. /**
    5. * 这里继承BaseController表示:可以处理异常类
    6. */
    7. public class UserController extends BaseController {
    8. @Autowired
    9. private IUserService userService;
    10. @GetMapping("/reg") //@RequestBody//表示此方法的响应结果以json格式进行数据的1响应给前端
    11. public JsonResult reg(User user) {
    12. //创建响应结果对象
    13. JsonResult result = new JsonResult<>();
    14. try {
    15. userService.reg(user);
    16. result.setState(200);
    17. result.setMessage("用户注册成功");
    18. } catch (UsernameDuplicatedException e) {
    19. result.setState(4000);
    20. result.setMessage("用户名被占用");
    21. } catch (InsertException e) {
    22. result.setState(5000);
    23. result.setMessage("注册时产生未知的异常");
    24. }
    25. return result;
    26. }
    27. }

    4.4 控制层优化设计


    凡是业务层抛出的异常我们都在控制层进行了捕获,如果其他的业务模块也抛用户名被占用或者插入时异常,那么抛出异常的代码就要重复编写

    优化方法:在控制层抽离出一个BaseController父类,在这个父类中统一处理关于异常的相关操作,优化如下:

    1.在controller包下创建UserController类作为控制层下类的基类,用来做统一的异常捕获:

    1. public class BaseController {
    2. //操作成功的状态码
    3. public static final int OK = 200;
    4. /**
    5. * 1.@ExceptionHandler表示该方法用于处理捕获抛出的异常
    6. * 2.什么样的异常才会被这个方法处理呢?所以需要ServiceException.class,这样的话只要是抛出ServiceException异常就会被拦截到handleException方法,此时handleException方法就是请求处理方法,返回值就是需要传递给前端的数据
    7. * 3.被ExceptionHandler修饰后如果项目发生异常,那么异常对象就会被自动传递给此方法的参数列表上,所以形参就需要写Throwable e用来接收异常对象
    8. */
    9. @ExceptionHandler(ServiceException.class)
    10. public JsonResult handleException(Throwable e) {
    11. JsonResult result = new JsonResult<>(e);
    12. if (e instanceof UsernameDuplicatedException) {
    13. result.setState(4000);
    14. result.setMessage("用户名已经被占用");
    15. } else if (e instanceof InsertException) {
    16. result.setState(5000);
    17. result.setMessage("插入数据时产生未知的异常");
    18. }
    19. return result;
    20. }
    21. }

    2.让UserController继承BaseController并重构UserController下的reg方法使该方法只需要关注请求处理而不再需要关注异常捕获:

    1. /**
    2. * 1.接收数据方式:请求处理方法的参数列表设置为pojo类型来接收前端的数据
    3. * SpringBoot会将前端url地址中的参数名和pojo类的属性名进行比较,如果这两个名称相同,
    4. * 则将值注入到pojo类中对于的属性上
    5. * @param user
    6. * @return
    7. */
    8. @PostMapping("/reg")
    9. //@RequestBody//表示此方法的响应结果以json格式进行数据的1响应给前端
    10. public JsonResult reg(User user) {
    11. userService.reg(user);
    12. return new JsonResult<>(OK);
    13. }

    5.前端页面

    1 熟悉ajax

    1.什么是ajax函数?

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

    2. ajax()函数的语法结构:

    • 使用ajax()时需要传递一个方法体作为方法的参数来使用(一对大括号就是一个方法体)
    • ajax接受多个参数时,参数与参数之间使用","分割
    • 每一组参数之间使用":"进行分割
    • 参数的组成部分一个是参数的名称(不能随便定义),另一个是参数的值(必须用字符串来表示)
    • 参数的声明顺序没有要求
    1. $.ajax({
    2. url: "",
    3. type: "",
    4. data: "",
    5. dataType: "",
    6. success: function() {
    7. },
    8. error: function() {
    9. }
    10. });

    3.ajax()函数参数的含义:

    2前端js编写

    js代码可以独立声明在一个js文件里或者声明在一个script标签中.现在我们在register.html中编写js代码,js代码可以放在head标签中,也可以放在body标签中,可以放在任意一个位置,只要被script标签包裹就行了,这里我们放在整个body结束之前:

  • 相关阅读:
    常见的RabbitMQ测试点及解决办法
    后端编译与优化(JIT,即时编译器)
    prisma使用mongodb副本集群报错引发的一些列问题
    17张图带你深度剖析 ArrayDeque(JDK双端队列)源码
    固态继电器的用途概述
    tcpdump 如何使用
    支持向量机基本原理,Libsvm工具箱详细介绍,基于支持向量机SVM的遥感图像分类
    蓝桥等考Python组别十三级007
    js之循环
    12【MyBatis注解开发】
  • 原文地址:https://blog.csdn.net/m0_63077733/article/details/132679039