目录
1. 用户注册
1. 创建t_user表
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. 创建用户实体类
- BaseEntity基类中
import java.io.Serializable;
import java.util.Date;
//作为实体类基类
public class BaseEntity implements Serializable {
private String createdUser;
private Date createdTime;
private String modifiedUser;
private String modifiedTime;
public String getCreatedUser() {
return createdUser;
}
public void setCreatedUser(String createdUser) {
this.createdUser = createdUser;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
public String getModifiedUser() {
return modifiedUser;
}
public void setModifiedUser(String modifiedUser) {
this.modifiedUser = modifiedUser;
}
public String getModifiedTime() {
return modifiedTime;
}
public void setModifiedTime(String modifiedTime) {
this.modifiedTime = modifiedTime;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BaseEntity)) return false;
BaseEntity that = (BaseEntity) o;
if (getCreatedUser() != null ? !getCreatedUser().equals(that.getCreatedUser()) : that.getCreatedUser() != null)
return false;
if (getCreatedTime() != null ? !getCreatedTime().equals(that.getCreatedTime()) : that.getCreatedTime() != null)
return false;
if (getModifiedUser() != null ? !getModifiedUser().equals(that.getModifiedUser()) : that.getModifiedUser() != null)
return false;
return getModifiedTime() != null ? getModifiedTime().equals(that.getModifiedTime()) : that.getModifiedTime() == null;
}
@Override
public int hashCode() {
int result = getCreatedUser() != null ? getCreatedUser().hashCode() : 0;
result = 31 * result + (getCreatedTime() != null ? getCreatedTime().hashCode() : 0);
result = 31 * result + (getModifiedUser() != null ? getModifiedUser().hashCode() : 0);
result = 31 * result + (getModifiedTime() != null ? getModifiedTime().hashCode() : 0);
return result;
}
@Override
public String toString() {
return "BaseEntity{" +
"createdUser='" + createdUser + '\'' +
", createdTime=" + createdTime +
", modifiedUser='" + modifiedUser + '\'' +
", modifiedTime='" + modifiedTime + '\'' +
'}';
}
}
tips:将来所有的表都会有以下四个字段
created_user VARCHAR(20) COMMENT ‘创建人’,
created_time DATETIME COMMENT ‘创建时间’,
modified_user VARCHAR(20) COMMENT ‘修改人’,
modified_time DATETIME COMMENT ‘修改时间’,
所以为了开发方便可以把这四个字段作为整个实体类
- BaseEntity基类
import java.io.Serializable;
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; //性别 0-女 1-男
private String avatar; // 头像
private Integer isDelete; //用户数据是否被删除
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public Integer getIsDelete() {
return isDelete;
}
public void setIsDelete(Integer isDelete) {
this.isDelete = isDelete;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
if (!super.equals(o)) return false;
User user = (User) o;
if (getUid() != null ? !getUid().equals(user.getUid()) : user.getUid() != null) return false;
if (getUsername() != null ? !getUsername().equals(user.getUsername()) : user.getUsername() != null)
return false;
if (getPassword() != null ? !getPassword().equals(user.getPassword()) : user.getPassword() != null)
return false;
if (getSalt() != null ? !getSalt().equals(user.getSalt()) : user.getSalt() != null) return false;
if (getPhone() != null ? !getPhone().equals(user.getPhone()) : user.getPhone() != null) return false;
if (getEmail() != null ? !getEmail().equals(user.getEmail()) : user.getEmail() != null) return false;
if (getGender() != null ? !getGender().equals(user.getGender()) : user.getGender() != null) return false;
if (getAvatar() != null ? !getAvatar().equals(user.getAvatar()) : user.getAvatar() != null) return false;
return getIsDelete() != null ? getIsDelete().equals(user.getIsDelete()) : user.getIsDelete() == null;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (getUid() != null ? getUid().hashCode() : 0);
result = 31 * result + (getUsername() != null ? getUsername().hashCode() : 0);
result = 31 * result + (getPassword() != null ? getPassword().hashCode() : 0);
result = 31 * result + (getSalt() != null ? getSalt().hashCode() : 0);
result = 31 * result + (getPhone() != null ? getPhone().hashCode() : 0);
result = 31 * result + (getEmail() != null ? getEmail().hashCode() : 0);
result = 31 * result + (getGender() != null ? getGender().hashCode() : 0);
result = 31 * result + (getAvatar() != null ? getAvatar().hashCode() : 0);
result = 31 * result + (getIsDelete() != null ? getIsDelete().hashCode() : 0);
return result;
}
@Override
public String toString() {
return "User{" +
"uid=" + uid +
", username='" + username + '\'' +
", password='" + password + '\'' +
", salt='" + salt + '\'' +
", phone='" + phone + '\'' +
", email='" + email + '\'' +
", gender=" + gender +
", avatar='" + avatar + '\'' +
", isDelete=" + isDelete +
'}';
}
}
tips:
实体类BaseEntity中自动导入Getter and Setter方法,euqals()方法,hashCode()方法,toString方法,其中euqals()方法,hashCode()方法自动导入步骤:
- enter+insert
- euqals() and hashCode()
- Accept…和Use这两段话,并且选择Template为IntelliJ Default
- next到底
ssm框架开发项目的时候需要在实体类上面加@Component然后spring才能自动进行对象的创建维护,而springboot不再需要,因为springboot遵循的原则是约定大于配置,如果字段名称相同那就可以自动完成字段的初始化。
3. 注册-持久层
通过Mybatis来操作数据库。
3.1 规划需要执行的SQL语句
1.用户的注册功能,相当于在做数据的插入操作
insert into t_user (username,password) values (值列表)
2.在用户注册时首先要去查询当球按用户名是否存在,如果存在则不能进行注册,相当于一条查询语句。
select * from t_user where username=?
3.2 通过Mybatis来操作数据库
1.定义Mapper接口。在项目的目录结构下首先创建一个mapper包,在这个包下根据不同的功能模块来创建mapper接口。创建一个UserMapper接
//用户模块持久层接口
public interface UserMapper {
/**
* 插入用户数据
* @param user 用户数据
* @return 受影响的行数(增删改 都受影响的行数作为返回值,可以根据返回值来判断是否执行成功)
*/
Integer integer(User user);
/**
* 根据用户名来查询用户的数据
* @param username 用户名
* @return 如果找到对应的用户则返回这个用户的数据,如果没有找到则返回null值
*/
User findByUsername(String username);
}
2.在启动类配置mapper接口文件的位置
//MapperScan注解指定当前项目中Mapper接口路径的位置,在项目启动的时候会自动加载所有接口
@MapperScan("com.wzh.store.mapper")
3.3 编写映射
1.定义xml映射文件,与对应接口进行关联。所有的映射文件需要放置在resource目录结构下,一般在这个目录下创建mapper文件夹,在这个文件夹下存放mapper映射文件。
2.创建接口对应的映射文件,遵循和接口的名称保持一致即可。创建一个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.wzh.store.mapper.UserMapper">
</mapper>
3.配置接口中的方法对应上SQL语句,需要借助标签来完成 insert、delete、update、select,对应的是SQL语句中的增删改查。
- into () values (),因为values后面插入的值是动态值,mybatis规定需要用占位符来占位,并给占位符起一个变量的名字,且变量的名字需要在占位符#{}内部
- t_user表时 uid INT AUTO_INCREMENT COMMENT ‘用户id’中的AUTO_INCREMENT表示主键uid自增,所以需要useGeneratedKeys和keyProperty
<mapper namespace="com.wzh.store.mapper.UserMapper">
<!--
id属性:表示给这个映射规则分配一个唯一的id值,对应的就是resultMap="id属性值"
type属性:取值是一个类,表示数据库中的查询结果与java中哪个实体类进行结果集的映射
-->
<resultMap id="UserEntityMap" type="com.wzh.store.entity.User">
<!--
将标的资源和类的属性不一致的字段进行匹配指定,名称一致的字段可以省略不写
无论何时主键都不可以省略!!!!!!!!!
-->
<id column="uid" property="uid"/>
<result column="is_delete" property="isDelete"/>
<result column="created_user" property="createdUser"/>
<result column="modified_user" property="modifiedUser"/>
<result column="modified_time" property="modifiedTime"/>
<result column="created_time" property="createdTime"/>
</resultMap>
<!--
id: 表示映射的接口中方法的名称
useGeneratedKeys属性: 表示开启某个字段的值递增
keyProperty属性: 标签将表中的哪个字段作为主键进行递增
-->
<insert id="insert" useGeneratedKeys="true" keyProperty="uid">
INSERT INTO t_user (uid,
username,
password,
salt,
phone,
email,
gender,
avatar,
is_delete,
created_user,
created_time,
modified_user,
modified_time)
VALUES (#{uid},
#{username},
#{password},
#{salt},
#{phone},
#{email},
#{gender},
#{avatar},
#{is_delete},
#{created_user},
#{created_time},
#{modified_user},
#{modified_time}
)
</insert>
<!--
resultType: 表示查询的结果集类型,只需要指定对应映射类的类型,并且包含完整包接口
resultMap: 表示当表的资源和类的对象属性的字段名称不一致时,来定义查询结果集的映射规则
-->
<select id="findByUsername" resultMap="UserEntityMap">
SELECT * FROM t_user WHERE username = #{username}
</select>
</mapper>
3.4.单元测试
1. 每个独立的层编写完毕后都需要编写单元测试方法,来测试当前的功能。
2. 在test包结构下创建一个mapper包,在这个包下再创建持久层的功能测试,单元测试方法是独立运行,不用启动整个项目,提高了代码的测试效率
3. 因为测试方法要用到mapper层的接口来访问刚刚写的两个方法,所以要在类里面声明UserMapper对象:即private UserMapper userMapper;且需要加上@Autowired完成值的初始化,但此时会发现提示"Could not autowire.No beans of’UserMapper’type found",报错原因是idea有自动检测的功能,在java中接口是不能够直接创建bean的,所以idea认为这个语法不合理,但本质上在项目启动时mybatis自动创建了接口的动态代理实现类,所以从项目的运行角度讲这不能算是错.解决办法:
- Settings里面搜索inspections,依次找到Spring->Spring Core->Code->Autowiring for Bean Class然后将Severity的Error改为Warning
关于第三条,建议直接使用@Resource
4. 在application.properties文件中增添:
mybatis.mapper-locations=classpath:mapper/*.xml
4. 注册-业务层
4.1规划异常
1. 作为RuntimeException异常的子类,然后再去定义具体的异常类型来继承这个异常。创建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);
}
}
2. 用户在进行注册的时候可能会产生用户名全被占用的错误,抛出一个异常: UsernameDuplicateException异常
public class UsernameDuplicateException extends ServiceException{
public UsernameDuplicateException() {
super();
}
public UsernameDuplicateException(String message) {
super(message);
}
public UsernameDuplicateException(String message, Throwable cause) {
super(message, cause);
}
public UsernameDuplicateException(Throwable cause) {
super(cause);
}
protected UsernameDuplicateException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
3.正在执行数据插入操作的时候,服务器、数据库宕机。处于正在执行插入的过程中所产生的异常InsertException异常。
//数据在插入过程中产生的异常
public class InsertException extends ServiceException{
public InsertException() {
super();
}
public InsertException(String message) {
super(message);
}
public InsertException(String message, Throwable cause) {
super(message, cause);
}
public InsertException(Throwable cause) {
super(cause);
}
protected InsertException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
4.2设计接口和抽象方法
1. 在service包下创建一个IUserService接口
import com.wzh.store.entity.User;
public interface IUserService {
/**
* 用户注册方法
* @param user 用户的数据对象
*/
void reg(User user);
}
2. 创建实现类UserServiceImpl 来实现接口和抽象方法。
package com.wzh.store.service.impl;
import com.wzh.store.entity.User;
import com.wzh.store.mapper.UserMapper;
import com.wzh.store.service.IUserService;
import com.wzh.store.service.ex.InsertException;
import com.wzh.store.service.ex.UsernameDuplicateException;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Locale;
import java.util.UUID;
@Service //将当前类的对象交给Spring来管理,自动创建对象以及对象的维护
public class UserServiceImpl implements IUserService {
//调用mapper层方法
@Resource
private UserMapper userMapper;
@Override
public void reg(User user) {
//通过user参数来获取传递过来的username
String username = user.getUsername();
//调用mapper的findByUsername方法(username)判断用户是否被注册过
User result = userMapper.findByUsername(username);
//判断结果集是否为null
if(result != null){
//抛出异常
throw new UsernameDuplicateException("用户名被占用");
}
//密码加密处理
//(串 + password + 串) ------ md5算法加密 连续加载三次
//串也叫盐值 盐值为随机的字符串
String oldPassword = user.getPassword();
//获取盐值(随机生成盐值) 盐值需要记录
String salt = UUID.randomUUID().toString().toUpperCase(Locale.ROOT);
user.setSalt(salt);
//将密码和盐值作为一个整体进行加密处理
String md5Password = getMD5Password(oldPassword,salt);
//将加密之后的密码重新补全设置到user对象中
user.setPassword(md5Password);
//补全数据 is_delete 设置成0
user.setIsDelete(0);
//补全数据 4个日志字段信息
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("用户注册过程中产生未知异常");
}
}
//定义一个md5算法加密处理
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. 在单元测试包下创建UserServiceTests类,在这个类中实现单元测试功能。
package com.wzh.store.service;
import com.wzh.store.entity.User;
import com.wzh.store.mapper.UserMapper;
import com.wzh.store.service.ex.ServiceException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@SpringBootTest
/**
* 1.@RunWith表示启动这个单元测试类,否则这个单元测试类是不能运行的,需要传递
* 一个参数,该参数必须是SpringRunner.class即SpringRunner的实例类型
* 2.敲完@RunWith(SpringRunner.class)后鼠标分别放在SpringRunner和@RunWith上按alt+enter分别导入包
* 3.单元测试类中出现的方法必须是单元测试方法
* 4.单元测试方法的特点: 1.必须被@Test注解修饰;
* 2. 返回值类型必须是void;
* 3. 方法的参数列表不指定任何类型;
* 4. 方法的访问修饰符必须是public
*/
@RunWith(SpringRunner.class)
public class UserServiceTests {
@Resource
private IUserService userService;
@Test
public void reg(){
try {
User user = new User();
user.setUsername("wzh02");
user.setPassword("123");
userService.reg(user);
System.out.println("OK");
} catch (ServiceException e) {
System.out.println(e.getClass().getSimpleName());
System.out.println(e.getMessage());
}
}
}
5. 注册-控制层
5.1创建响应
状态码、状态描述信息、数据。这部分功能封装在一个类中,将这类作为方法返回值返回给前端浏览器。
package com.wzh.store.util;
import java.io.Serializable;
/*
* Json格式的数据进行响应
*/
public class JsonResult<E> implements Serializable {
//状态码
private Integer state;
//描述信息
private String message;
//数据
private E data;
public JsonResult() {
}
public JsonResult(Integer state) {
this.state = state;
}
public JsonResult(Throwable e) {
this.message = e.getMessage();
}
public JsonResult(Integer state, E data) {
this.state = state;
this.data = data;
}
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public E getData() {
return data;
}
public void setData(E data) {
this.data = data;
}
}
5.2 设计请求
依据当前的业务功能模块进行请求的设计。
请求路径:/user/reg
请求参数:User user
请求类型:POST
响应结果:JsonResult<void>
5.3 处理请求
创建一个控制层对应的类UserController类,依赖于业务层接口。
package com.wzh.store.controller;
import com.wzh.store.entity.User;
import com.wzh.store.service.IUserService;
import com.wzh.store.service.ex.InsertException;
import com.wzh.store.service.ex.UsernameDuplicateException;
import com.wzh.store.util.JsonResult;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController //@ResponseBody + @Controller
@RequestMapping("users")
public class UserController {
@Resource
private IUserService userService;
@RequestMapping("reg")
public JsonResult<Void> reg(User user){
//创建响应结果对象
JsonResult<Void> result = new JsonResult<>();
try {
userService.reg(user);
result.setState(200);
result.setMessage("注册成功");
} catch (UsernameDuplicateException e) {
result.setState(4000);
result.setMessage("用户名被占用");
} catch (InsertException e) {
result.setState(5000);
result.setMessage("注册时产生未知异常");
}
return result;
}
}
5.4 控制层优化设计
在控制层抽离出来一个父类,在这个父类中统一处理关于异常的相关操作。编写一个BaseController的类,同一处理异常:
package com.wzh.store.controller;
import com.wzh.store.entity.User;
import com.wzh.store.service.ex.InsertException;
import com.wzh.store.service.ex.ServiceException;
import com.wzh.store.service.ex.UsernameDuplicateException;
import com.wzh.store.util.JsonResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
//控制层类的基类 主要做异常的捕获处理
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 UsernameDuplicateException){
result.setState(4000);
result.setMessage("用户名被占用");
}else if(e instanceof InsertException){
result.setState(5000);
result.setMessage("注册时产生未知错误");
}
return result;
}
}
UserController类改动后:
package com.wzh.store.controller;
import com.wzh.store.entity.User;
import com.wzh.store.service.IUserService;
import com.wzh.store.service.ex.InsertException;
import com.wzh.store.service.ex.UsernameDuplicateException;
import com.wzh.store.util.JsonResult;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController //@ResponseBody + @Controller
@RequestMapping("users")
public class UserController extends BaseController{
@Resource
private IUserService userService;
@RequestMapping("reg")
public JsonResult<Void> reg(User user){
userService.reg(user);
return new JsonResult<>(OK);
}
// @RequestMapping("reg")
// public JsonResult<Void> reg(User user){
// //创建响应结果对象
// JsonResult<Void> result = new JsonResult<>();
// try {
// userService.reg(user);
// result.setState(200);
// result.setMessage("注册成功");
// } catch (UsernameDuplicateException e) {
// result.setState(4000);
// result.setMessage("用户名被占用");
// } catch (InsertException e) {
// result.setState(5000);
// result.setMessage("注册时产生未知异常");
// }
// return result;
// }
}
6. 注册-前端页面
1. 在register页面中编写发送请求的方法,通过点击事件来完成。先选中对应的按钮($("选择器")),再去添加点击的事件,$.ajax()函数发送异步请求。
2. JQuery封装了一个函数,称之为$.ajax()函数,通过对象调用ajax()函数可以异步加载相关的请求。依靠的是Javascript提供的一个对象XHR(XmlHttpResponse),封装了这个对象。
3. ajax()使用方式: 需要穿第一个方法体作为方法的参数来使用,一堆大括号称之为方法体。语法结构:
$.ajax({
url: "",
type: "",
data: "",
dataType: "",
success: function() {
},
error: function() {
}
});
4. ajax()函数参数的含义
5.js代码可以独立存放在后缀为.js的文件里或者声明在一个script标签中。
一般放在页脚结束和</body>之间。
<script type="text/javascript">
//1. 监听注册按钮是否被点击
$("#btn-reg").click(function () {
console.log($("#form-reg").serialize());
//2. 发送ajax()的异步请求来完成用户的注册功能
$.ajax({
url: "/users/reg",
type: "POST",
data: $("#form-reg").serialize(),
//dataType: "username=" + username + "&password=" + password,
dataType: "JSON",
success: function (json){
if(json.state == 200){
alert("注册成功")
}else{
alert("注册失败")
}
},
error: function (xhr){
alert("注册时产生未知错误" + xhr.status)
}
});
});
</script>