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