静态资源及sql文件分享
链接:https://pan.baidu.com/s/1jWSRPf19GGK1_-z_sgfzTQ?pwd=bdgy
提取码:bdgy
- 项目名称:store,表示商城
- 结构:com.cy.store
- 资源文件:resources文件夹下(static,templates)
- 单元测试:test.com.cy.store
配置文件
application.yml
server:port: 8080
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/store?characterEncoding=utf-8&serverTimezone=Asia/Shanghaiusername: rootpassword: root
mybatis:mapper-locations: classpath*:mapper/*.xml
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.cy</groupId><artifactId>store</artifactId><version>0.0.1-SNAPSHOT</version><name>store</name><description>store</description><properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-boot.version>2.6.13</spring-boot.version></properties><dependencies><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.2.2</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><configuration><mainClass>com.cy.store.StoreApplication</mainClass><skip>true</skip></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build>
</project>
用户注册功能
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;
将来任何一张表都有以下四个字段:
created_user VARCHAR(20) COMMENT ‘创建人’,
created_time DATETIME COMMENT ‘创建时间’,
modified_user VARCHAR(20) COMMENT ‘修改人’,
modified_time DATETIME COMMENT ‘修改时间’,
所以为了开发方便可以把这四个字段作为整个实体类
2.创建用户的实体类
1.通过表的结构提取出表的公共字段,放在一个实体类的基类中,起名BaseEntity基类中
package com.cy.store.entity;import java.io.Serializable;
import java.util.Date;
import java.util.Objects;/*** @author LiuHe* @version 1.0* @ClassName BaseEntity* @date 2024/5/18 22:19*/
//作为实体类的基类
public class BaseEntity implements Serializable {private String createdUser;private Date createdTime;private String modifiedUser;private Date 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 Date getModifiedTime() {return modifiedTime;}public void setModifiedTime(Date modifiedTime) {this.modifiedTime = modifiedTime;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;BaseEntity that = (BaseEntity) o;return Objects.equals(createdUser, that.createdUser) && Objects.equals(createdTime, that.createdTime) && Objects.equals(modifiedUser, that.modifiedUser) && Objects.equals(modifiedTime, that.modifiedTime);}@Overridepublic int hashCode() {return Objects.hash(createdUser, createdTime, modifiedUser, modifiedTime);}@Overridepublic String toString() {return "BaseEntity{" +"createdUser='" + createdUser + '\'' +", createdTime=" + createdTime +", modifiedUser='" + modifiedUser + '\'' +", modifiedTime=" + modifiedTime +'}';}
}
2.创建用户的实体类,并使其继承BaseEntity基类
package com.cy.store.entity;import java.io.Serializable;
import java.util.Objects;/*** @author LiuHe* @version 1.0* @ClassName User* @date 2024/5/18 22:26*/
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;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;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;if (!super.equals(o)) return false;User user = (User) o;return Objects.equals(uid, user.uid) && Objects.equals(username, user.username) && Objects.equals(password, user.password) && Objects.equals(salt, user.salt) && Objects.equals(phone, user.phone) && Objects.equals(email, user.email) && Objects.equals(gender, user.gender) && Objects.equals(avatar, user.avatar) && Objects.equals(isDelete, user.isDelete);}@Overridepublic int hashCode() {return Objects.hash(super.hashCode(), uid, username, password, salt, phone, email, gender, avatar, isDelete);}@Overridepublic String toString() {return "User{" +"uid=" + uid +", username='" + username + '\'' +", password='" + password + '\'' +", salt='" + salt + '\'' +", phone='" + phone + '\'' +", email='" + email + '\'' +", gender=" + gender +", avatar='" + avatar + '\'' +", isDelete=" + isDelete +'}' + super.toString();}
}
3.注册-持久层
通过Mybatis来操作数据库,也就是在做mybatis开发的流程
3.1规划需要执行的SQL语句
1.用户的注册功能,从后端持久层来看相当于在做数据的插入操作
inser into t_user (username)
2.在用户的注册时首先要去查询当前的用户名是否存在,如果存在则不能进行注册,相当于是一条查询语句
select * from t_user where username=?
3.2设计接口和抽象方法及实现
1.定义Mapper接口.在项目的目录结构下首先创建一个mapper包,在这个包下再根据不同的功能模块来创建mapper接口.注册功能需要在mapper包下创建UserMapper接口然后定义上述两个SQL语句的抽象方法
public interface UserMapper {/** @description: 插入用户的数据* @author: LiuHe* @date: 2024/5/19 13:42* @param: [user]用户的数据* @return: 受影响的行数(增删改)**/Integer insert(User user);/** @description: 根据用户名查询用户数据* @author: LiuHe* @date: 2024/5/19 13:44* @param: [username]* @return: 找到用户名返回用户数据,否则返回null**/User findByUsername(String username);
}
2.ssm框架开发项目的时候需要在mapper接口上加@Mapper用于自动生成相应的接口实现类,在springboot也可以这样,但是后续会有很多mapper接口,每个接口分别加@Mapper太麻烦了,所以在启动类类里面指定当前项目的mapper接口在哪,然后项目启动的时候会自动加载所有的接口
package com.cy.store;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan("com.cy.store.mapper")
public class StoreApplication {public static void main(String[] args) {SpringApplication.run(StoreApplication.class, args);}
}
3.3编写映射
1.定义xml映射文件,与对应的接口进行关联.所有的映射文件都属于资源文件,需要放在resources目录下,为了管理方便我们在resources目录下创建一个mapper文件夹,然后在这个文件夹里存放mapper的映射文件
2.创建接口的映射文件,需要和接口的名称保持一致.如UserMapper.xml
UserMapper.xml的配置在Mybatis官网
3.将配置接口的方法对应到SQL语句上
insert into () values (),因为values后面插入的值是动态值,mybatis规定需要用占位符来占位,并给占位符起一个变量的名字,且变量的名字需要在占位符#{}内部
创建t_user表时uid INT AUTO_INCREMENT COMMENT ‘用户id’,中的AUTO_INCREMENT表示主键uid自增,所以需要useGeneratedKeys和keyProperty
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.cy.store.mapper.UserMapper"><!-- 自定义映射规则 --><resultMap id="UserEntityMap" type="com.cy.store.entity.User"><!-- 将表的资源和类的属性不一致的字段进行匹配指定,名称一致的字段可以省略不写--><!-- 配合完成名称不一致的映射:--><!-- column属性:表示表中的资源名称--><!-- property属性:表示类中的属性名称--><!-- 在定义映射规则时主键是不可以省略的--><id column="uid" property="uid"/><result column="is_delete" property="isDelete"/><result column="created_user" property="createdUser"/><result column="created_time" property="createdTime"/><result column="modified_user" property="modifiedUser"/><result column="modified_time" property="modifiedTime"/></resultMap><!-- Integer insert(User user);--><!-- 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><!-- User findByUsername(String username);--><!-- resultType:表示查询的结果集类型,只需要指定对应映射类的类型,并且包含完整包接口--><!-- resultMap:标签当表的资源和类的对象属性的字段名称不一致时,来自定义查询结果集的映射规则 --><select id="findByUsername" resultMap="UserEntityMap">select *from t_userwhere username = #{username}</select></mapper>
4.注册-业务层
业务层的核心功能:
- 接受前端从控制器流转过来的数据
- 结合真实的注册业务来完成功能业务逻辑的调转和流程
service下的目录结构(建议这样):
- service包下创建ex包用来写异常类
- service包下创建impl包用来写接口的实现类
- 接口直接写在service包下,不再需要接口包
4.1规划异常
1.为什么会有异常:
比如,用户在进行注册时可能会产生用户名被占用的错误,这时需要抛出一个异常
2.怎么处理异常:
异常不能用RuntimeException,太笼统了,开发者没办法第一时间定位到具体的错误类型上,我们可以定义具体的异常类型来继承这个异常.
正常的开发中异常又要分等级,可能是在业务层产生异常,可能是在控制层产生异常,所以可以创建一个业务层异常的基类,起名ServiceException异常,并使其继承RuntimeException异常
后期开发业务层时具体的异常可以再继承业务层的异常ServiceException
3.处理异常的具体步骤:
步骤一:在ex包下创建ServiceException类作为业务层异常的基类:
package com.cy.store.service.ex;/*** @author LiuHe* @version 1.0* @ClassName ServiceException* @date 2024/5/20 16:37*/
//业务层异常的基类
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异常基类:
- 用户在进行注册时可能会产生用户名被占用的错误,这时需要抛出一个UsernameDuplicatedException异常
package com.cy.store.service.ex;/*** @author LiuHe* @version 1.0* @ClassName UsernameDuplicatedException* @date 2024/5/20 16:44*/
//用户名被占用
public class UsernameDuplicatedException extends ServiceException{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);}
}
- 正在执行数据插入操作的时候,服务器宕机或数据库宕机.这种情况是处于正在执行插入的过程中所产生的异常,起名InsertException异常
package com.cy.store.service.ex;/*** @author LiuHe* @version 1.0* @ClassName InsertException* @date 2024/5/20 16:47*/
//数据在插入过程中产生的异常
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接口(接口命名的默认规则:I+业务名字+层的名字)
public interface IUserService {/** @description: 用户注册方法* @author: LiuHe* @date: 2024/5/20 16:54* @param: [user] 用户的数据对象* @return: void**/void reg(User user);
}
2.创建一个实现UserServiceImpl类,需要实现IUserService接口,并且实现抽象的方法
因为要将这个实现类交给spring管理,所以需要在类上加@Service
package com.cy.store.service.impl;import com.cy.store.entity.User;
import com.cy.store.mapper.UserMapper;
import com.cy.store.service.IUserService;
import com.cy.store.service.ex.InsertException;
import com.cy.store.service.ex.PasswordNotMatchException;
import com.cy.store.service.ex.UsernameDuplicatedException;
import com.cy.store.service.ex.UsernameNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;import java.util.Date;
import java.util.UUID;/*** @author LiuHe* @version 1.0* @ClassName UserServiceImpl* @date 2024/5/20 16:54*/
//用户模块业务层的实现类
@Service
public class UserServiceImpl implements IUserService {@Autowiredpublic UserMapper userMapper;/** @description: 注册功能* @author: LiuHe* @date: 2024/5/22 20:06* @param: [user]* @return: void**/@Overridepublic void reg(User user) {//通过user参数来获取传递过来的usernameString username = user.getUsername();//调用findByUsername(username)判断用户是否被注册过User result = userMapper.findByUsername(username);//判断结果集是否不为null则抛出用户名被占用的异常if (result != null) {//抛出异常throw new UsernameDuplicatedException("用户名被占用");}// 密码加密处理的实现:md5算法的形式:67dhdsgh-yeuwrey121-yerui374-yrwirei-67123// (串 + password + 串)---- md5算法进行加密,连续加载三次// 盐值 + password + 盐值 ---- 盐值就是一个随机的字符串String oldPassword = user.getPassword();// 获取盐值(随机生成一个盐值)String salt = UUID.randomUUID().toString().toUpperCase();//补全数据:盐值的记录user.setSalt(salt);//将密码和盐值作为一个整体进行加密处理,忽略原有密码强度提升了数据的安全性String md5Password = getMD5Password(oldPassword, salt);//将加密之后的密码重新补全设置到user对象中user.setPassword(md5Password);//补全数据:is_delete=0user.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("在用户注册过程中产生的未知异常");}}/** @description: md5算法的加密处理* @author: LiuHe* @date: 2024/5/22 19:46* @param: [password, salt]* @return: java.lang.String**/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;}
}
5.注册-控制层
5.1创建响应
状态码,状态描述信息,数据是所有控制层对应的方法都涉及到的操作,所以把这部分功能封装到一个类JsonResult中,将这个类作为方法的返回值返回给前端浏览器:
package com.cy.store.util;import org.springframework.stereotype.Component;import java.io.Serializable;/*** @author LiuHe* @version 1.0* @ClassName JsonResult* @date 2024/5/22 20:09*/
//Json格式数据进行响应
@Component
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设计请求
接下来该向后端服务器发送请求以把用户数据插入到数据库,设计发送请求模块的第一步就是设计相关的请求
依据当前的业务功能模块进行请求的设计:
- 请求路径:/users/reg
- 请求参数:User user
- 请求类型:POST
- 响应结果:JsonResult<void>
5.3处理请求
在控制层抽离出一个BaseController父类,在这个父类中统一处理关于异常的相关操作
1.在controller包下创建UserController类作为控制层下类的基类,用来做统一的异常捕获:
package com.cy.store.controller;import com.cy.store.service.ex.*;
import com.cy.store.util.JsonResult;
import org.springframework.web.bind.annotation.ExceptionHandler;import javax.servlet.http.HttpSession;/*** @author LiuHe* @version 1.0* @ClassName BaseController* @date 2024/5/23 8:43*/
//控制层类的基类
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;}}
2.让UserController继承BaseController并重构UserController下的reg方法使该方法只需要关注请求处理而不再需要关注异常捕获:
@RestController
@RequestMapping("/users")
public class UserController extends BaseController {@Autowiredprivate IUserService userService;/** @description: 注册* @author: LiuHe* @date: 2024/5/23 9:04* @param: [user]* @return: com.cy.store.util.JsonResult<java.lang.Void>**/@RequestMapping("/reg")public JsonResult<Void> reg(User user) {userService.reg(user);return new JsonResult<>(OK);}
}
6.注册-前端页面
6.1熟悉ajax
1.什么是ajax函数?
这是jQuery封装的一个函数,称为$.ajax()函数,通过对象调用ajax()函数用来异步加载相关的请求.依靠的是JavaScript提供的一个对象:XHR(全称XmlHttpResponse)
2.ajax()函数的语法结构:
使用ajax()时需要传递一个方法体作为方法的参数来使用(一对大括号就是一个方法体)
ajax接受多个参数时,参数与参数之间使用","分割
每一组参数之间使用":"进行分割
参数的组成部分一个是参数的名称(不能随便定义),另一个是参数的值(必须用字符串来表示)
参数的声明顺序没有要求
$.ajax({url: "",type: "",data: "",dataType: "",success: function() {},error: function() {}
});
6.2前端js编写
<script type="text/javascript">//1.监听注册按钮是否被点击,如果被点击可以执行一个方法$("#btn-reg").click(function () {//2.发送ajax()的异步请求来完成用户的注册功能$.ajax({url: "http://localhost:8080/users/reg",type: "post",data: $("#form-reg").serialize(),dataType: "json",success: function (json) {if (json.state === 200) {alert("注册成功");}else {alert("注册失败");}},error: function (xhr) {alert("注册时产生未知的错误"+xhr.status);}});});
</script>