> 文档中心 > SpringBoot+MyBatis+MYSQL项目实战一(用户的注册和登录)

SpringBoot+MyBatis+MYSQL项目实战一(用户的注册和登录)


SpringBoot+MyBatis+MYSQL项目实战一

项目源码地址:电脑商城实战

一:项目基础环境搭建

1.搭建环境

pom.xml文件的依赖

 <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency>     <groupId>org.mybatis.spring.boot</groupId>     <artifactId>mybatis-spring-boot-starter</artifactId>     <version>2.1.4</version> </dependency> <dependency>     <groupId>org.projectlombok</groupId>     <artifactId>lombok</artifactId>     <optional>true</optional> </dependency><!-- devtools--> <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-devtools</artifactId>     <optional>true</optional>     <scope>true</scope> </dependency> <dependency>     <groupId>mysql</groupId>     <artifactId>mysql-connector-java</artifactId>     <scope>runtime</scope> </dependency>

application.yaml文件配置

spring:  datasource:    url: jdbc:mysql://localhost:3306/store?serverTimezone=UTC&useUnicode=true&characterEcoding=utf-8    username: root    password: root  devtools:    restart:      enabled: true      exclude: templates      additional-paths: src/main/java    livereload:      port: 3579

测试数据库是否连接成功

@SpringBootTestclass StoreApplicationTests {    @Autowired    private DataSource dataSource;    @Test    void contextLoads() {    }    @Test    void getConnection() throws SQLException{ System.out.println(dataSource.getConnection());    }}

2.用户注册持久层
建表,搭建实体类,mapper层

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;

BaseEntity类

/** 作为实体类的基类 **/@Data@AllArgsConstructor@NoArgsConstructorpublic class BaseEntity {    private String createdUser;    private Date createdTime;    private String modifiedUser;    private Date modifiedTime;}

User实体类

/** 用户的实体类  springboot约定大于配置**/@Data@AllArgsConstructor@NoArgsConstructorpublic 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;}

UserMapper层接口

package com.example.store.mapper;import com.example.store.entity.User;import org.apache.ibatis.annotations.Mapper;/** 用户模块的持久层接口 **/public interface UserMapper {    /**     * 插入用户的数据     * @param user 用户的数据     * @return 受影响的行数(增、删、查。该兜售印象的函数作为返回值,可以根据返回值来判断是否执行成功     */    Integer insert(User user);    /**     * 根据用户名来查询用户的数据     * @param username 用户名     * @return 如果找到对应的用户名则返回这个用户的数据,如果没有找到则返回null值     */    User findByUsername(String username);}

配置SQL映射
在src/main/java创建mapper文件夹,并在该文件夹下创建UserMapper.xml文件

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.store.mapper.UserMapper">    <resultMap id="UserEntityMap" type="com.example.store.entity.User"> <result column="uid" property="uid"></result> <result column="is_delete" property="isDelete"/> <result column="created_user" property="createdUser"/> <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 = #{username}    </select></mapper>

由于使用了SQL映射,需要在application.yaml文件中添加

mybatis:  mapper-locations: classpath:mapper/*.xml  type-aliases-package: com.example.store.entity

3.进行单元测试

 * 单元测试方法:具备以下条件就可以单独运行,不用启动整个项目,可以做单元测试,提升了代码的测试效率 * 1.必须被@Test注解修饰 * 2.返回值类型必须是void * 3.方法的参数列表不指定任何类型 * 4.方法的访问修饰符必须是public

在Test文件夹下建立一个mapper层,并建立一个UserMapperTest测试类
每个测试类的上方必须增加@SpringBootTest@RunWith(SpringRunner.class)注解
@SpringBootTest表示标注当前的类是一个测试类,不会随项目一起打包

  • @RunWith表示启动这个单元测试类(不写的话,单元测试类是不能运行的),需要传递一个参数,必须是SpringRunner的实例
  • @SpringBootTest表示标注当前的类是一个测试类,不会随项目一起打包

在这里插入图片描述

// @SpringBootTest表示标注当前的类是一个测试类,不会随项目一起打包@SpringBootTest//  @RunWith表示启动这个单元测试类(不写的话,单元测试类是不能运行的),需要传递一个参数,必须是SpringRunner的实例@RunWith(SpringRunner.class)public class UserMapperTests {    // idea有检测的功能,接口是不能够直接创建Bean的(动态代理技术解决)在UserMapper增加@Repository    @Autowired    private UserMapper userMapper;    /**     * 单元测试方法:具备以下条件就可以单独运行,不用启动整个项目,可以做单元测试,提升了代码的测试效率     * 1.必须被@Test注解修饰     * 2.返回值类型必须是void     * 3.方法的参数列表不指定任何类型     * 4.方法的访问修饰符必须是public     * */    @Test    public void insert(){ User user  = new User(); user.setUsername("wangchong"); user.setPassword("123456"); Integer rows = userMapper.insert(user); System.out.println(rows);    }     @Test    public void findByUsername(){ User user = userMapper.findByUsername("wangchong"); System.out.println(user);    }}

二:注册-业务层

2.1规划异常——自定义异常机制
1.RuntimeException异常,作为这个异常的子类,然后再去定义具体的异常类型来继承这个异常。业务层异常的基类,ServiceException异常,这个异常继承RuntimeException.

package com.example.store.service.ex;/** 业务层异常的基类: throws new ServiceException("业务层产生未知的异常")*/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);    }}

根据业务层不同的功能来详细定义具体的异常的类型,统一的去继承ServiceException
2.用户在进行注册时可能会产生用户名被占用的错误,抛出一个异常:UsernameDuplicatedException异常.

package com.example.store.service.ex;//表示用户名被占用的异常public class UsernameDuplicatedException extends ServiceException{    // alt + insert --- override methods    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);    }}

3.数据插入过程中产生的异常,方法同上

2.2设计接口和抽象方法
1.在service层下加一个IUserService接口

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

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

@Service  //@Service 注解:将当前类的对象交给spring来管理,自动创建对象以及对象的维护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("用户名被占用"); } //密码加密;使用md5进行加密 ,连续加载三次 // (串 + password + 串) --- MD5算法进行加密,连续加载三次 String oldPassword = user.getPassword(); // 获取盐值(随机生曾一个盐值) String salt = UUID.randomUUID().toString().toUpperCase(); // 将密码和盐值作为一个整体进行加密处理 //补全数据:盐值记录 user.setSalt(salt); String md5Password = getMD5Password(oldPassword,salt); user.setPassword(md5Password); //补全数据: is_delete设置为0 user.setIsDelete(0); // 补全四个基类的属性 user.setCreatedUser(user.getUsername()); user.setModifiedUser(user.getUsername()); Date date = new Date(); user.setCreatedTime(date); user.setModifiedTime(date); // 执行注册业务功能的实现 Integer rows = userMapper.insert(user); if(rows != 1){     throw new InsertException("在用户注册过程中,产生了未知的异常"); }    }    /** 加密处理方法*/    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.单元测试类

 @Autowired    private IUserService UserService;    /**     * 单元测试方法:具备以下条件就可以单独运行,不用启动整个项目,可以做单元测试,提升了代码的测试效率     * 1.必须被@Test注解修饰     * 2.返回值类型必须是void     * 3.方法的参数列表不指定任何类型     * 4.方法的访问修饰符必须是public     * */    @Test    public void reg(){ try {     User user  = new User();     user.setUsername("wang");     user.setPassword("123456");     UserService.reg(user);     System.out.println("ok"); } catch (ServiceException e) {     // 获取类的对象,再获取类的名称     System.out.println(e.getClass().getSimpleName());     System.out.println(e.getMessage()); }    }

4.使用MD5进行盐值加密

 /*     * 加密规则:     * 1、无视原始密码的强度     * 2、使用UUID作为盐值,在原始密码的左右两侧拼接     * 3、循环加密3次     */      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.1创建响应
状态码、状态描述信息、数据。这部分功能封装一个类中,将这类作为返回值,返沪给前端游览器。
建立一个util包,新建一个JsonResult.java

@Data@AllArgsConstructor@NoArgsConstructorpublic class JsonResult<E> implements Serializable {    // 状态码    private Integer state;    // 描述信息    private String message;    //数据    private E data;    public JsonResult(Throwable e){ this.message = e.getMessage();    }}

四:设计请求和注册——前端页面

4.1设计用户提交的请求,并设计响应的方式

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

4.2处理请求——创建controller

@Controller@RequestMapping("/users")public class UserController {    @Autowired    private IUserService iUserService;    @RequestMapping("/reg")    @ResponseBody    public JsonResult<Void> reg(User user){ //创建响应结果对象 JsonResult<Void> result = new JsonResult<>(); try {     iUserService.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;    }}

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

/** * 控制层的基类 */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更改

@Controller@RequestMapping("/users")public class UserController extends BaseController{    @Autowired    private IUserService iUserService;    @RequestMapping("/reg")    @ResponseBody    public JsonResult<Void> reg(User user){ iUserService.reg(user); return new JsonResult<>(OK);    }}