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

测试数据库是否连接成功

@SpringBootTest
class 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
@NoArgsConstructor
public class BaseEntity {
    private String createdUser;
    private Date createdTime;
    private String modifiedUser;
    private Date modifiedTime;
}

User实体类

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

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文件

<?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.example.store.mapper.UserMapper">
<!--    id属性:标签给这个映射负责分配一个唯一的id值,对应的就是resultMap=“id"的属性的取值-->
<!--    type属性:取值是一个类,表示的是数据库中的查询结果与java中那个实体类进行结果映射-->

    <resultMap id="UserEntityMap" type="com.example.store.entity.User">
<!--        将表的资源和类的睡醒不一样的字段进行匹配指定,名称一直的字段可以省略不写-->
<!--
            配合完成名称不一致的映射
            column属性:表示表中的资源名称
            property属性:表示类中的属性名称
-->
<!--        在定义映射规则时主键是不可以省略的-->
        <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>




    <!--    id属性:表示映射的接口中方法的名称-->
<!--    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>


<!--    select语句在执行的时候,查询的结果是一个对象,多个对象-->
<!--    resultType:表示查询的结果集类型,只需要指定对应映射类的类型,并且包含完整包接口: resultType="com.example.store.entity.User"
        resultMap: 表示当表的字段和类的对象属性的字段名称不一致值,来自定义映射规则
-->
    <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
@NoArgsConstructor
public 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);
    }
}
  • 11
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@黑夜中的一盏明灯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值