项目基本环境
1:JDK:1.8
2:maven:需要配置到idea,3.6.1版本
3:数据库:MariaDB,MySQL,要求是5.1版本
4:开发的平台:idea开发
1.创建数据库
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.创建用户的实体类
1.通过表的结构提取出表的公共字段,放在一个实体类的基类中,起名BaseEntity基类中
//实体类User因为要在网络中以流的形式传输,所以需要serialize序列化
//开发项目的时候需要在实体类上面加@Component然后spring才能自动进行对象的创建维护,而springboot不再需要,因为springboot遵循的原则是约定大于配置,如果字段名称相同那就可以自动完成字段的初始化
public class BaseEntity implements Serializable {
private String createdUser;
private Date createdTime;
private String modifiedUser;
private Date emodifiedTime;
/**
* get,set
* equals和hashCode
* toString
*/
}
2.创建用户的实体类,并使其继承BaseEntity基类
public class User extends BaseEntity {
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;
/**
* get,set
* equals和hashCode
* toString
*/
}
3.注册-持久层
3.1设计接口和抽象方法及实现
public interface UserMapper {
/**
* 插入用户的数据
* @param user 用户的数据
* @return 受影响的行数(增删改都将受影响的行数作为返回值,可以根据返回值来判断是否执行成功)
*/
Integer insert(User user);
/**
* 根据用户名来查询用户的数据
* @param username 用户名
* @return 如果找到对应的用户则返回这个用户的数据,如果没有找到则返回null
*/
User findByUsername(String username);
}
3.2编写映射
<?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.cy.store.mapper.UserMapper">
<!--将配置接口的方法对应到SQL语句上-->
<!--在sql语句的最上面借助ResultMap标签来自定义映射规则
id属性:表示给这个映射规则分配一个唯一的id值,对应的就是resultMap="id属性值"
type属性:取值是一个类,表示数据库中的查询结果与java中哪个实体类进行结果集的映射
-->
<resultMap id="UserEntityMap" type="com.cy.store.entity.User">
<!--将表的字段和类的属性名不一致的进行匹配指定,名称一致的也可以指定,但没必要
但是,在定义映射规则时无论主键名称是否一致都不能省
column属性:表示表中的字段名称
property属性:表示类中的属性名称
-->
<id column="uid" property="uid"></id>
<result column="is_delete" property="isDelete"></result>
<result column="created_user" property="createdUser"></result>
<result column="created_time" property="createdTime"></result>
<result column="modified_user" property="modifiedUser"></result>
<result column="modified_time" property="modifiedTime"></result>
</resultMap>
<!--id属性:表示映射的接口中方法的名称,直接标签的内容部来编写SQL语句-->
<!--useGeneratedKeys="true"表示开启某个字段的值递增(大部分都是主键递增)
keyProperty="uid"表示将表中哪个字段进行递增
-->
<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.cy.store.entity.User",因为这种写法要求表的字段的名字和类的属性名一模一样
resultMap:表示当表的字段和类的对象属性名不一致时,来自定义查询结果集的映射规则
-->
<select id="findByUsername" resultMap="UserEntityMap">
select * from t_user where username=#{username}
</select>
</mapper>
4.注册-业务层
4.1规划异常(后续用到的异常都要继承ServiceException类并重写里面的所有方法)
/**
* 因为整个业务的异常只有一种情况下才会产生:只有运行时才会产生,不运行不会产生
* 所以要求业务层的异常都要继承运行时异常RuntimeException并且重写父类的所有构造方法以便后期能抛出自已定义的异常
*/
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);
}
}
4.2设计接口和抽象方法
1.在service包下创建IUserService接口(接口命名的默认规则:I+业务名字+层的名字)
/**用户模块业务层接口*/
public interface IUserService {
/**
* 用户注册方法
* @param user 用户的数据对象
*/
void reg(User user);
}
2.创建一个实现UserServiceImpl类,需要实现IUserService接口,并且实现抽象的方法
@Service
public class UserServiceImpl implements IUserService {
//reg方法核心就是调用mapper层的方法,所以要声明UserMapper对象并加@Autowired注解
@Autowired
private UserMapper userMapper;
@Override
public void reg(User user) {
//通过user参数来获取传递过来的username
String username = user.getUsername();
//调用mapper的findByUsername(username)判断用户是否被注册过了
User result = userMapper.findByUsername(username);
//判断结果集是否为null,不为null的话则需抛出用户名被占用的异常
if (result != null) {
//抛出异常
throw new UsernameDuplicatedException("用户名被占用");
}
/**
* 密码加密处理作用:
* 1.后端不再能直接看到用户的密码2.忽略了密码原来的强度,提升了数据安全性
* 密码加密处理的实现:
* 串+password+串->交给md5算法连续加密三次
* 串就是数据库字段中的盐值,是一个随机字符串
*/
String oldpassword = user.getPassword();
//1.随机生成一个盐值(大写的随机字符串)
String salt = UUID.randomUUID().toString().toUpperCase();
//2.将密码和盐值作为一个整体进行加密处理
String md5Password = getMD5Password(oldpassword, salt);
//3.将盐值保存到数据库
user.setSalt(salt);
//4.将加密之后的密码重新补全设置到user对象当中
user.setPassword(md5Password);
//补全数据:is_delete设置为0
user.setIsDelete(0);
//补全数据:四个日志字段信息
user.setCreatedUser(user.getUsername());
user.setModifiedUser(user.getUsername());
Date date = new Date();//java.util.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) {
for (int i = 0; i < 3; i++) {
password = DigestUtils.md5DigestAsHex((salt + password + salt).getBytes()).toUpperCase();
}
return password;
}
}
5.注册-控制层
5.1创建响应
状态码,状态描述信息,数据是所有控制层对应的方法都涉及到的操作,所以把这部分功能封装到一个类JsonResult中,将这个类作为方法的返回值返回给前端浏览器:
//因为所有的响应的结果都采用Json格式的数据进行响应,所以需要实现Serializable接口
public class JsonResult<E> implements Serializable {
//状态码
private Integer state;
//描述信息
private String message;
//数据类型不确定,用E表示任何的数据类型,一个类里如果声明的有泛型的数据类型,类也要声明为泛型
private E data;
//无参构造
public JsonResult() {
}
//将状态码传给构造方法初始化对象
public JsonResult(Integer state) {
this.state = state;
}
//将状态码和数据传给构造方法初始化对象
public JsonResult(Integer state, E data) {
this.state = state;
this.data = data;
}
//如果有异常,直接将异常传递给构造方法初始化对象
public JsonResult(Throwable e) {
this.message=e.getMessage();
}
/**以及属性的get和set方法*/
}
5.2处理请求
1.在controller包下创建UserController类作为控制层下类的基类,用来做统一的异常捕获:
public class BaseController {
//操作成功的状态码
public static final int OK = 200;
/**
* 1.@ExceptionHandler表示该方法用于处理捕获抛出的异常
* 2.什么样的异常才会被这个方法处理呢?所以需要ServiceException.class,这样的话只要是抛出ServiceException异常就会被拦截到handleException方法,此时handleException方法就是请求处理方法,返回值就是需要传递给前端的数据
* 3.被ExceptionHandler修饰后如果项目发生异常,那么异常对象就会被自动传递给此方法的参数列表上,所以形参就需要写Throwable e用来接收异常对象
*/
@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;
}
}
2.让UserController继承BaseController使得reg方法只需要关注请求处理而不再需要关注异常捕获:
@RestController //其作用等同于@Controller+@ResponseBody
//@Controller
@RequestMapping("users")
public class UserController extends BaseController {
@Autowired
private IUserService userService;
@RequestMapping("reg")
//@ResponseBody //表示此方法的响应结果以json格式进行数据的响应给到前端
public JsonResult<Void> reg(User user) {
userService.reg(user);
return new JsonResult<>(OK);
}
}