目录
注册
3.1规划需要执行的SQL语句
3.2设计接口和抽象方法
3.3编写映射
4.注册-业务层
4.1规划异常
4.2设计接口和抽象方法
5.注册控制层
5.1创建响应
5.2设计请求
5.3处理请求
5.4控制层优化设计
6.注册-前端页面
持久层:通过Mybatis来操作数据库,在做mybatis开发流程
用户注册的功能,相当于在做数据的插入操作
insert into t_user (username,password) values(值列表)
用户注册时首先要去查询当前的用户名是否存在,如果存在则不能执行注册。相当于是一条查询语句
select * from t_user where username=?
1.编写Mapper接口,在项目的目录下首先创建一个mapper文件,在这个包下再根据不同功能模块来创建mapper接口,创建一个usermapper接口,要在接口定义这两个SQL语句抽象方法
/*用户模块的接口*/ //@Mapper public interface UserMapper { /* * *插入用户的数据 * 用户的数据 * 返回受影响的行数(增删改查)根据返回值返回是否执行成功*/ Integer insert(User user); /* * 根据用户名来查询用户数据 * 用户名 * 如果对应的用户返回这个用户的数据如果没有找到则返回null值*/ User FindByname(String username); }
2.在启动类配置Mapper接口文件上
@MapperScan(“com.liyang.store.mapper”)
1.定义XML文件,与对应的接口定义进行关联,所有的映射文件需要放置resource目录下,在这个目录下创建一个mapper文件夹,然后在这个文件夹存放Mapper的映射文件
2.创建接口对应的映射文件,遵循和接口的名称保持一致即可,创建一个UserMapper.xml文件
3.配置接口中的方法对应上SQL文件语句上,需要借助标签来完成,insert\updata\delete\select对应的SQL语句操作
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})
3.将mapper文件的位置注册到properties对应的配置文件中
mybatis.mapper-locations=classpath:mapper/*.xml
3.单元测试,每个独立的层编写完成后需要编写单元测试方法,来测试当前的功能。在test包下结构下
@SpringBootTest//表示标注当前的类是一个测试类,不会随同项目一块打包
@RunWith(SpringRunner.class)//表示启动这个单元测试类,需要传递一个参数必须是SpringRunner的实例类型
public class UserMapperTests {
//idea有检测的功能,接口是不能狗直接创建bean的(动态代理技术来解决)
@Autowired
private UserMapper userMapper;
/*单元测试方法:就可以单独独立运行,不用启动整个项目,可以做单元测试,提升了测试效率
*1.必须被Test注解修饰,
* 2.返回类型必须是void
* 3.方法的参数列表不指定任何类型
* 4.方法返回修饰符必须是public
* */
@Test
public void insert(){
User user = new User();
user.setUsername("tim");
user.setPassword("1234");
Integer insert = userMapper.insert(user);
System.out.println(insert);
}
@Test
public void findByUsername(){
User tim = userMapper.findByUsername("tim");
System.out.println(tim);
}
}
1RuntimeException异常。作为异常的子类,然后再去执行定义具体的异常类型来继承这个异常 ,业务层异常基类,ServiceException,这个异常继承RuntimeException。
/*业务层异常的基类: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.用户在进行注册时候可能会产生用户名被占用的错误,抛出一个异常:UsernamDepuiltedException异常
package com.liyang.store.service.ex;
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);
}
}
3.正在执行数据插入操作的时候,服务器,数据库宕机执行插入的过程中所产生的异常insertException
package com.liyang.store.service.ex;
/*数据在插入过程中产生的异常*/
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);
}
}
在service包下创建一个IUserService接口
package com.liyang.store.service;
import com.liyang.store.entity.User;
/*用户模块用户接口*/
public interface IUserService {
/*
* 用户注册方法
* */
void reg(User user);
}
2.创建一个实现类UserServiceImpl类需要实现这个接口,并且实现抽象的方法
package com.liyang.store.service.impl;
import com.liyang.store.entity.User;
import com.liyang.store.mapper.UserMapper;
import com.liyang.store.service.IUserService;
import com.liyang.store.service.ex.InsertException;
import com.liyang.store.service.ex.UsernameDuplicatedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import javax.xml.crypto.Data;
import java.util.Date;
import java.util.UUID;
/*用户模块业务层的实现类*/
@Service //@Service注解:将当前类的对象交给Spring来管理,自动创建对象以及对象的维护
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper;
@Override
public void reg(User user) {
//通过user参数来获取传递过来的参数
String username = user.getUsername();
//调用findByuserName(username) 判断用户是否被注册过
User result = userMapper.findByUsername(username);
//判断结果集是否为null,抛出用户名被占用的异常
if(result!=null){
throw new UsernameDuplicatedException("用户名被占用");
}
//密码加密处理的实现:md5
String oldpassword = user.getPassword();
//获取颜值 (随机生成一个颜值)
String salt = UUID.randomUUID().toString().toUpperCase();
user.setSalt(salt);//补全颜值的数据
//密码和颜值作为一个整体进行加密处理 忽略了原有密码的强度提升了数据的安全性
String md5Password = getMD5Password(oldpassword, salt);
//将加密之后的密码重新补全到user对象中
user.setPassword(md5Password);
//补全数据:is_delete设置为零
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){
for (int i = 0; i < 3; i++) {
//md5加密算法方法的调用
password=DigestUtils.md5DigestAsHex((salt+password+salt).getBytes()).toUpperCase();
}
//返回加密之后的密码
return password;
}
}
3.在单元测试包创建一个UserServiceTests类,在这个类添加单元测试类的功能
package com.liyang.store.service;
import com.liyang.store.entity.User;
import com.liyang.store.mapper.UserMapper;
import com.liyang.store.service.ex.ServiceException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.sql.rowset.serial.SerialException;
@SpringBootTest//表示标注当前的类是一个测试类,不会随同项目一块打包
@RunWith(SpringRunner.class)//表示启动这个单元测试类,需要传递一个参数必须是SpringRunner的实例类型
public class UserServiceTests {
//idea有检测的功能,接口是不能狗直接创建bean的(动态代理技术来解决)
@Autowired
private IUserService userService;
/*单元测试方法:就可以单独独立运行,不用启动整个项目,可以做单元测试,提升了测试效率
*1.必须被Test注解修饰,
* 2.返回类型必须是void
* 3.方法的参数列表不指定任何类型
* 4.方法返回修饰符必须是public
* */
@Test
public void reg(){
try {
User user = new User();
user.setUsername("liyang02");
user.setPassword("1234");
userService.reg(user);
System.out.println("ok");
} catch (ServiceException e) {
//获取类的对象,获取类的名称
System.out.println(e.getClass().getSimpleName());
//获得异常的具体描述信息
System.out.println(e.getMessage());
}
}
}
状态码,状态描述信息、数据。这部分功能封装一个类中,将这类作为方法返回值
package com.liyang.store.util;
import java.io.Serializable;
/*Json格式的数据进行响应。
* */
public class JsonResult implements Serializable {
private Integer state;//状态码
private String message;//描述信息
private E data;
public JsonResult() {
}
public JsonResult(Integer state) {
this.state = state;
}
public JsonResult(String message) {
this.message = message;
}
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;
}
}
依照当前的业务功能模块进行请求的设计
请求路径:/user/reg
请求参数:User user
请求类型:Post
响应结果:JsonResult
1.创建一个控制层对应的类UserController类,依赖于业务层的接口
package com.liyang.store.controller;
import com.liyang.store.entity.User;
import com.liyang.store.service.IUserService;
import com.liyang.store.service.ex.InsertException;
import com.liyang.store.service.ex.UsernameDuplicatedException;
import com.liyang.store.util.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController //@Controller + @ResponseBody
@RequestMapping("users")
public class UserController {
@Autowired
private IUserService userService;
public JsonResult reg(User user){
//创建响应结果对象
JsonResult result = new JsonResult<>();
try {
userService.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;
}
}
在控制层抽离出来一个父类,在这个父类中统一的去处理关于异常的相关操作。编写BaseController类,统一处理异常@ExceptionHandler注解实现了对服务层抛出的异常进行统一处理,提高了代码的健壮性和可维护性。
package com.liyang.store.controller;
import com.liyang.store.service.ex.InsertException;
import com.liyang.store.service.ex.ServiceException;
import com.liyang.store.service.ex.UsernameDuplicatedException;
import com.liyang.store.util.JsonResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
/*控制层的基类*/
public class BaseController {
public static final int OK=200;//操作成功状态码
//请求处理方法,这个方法的返回值就是需要传递给前端的数据
//自动将异常对象传递给此方法的参数列表上
//当前项目中产生了异常,被统一拦截到此方法中,这个 方法此时就充当的是请求处理的方法,方法的返回值直接给到前端
@ExceptionHandler(ServiceException.class) //用于统一处理抛出的异常
public JsonResult handleException(Throwable e){
JsonResult result=new JsonResult<>(e.getMessage());
if(e instanceof UsernameDuplicatedException){
result.setState(4000);
result.setMessage("用户名已被占用");
}else if(e instanceof InsertException){
result.setState(5000);
result.setMessage("注册时产生未知的异常");
}
return result;
}
}
localhost:8080/users/reg?username=liyangggee&password=123456测试端口地址
重新构造了reg()方法
@RequestMapping("reg")
public JsonResult reg(User user){
//创建响应结果对象
userService.reg(user);
return new JsonResult<>(OK);
}
1.在register页面中编写发送请求方法,点击事件完成。先选中对应的按钮($(“选择器”)),再去添加点击的事件,$ajax()发送异步请求
2.JQuery封装了一个函数,称之为$.ajax()函数,通过对象调用ajax()函数,可以异步加载相关请求,依靠的是javascript提供的一个XHR(XMLhttpResonse)
3.ajax()使用方式,需要传递一个方法作为的参数来使用,一对大括号称之为方法体。ajax接收多个参数语法结构,参数和参数之间要求使用 ,分割 每一组参数之间使用:进行语法分割 参数的组成部分是参数的名称(不能随便定义),是参数的值,参数的值要求是用字符串来标识。参数的声明顺序没有要求
语法结构:
$.ajax({
url:" ",
type:"",
data:"",
dataType:" ",
success:function(){
} ,
error:function(){
}
})
4.ajax()函数参数含义
参数 | 功能描述 |
url | 表示请求的地址:url:"local:8080/user/reg?" |
type | 请求类型(get和post请求类型)列如:type:"post" |
data | 向指定的请求url地址提交的数据,data:“username=tom&pwd=123” |
dataType | 提交的数据的类型,数据的类型一般指定为json类型。dataType:"json" |
success | 当服务器正常响应客户端时,会自动调用success参数的方法,并将服务器返回的数据以参数的形式传递给这个参数上 |
error | 当服务器正常响应客户端时,会自动调用error参数的方法,并将服务器返回的数据以参数的形式传递给这个参数上 |
5.js代码可以独立存放在一个js的文件里或者声明在一个script标签中。
6.js代码无法正常被服务器解析执行,体现在点击页面的中的按钮没有任何的响应。解决方案:
在项目的Maven下clear清理项目--install中
在项目的fille选项-cash清理缓存
重新去构建项目:build选项下rebuild选项
重启idea
当用户输入用户名和密码将数据提交给后台数据库进行查询,如果存在对应的用户名和密码则表示登录成功,登录成功之后跳转到主页就是index.html跳转在前端使用jquery来完成
1.1规划需要执行的sql语句
一句用户提交的用户名和密码做select查询。密码比较在业务层
select * from where username=? and password=?
说明:如果在分析过程中发现某个功能模块已经被开发完成,所以就可以省略当前的开发步骤
分析的过程不能省略。
1.2接口设计和方法
不用重复开发,单元测试也是无需单独执行了
2.1异常规划
用户名对应的密码错误,密码匹配失败的异常:PasswordNotMatchException运行时异常。
2.用户名没有被找到,抛出异常:UsernameNotException
3.异常编写:
业务层异常需要继承ServiceException异常类
在具体的异常类中定义构造方法(可以使用快捷键生成,有五个构造方法)。
2.2设计业务接口层接口和抽象方法
1.直接在IUservice接口中编写抽象方法 login(String username,String Password)。
将当前登录成功的用户数据以当前用户对象的形式进行返回,状态管理:可以将数据保存在Session中和Cookie中,可以避免重复度很高的数据多次频繁操作数据进行获取(用户名、用户id-存放在session中,用户头像存在cookie当中)
2.需要实现中实现父接口的抽象方法。
3.在测试类中测试业务层类中实现父接口中的抽象方法
4.如果一个类没有手动创建直接将这个类赋值到项目,idea找不到这个类。之前缓存导致不能够正常找到这类的符号。重新构建项目,
2.3抽象方法的实现
3.1处理异常
业务层抛出的异常是什么,需要在统一异常处理类中进行统一的捕获和处理,如果也曾抛出的异常类型已经在异常处理过,则不需要重复添加
else if(e instanceof UserNotFoundException){ result.setState(5001); result.setMessage("用户数据不存在的异常"); }else if(e instanceof PasswordNotMatchException){ result.setState(5002); result.setMessage("用户名的密码错误的异常"); }
3.2设计请求
请求路径:/user/login
请求方式:POST
请求数据:String username,Sring Password ,HttpSession session
响应结果:JsonResolut
3.3处理请求
在UserController类中编写处理请求的方法
@RequestMapping("login") public JsonResultlogin(String username,String password){ User data = userService.login(username, password); return new JsonResult (OK,data); }
1.在login.html页面中依据前面所设置的请求来发送ajax请求。
session对象主要存在服务器端,可以用来保存服务器的临时数据对象,所保存的数据可以在整个项目中都可以通过访问来获取,把session的数据看做是一个共享的数据。首次登录的时候所获取的用户的数据,转移到session对象即可。session.getAttrbute("key")可以将获取session中的数据这种行为进行封装,封装到BaseContrller类中。
1.封装session对象中数据的获取(封装到父类中)数据的设置(当用户登录成功进行数据的设置,设置到全局的session)
2.在父类中封装两个数据:获取uid和获取username对应的两个方法。用户头像暂时不考虑,将来封装cookie中
protected final Integer getuidFromSession(HttpSession session){
return Integer.valueOf(session.getAttribute("uid").toString());
}
/*
* 获取当前登录用户的username
* Session session
* 在实现类中重写父类中的toString,不是句柄信息的输出
* */
protected final String getUsernameFromSession(HttpSession session){
return session.getAttribute("username").toString();
}
}
3.在登录的方法中将数据封装在session对象中,服务本身自动创建有session对象,已经是一个全局的session对象。springboot直接使用session对象,直接将Http类型的对象作为请求处理方法,会自动将全局的session对象注入到请求处理方法的session形参上
拦截器:首先将所有的请求统一拦截到拦截器中,可以在拦截器中来定义过滤的规则,如果不满足系统的设置的过滤规则,统一的处理是重新去打开login.html页面(重定向和转发)推荐使用重定向。在SpringBoot项目中拦截器的定义和使用。springboot是依靠springMVC来完成的。SpringMVC提供了一个接口,HandlerIntercepter接口,用于表示定义一个拦截器,受限自定义一个拦截器,受限自定义各类,在这个类实现这个接口
1.首先自定义一个类,在这个类实现这个HandlerIntercepter接口
public interface HandlerInterceptor {
//在调用所有处理请求的方法之前被自动调用执行的方法
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
//请求之后login在ModelAndView对象返回之后调用的方法
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
//在整个请求所有关联的资源被执行完毕最后所执行的方法
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
2.注册过滤器:添加白名单(那些资源可以在不登录的情况下访问:login.html\register.html\login.html\index.html\product.html),添加黑名单(在用户登录的情况下才可以登录页面资源)。
3.注册过滤器的技术:借助WebMvcConfigure接口,可以将用户定义的拦截器进行注册,才可以保证拦截器能够生效和使用。定义一个类,然后让这个类实现WebMvcConfigure接口。配置信息,建议存放在项的config包架构下。
/*
* 处理器拦截器的注册 实现白名单和黑名单
* */
@Configuration //加载拦截器
public class LoginInterceptorConfigurer implements WebMvcConfigurer {
/*
* 配置拦截器
* */
@Override
public void addInterceptors(InterceptorRegistry registry) {
//创建自定义的拦截器对象
HandlerInterceptor interceptor=new LoginInterceptor();
// 白名单
List i=new ArrayList<>();
i.add("/bootStrap3/**");
i.add("/css/**");
i.add("images/**");
i.add("/js/**");
i.add("/web/register.html");
i.add("/web/login.html");
i.add("/web/index.html");
i.add("/web/product.html");
i.add("/users/reg");
i.add("/users/login.html");
//完成拦截器的注册
registry.addInterceptor(interceptor)
.addPathPatterns("/**")
.excludePathPatterns(i);//表示要拦截的url是什么内容
}
}
需要用户提交原始密码和新密码,在根据用户根据当前登录的用户进行信息修改操作。
1.1规划需要执行的执行的SQL语句 根据用户uid修改用户的password
updata t_user set password=?,modified=?,modified_time where uid=?
根据uid查询用户的数据,在修改密码之前,首先要保证当前用户的数据存在,检测标记为已经删除,检测输入的原始密码是否正确
select * from t_user where uid=?
1.2设计接口和抽象方法
UseMapper接口,将以上的两个方法抽象出来,将定义的映射到sql语句上。
1.3SQL映射 ,配置到映射文件中,配置到UserMapper.xml文件中。
update t_user set password=#{password},
modified_user=#{modifiedUser},
modified_Time=#{modifiedTime} where uid=${uid}
@Test
public void updataPasswordByUid(){
userMapper.updataPasswordByUid(78,"1234","管理员",new Date());
}
@Test
public void findByUid(){
System.out.println(userMapper.findByUid(78));
}
2.需要规划异常
2.1.用户的原密码异常、is_delete=1、uid找不到在用户发现的异常
2.2.updata在更新的时候、有可能未知的异常,UpdateException。
2.2设计接口和抽象方法
执行用户修改密码的核心方法
void changePassword(Integer uid,String username,String oldPassword,String newPassword);
在实现类中实现当前的抽象方法。
@Override
public void changePassword(Integer uid, String username, String oldPassword, String newPassword) {
User result=userMapper.findByUid(uid);
if(result==null || result.getIsDelete()==1){
throw new UserNotFoundException("用户数据不存在");
}
//原始密码和数据库中密码进行比较
String oldMd5Password=getMD5Password(oldPassword,result.getSalt());
if(!result.getPassword().equals(oldMd5Password)){
throw new PasswordNotMatchException("密码错误");
}
//将新的密码设置到数据库中,将新的密码进行加密再去更新
String newMd5Password=getMD5Password(newPassword,result.getSalt());
Integer rows = userMapper.updataPasswordByUid(uid, newPassword, username, new Date());
if(rows!=1){
throw new UpdateException("更新数据产生了未知的异常")
}
}
3.1处理异常
UpdateException需要配置到统一的处理方法中。
else if(e instanceof UpdateException){ result.setState(5001); result.setMessage("更新数据时产生了未知的异常"); }
3.2设计请求
/users/change_password
post
String oldPassword,String newPassword//需要和表单中的name 属性保持一致
JsonResult
3.3处理请求
@RequestMapping("change_password")
public JsonResult changePassword(String oldPassword,
String newPassword,
HttpSession session) {
Integer uid = getuidFromSession(session);
String username = getUsernameFromSession(session);
userService.changePassword(uid, username, oldPassword, newPassword);
return new JsonResult(OK);
}
password.html中添加ajax请求的处理,不在手动去编写ajax结构,直接
1.根据用户信息的SQL语句
updata t_user set phone=?,email=?,gender=?,modified_user=?,modifed_time=? where uid=?
2.根据用户名查询用户的数据
select * from t_user where uid=?
查询用户的数据不需要再重复开发。
更新用户信息的方法
update t_user set
phone=#{phone},
email=#{email},
gender=#{gender},
modified_user=#{modifiedUser},
modified_Time=#{modifiedTime}
where uid=${uid}
在测试类中测试功能
@Test
public void updateInfoVyUid(){
User user = new User();
user.setUid(21);
user.setPhone("155111000");
user.setEmail("[email protected]");
user.setGender(1);
userMapper.updateInfoByUid(user);
}
1.设计两个功能
2.打开页面的时候可能找不到用户的数据,点击删除按钮之前需要再次的去检测用户数据是否存在
主要有两个功能的模块,对应的是两个抽象的方法设计
/*根据用户id查询 * */ User getByUid(Integer uid); void changInfo(Integer uid,String username,User user);
在UserSerivceImpl类中添加两个抽象方法的具体实现,
暂无
/users/get_by_uid
GET
HttpSession session
JsonResult
2.点击修改按钮发送用户的数据修改操作请求的设计
/users/chang_info
post
User user, httpsession
JsonResult
@RequestMapping("get_by_uid")
public JsonResult getByUid(HttpSession session) {
// 从HttpSession对象中获取uid
Integer uid = getuidFromSession(session);
// 调用业务对象执行获取数据
User data = userService.getByUid(uid);
// 响应成功和数据
return new JsonResult(OK, data);
}
@RequestMapping("change_info")
public JsonResult changInfo(User user,HttpSession session){
//user 对象有四个部分的数据 username phone email gender
//uid 需要再次封装到user对象中
Integer uid = getuidFromSession(session);
String username = getUsernameFromSession(session);
userService.changInfo(uid,username,user);
return new JsonResult<>(OK);
}
1.在打开userdata.html页面自动发送ajax请求(get_by_uid),查询到的数据填充到这个页面。
2.检测到用户点击了修改 按钮之后发送一个ajax请求
1.1SQL语句的规划,将对象的文件保存在操作系统上,然后把这个文件路径给纪录在,因为在纪录路径的是非常便捷和方便,将如果要打开这个文件可以依据这个路径找到这个文件。在数据库中只需要保存这个文件路径即可,将左右的静态资源放到某台电脑上,再把这台电脑作为服务器使用,对应的是更新avatar字段的sql语句
updata t_user set avatar=?,modified_user=?,modified_time=? where uid=?
1.2设计接口和抽象方法来定义抽象方法用于修改用户的头像
/*修改用户头像
* @Param(SQL映射文件中#{}占位符变量名,解决的问题,当SQL语句的占位符和
* 需要将某个参数强制注入到某个占位符变量时,可以使用@Param这个注解来标注映射的*/
Integer updateAvatarByUid(@Param("uid") Integer uid,
@Param("avatar") String avatar,
@Param("modifiedUser") String modifiedUser,
@Param("modifiedTime") Date modifiedTime);
UserMapper.xml文件中编写映射的SQL语句
update t_user set avatar=#{avatar},
modified_user=#{modifiedUser},
modified_Time=#{modifiedTime}
where uid=#{uid}
@Test
public void updateAvatarByUid(){
userMapper.updateAvatarByUid(6,
"/update/avatar.png",
"管理员",new Date());
}
1.用户数据不存在,找不到对应的用户数据。
2.更新的时候,未知异常产生。
(无需开发)
/**
* 修改用户的头像
* @param uid 用户id
* @param avatar 用户头像id
* @param username 用户的名称
*/
void changeAvatar(Integer uid,String avatar,String username);
编写业务层的更新用户头像的方法
@Override
public void changeAvatar(Integer uid, String avatar, String username) {
//查询当前用户数据是否存在
User result = userMapper.findByUid(uid);
if(result==null || result.getIsDelete().equals(1)){
throw new UserNotFoundException("用户不存在");
}
Integer rows=userMapper.updateAvatarByUid(uid,avatar,username,new Date());
if(rows!=1){
throw new UpdateException("更新用户头像产生未知的异常");
}
}
测试业务层的方法执行。
@Test
public void changeAvatar(){
userService.changeAvatar(21,"/upload/test.png","xiaoming");
}
FileUploadException 泛指文件上传的异常 文件上传的异类 继承RunTimeException
FileEmtyException文件为空的异常
FileSizeException 文件大小超出限制
FileTypeException文件类型异常
FileUploadException文件读写异常
五个构造方法显示的声明出来,再去继承相关的父类
else if (e instanceof FileEmptyException) { result.setState(6000); } else if (e instanceof FileSizeException) { result.setState(6001); } else if (e instanceof FileTypeException) { result.setState(6002); } else if (e instanceof FileStateException) { result.setState(6003); } else if (e instanceof FileUploadIOException) { result.setState(6004); }
在异常统一处理方法的参数列表上增加新的异常处理作为他的参数
@ExceptionHandler({ServiceException.class,FileUploadException.class})
/users/change_avatar
POST 2kb
HttpSession session,MutipartFile file .
JsonResoult
实现请求
public JsonResoult
changeAvatar(){}
@RequestMapping("chang_avatar")
public JsonResult changeAvatar(HttpSession session,
@RequestParam("file") MultipartFile file){
//判断文件是否为空
if(file.isEmpty()){
throw new FileEmptyException("文件为空");
}
if(file.getSize()>AVATAR_MAX_SIZE){
throw new FileSizeException("文件超出限制");
}
//判断文件的类型是否是我们规定的和后缀类型
String contentType=file.getContentType();
//如果集合包含某个元素则返回值true
if(!AVATAR_TYPE.contains(contentType)){
throw new FileTypeException("文件不支持");
}
//上传的文件 //upload/文件.png
String parent= session.getServletContext().getRealPath("upload");
//File对象指向这个路径 File是否存在
File dir=new File(parent);
if (!dir.exists()){
dir.mkdirs();//创建目录
}
//获取到这个文件名称,UUID工具生成一个新的字符串作为文件名
String originalFilename=file.getOriginalFilename();
System.out.println(originalFilename);
int index = originalFilename.lastIndexOf(".");
String suffix=originalFilename.substring(index);
String filename=UUID.randomUUID().toString().toUpperCase()+suffix;
File dest=new File(dir,filename);// 空文件
//参数file中数据写入到这个空文件中
try {
file.transferTo(dest);//将file文件中的数据写入到dest文件中
} catch (FileStateException e){
throw new FileStateException("文件状态异常");
}
catch (IOException e) {
throw new FileUploadIOException("文件读写异常");
}
Integer uid=getuidFromSession(session);
String username=getUsernameFromSession(session);
//返回头像的路径/upload/test.png
String avatar="/upload"+filename;
userService.changeAvatar(uid,avatar,username);
//返回用户头像路径给前端页面,将来用于头像使用
return new JsonResult<>(OK,avatar);
}
在upload页面编写上传头像的代码
说明:如果直接使用表单进行文件上传,需要给表单显示的添加一个属性enctype=“multipart/form-data”声明出来,不会将目标的数据结构做修改在上传
SpringMVC默认为1mb文件可以进行上传,手动的去修改SpringMVC默认上传文件的大小。
方式1:直接可以在配置文件中进行配置
spring.servlet.multipart.max-request-size=15MB spring.servlet.multipart.max-file-size=10MB
方式2:需要采用Java代码形式来设置文件的上传大小的限制。主类进行配置,可以定义一个方法,必须用@Bean修饰。在类的前面添加一个@Configration注解进行修改类 mutipartConfigElement
@Bean public MultipartConfigElement getMultipartConfigElement() { MultipartConfigFactory factory=new MultipartConfigFactory(); //设置需要创建的对象的相关信息 factory.setMaxFileSize(DataSize.of(10, DataUnit.MEGABYTES)); factory.setMaxRequestSize(DataSize.of(15,DataUnit.MEGABYTES)); //通过工厂类创建对象 MultipartConfigElement 对象 return factory.createMultipartConfig(); }
在页面中通过ajax请求来提交文件,提交完成后返回json串,解析出data中数据,设置到img头像
img头像标签的src属性上就可以了,
serialize(),可以将表单数据自动拼接成key=Vlaue的结构进行提交给服务器,一般提交是普通的控件类型的数据等等
FormData类:将表单的数据保持原有的结构进行数据的条件。newFormData(),
ajax默认处理数据时按照字符串
可以更新头像成功后,将服务器返回的头像路径保存在客户端cookies对象中,然后每次检测到用户打开上传头像页面 ,在这个页面中通过ready方法来自动检测去读取cookies中头像并设置
2.新建收货地址
当前收货地址功能模块:列表展示,修改删除、新增收货地址。开发顺序:新增收货地址-列表展示-设置默认收货地址-删除收货地址-修改收货地址
1.对应是插入
insert into t_address (除了aid外字段列表) values (字段值列表)
2.一个用户的收货地址规定最多只能有20条数据对应,在插入用户数据之前先做查询操作,大于20条抛出异常
select count(*) t_address where uid=?
public interface AddressMapper {
/**
* 插入用户的收货地址
* @param address 收货地址数据
* @return 受影响的行数
*/
Integer insert(Address address);
/**
* 根据用户id统计收货地址数量
* @param uid
* @return
*/
Integer countByUid(Integer uid);
}
INSERT INTO t_address (
uid, name, province_name, province_code, city_name, city_code, area_name, area_code, zip,
address, phone, tel,tag, is_default, created_user, created_time, modified_user, modified_time
) VALUES (
#{uid}, #{name}, #{provinceName}, #{provinceCode}, #{cityName}, #{cityCode}, #{areaName},
#{areaCode}, #{zip}, #{address}, #{phone}, #{tel}, #{tag}, #{isDefault}, #{createdUser},
#{createdTime}, #{modifiedUser}, #{modifiedTime}
)
如果用户是第一插入用户的收货地址,规则当用户插入的地址是第一条时,需要将当前地址作为默认的收货 地址,当查询到统计总数为0则将当前地址的is_default值设置为1,查询统计的结果为0不代表异常查询到的结果大于20了,这时候需要抛出业务控制AddressCountLimitException异常。自行创建这个异常
package com.liyang.store.service.ex;
/*收货地址总数大于20条异常*/
public class AddressCountLimitException extends ServiceException{
public AddressCountLimitException() {
super();
}
public AddressCountLimitException(String message) {
super(message);
}
public AddressCountLimitException(String message, Throwable cause) {
super(message, cause);
}
public AddressCountLimitException(Throwable cause) {
super(cause);
}
protected AddressCountLimitException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
1.创建一个IAddressService接口,在中定义业务的抽象方法。
public interface IAddressService {
void addNewAddress(Integer uid, String username, Address address);
}
2.创建一个AddressServiceImpl实现类,去实现抽象方法
#Spring读取配置文件中的数据:@Values("${user.address.max-count}")
user.address.max-count=20
/*新增收货地址的实现类*/
public class AddressSericeImpl implements IAddressService {
@Autowired
private AddressMapper addressMapper;
@Value("${user.address.max-count}")
private Integer maxCount;
@Override
public void addNewAddress(Integer uid, String username, Address address) {
//调用收货地址统计的方法
Integer count=addressMapper.countByUid(uid);
if(count>=maxCount){
throw new AddressCountLimitException("用户收货地址超出上限");
}
//uid is_delete、
address.setUid(uid);
Integer isDefault= count==0?1:0; //1表示 默认,0表示不是默认
address.setIsDefault(isDefault);
//补全4项日志
address.setCreatedUser(username);
address.setModifiedUser(username);
address.setCreatedTime(new Date());
address.setModifiedTime(new Date());
//插入收货地址的方法
Integer rows=addressMapper.insert(address);
if(rows!=1){
throw new InsertException("插入用户的收货地址异常");
}
}
}
3.测试业务层的功能,AddressServiceTests测试业务功能
@SpringBootTest//表示标注当前的类是一个测试类,不会随同项目一块打包
@RunWith(SpringRunner.class)//表示启动这个单元测试类,需要传递一个参数必须是SpringRunner的实例类型
public class AddressServiceTests {
@Autowired
private IAddressService addressService;
@Test
public void addNewAddress(){
Address address=new Address();
address.setPhone("178423232");
address.setName("男朋友");
addressService.addNewAddress(23,"管理员",address);
}
}
业务层抛出了收货地址总数超标异常,在BaseController中进行处理。
/address/add_new_address
post
Address address,HttpSession session
JsonResult
在控制层创建AddressController来处理用户收货地址的请求
@RequestMapping("addresses")
@RestController
public class AddressController extends BaseController{
@Autowired
private IAddressService addressService;
@RequestMapping("/add_new_address")
public JsonResult addNewAddress(Address address, HttpSession session){
Integer uid=getuidFromSession(session);
String username=getUsernameFromSession(session);
addressService.addNewAddress(uid,username,address);
return new JsonResult<>(OK);
}
}
先登录用户,然后在进行接口测试
1.获取省市区列表-数据库
2.获取省市区列表-实体类
package com.liyang.store.entity;
import java.io.Serializable;
import java.util.Objects;
/**
* 省/市/区数据的实体类
*/
public class District implements Serializable {
private Integer id;
private String parent;
private String code;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getParent() {
return parent;
}
public void setParent(String parent) {
this.parent = parent;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof District)) return false;
District district = (District) o;
return Objects.equals(getId(), district.getId()) && Objects.equals(getParent(), district.getParent()) && Objects.equals(getCode(), district.getCode()) && Objects.equals(getName(), district.getName());
}
@Override
public int hashCode() {
return Objects.hash(getId(), getParent(), getCode(), getName());
}
@Override
public String toString() {
return "District{" +
"id=" + id +
", parent='" + parent + '\'' +
", code='" + code + '\'' +
", name='" + name + '\'' +
'}';
}
}
3获取省市区列表-持久层
select * from t_dict_district where parent=? order by code asc
抽象方法的定义DstrictMapper
查询语句,根据父代号进行查询
public interface IDistrictService {
/**
* 根据父代号来查询区域地址信息
* @param parent
* @return
*/
List getByParent(String parent);
}
2.创建DistrictService实现类,实现抽象的方法
@Service
public class DistrictServiceImpl implements IDistrictService {
@Autowired
private DistrictMapper districtMapper;
@Override
public List getByParent(String parent) {
Listlist= districtMapper.findByParent(parent);
//在进行网络数据传输时为了尽量避免无效数据的传输,可以将无效数据设置为 null
//可以节省流量
for(District d:list){
d.setId(null);
d.setParent(null);
}
return list;
}
}
3.单元测试
@SpringBootTest//表示标注当前的类是一个测试类,不会随同项目一块打包
@RunWith(SpringRunner.class)//表示启动这个单元测试类,需要传递一个参数必须是SpringRunner的实例类型
public class DistrictServiceTests {
@Autowired
private IDistrictService districtService;
@Test
public void getByParent(){
//86中国
List list= districtService.getByParent("86");
for(District d:list){
System.out.println(d);
}
}
}
4.获取省市区列表-控制层
4.1设计请求
/districts/
GET
String parent
JsonResolt
>
4.2请求处理
创建一个DistrictController,在类中来编写请求的方法
@RequestMapping("districts")
@RestController
public class DistrictController extends BaseController{
@Autowired
private IDistrictService districtService;
public JsonResult> getByParent(String parent){
List data=districtService.getByParent(parent);
return new JsonResult<>(OK,data);
}
}
districts请求添加白名单中
patterns.add("/districts/**");
直接请求服务器,来访问localhost:8080/districts?parent=86访问进行测试
1.规划根据当前code来获取当前省市区的名称,对应就是一条查询语句
select * from t_dist_district where code=?
2.在DistrictMapper接口定义出来。
String findNameBycode(String code);
3.在DistrictMapper.xml文件中添加抽象方法的映射
4.单元测试
@Test
public void fidNameByCode(){
String name= districtMapper.findNameBycode("610000");
System.out.println(name);
}
1.在业务层有没有异常需要处理的
2.定义对应的业务接口的
String getNameByCode(String code);
3.在子类中进行实现
@Override public String getNameByCode(String code) { return districtMapper.findNameBycode(code); } }
4.测试可以省略不写。(超过8行以上代码都要进行独立的测试)
1.添加地址层依赖于IDistrictService层
//在添加用户的收货地址的业务层依赖于IDistrictService的业务层接口 @Autowired private IDistrictService districtService;
2.在addNewAddress方法中将districtServcie接口中获取到的省市区数据专业到address对象,这个对象中就包含了所有的收货地址
//对address对象中的数据进行补全 省市区
String provinceName= districtService.getNameByCode(address.getProvinceCode());
String cityName= districtService.getNameByCode(address.getCityCode());
String areaName= districtService.getNameByCode(address.getAreaCode());
address.setAddress(provinceName);
address.setAddress(cityName);
address.setAddress(areaName);
获取省市区-前端页面
1.AddAddress.html页面中来编写对应的省市区展示及根据用户的不同选择来限制对应的标签中的内容