2022.11.1~2022.11.14
地址:store: 这是一个跟练项目,视频地址链接:https://www.bilibili.com/video/BV1bf4y1V7Bx/?spm_id_from=333.999.0.0 (gitee.com)
1.jdk 1.8
2.maven:配置到3.61
3.数据库:MariaDB、Mysql,5.1及以上
4.项目名称:store,商城
5.结构:com.cy.store
6.资源:resources文件夹(static、templates)
7.单元测试:test.com.cy.store
这里用老师的是错误的,改为自己的
spring.datasource.url=jdbc:mysql://localhost:3306/store?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=123456
8.访问项目的静态资源是否可以正常的加载
通过MyBatis来操作数据库,在做mybatis开发的流程。
1.用户的注册功能,相当于在做数据的插入操作
insert into t_user (username,password) value(值列表)
2.在用户的注册时首先要查询当前的用户名是否存在,如果存在则不能进行注册。相当于是一条查询语句。(创建一个UserMapper的接口。要在接口中定义这两个SQL语句抽象方法。
select * from t_user where username=?
(一定是先规划语句,然后再想代码怎么写)
定义Mapper接口,在项目的目录结构下首先创建一个mapper包,在这个包下在再根据不同的功能模块来创建mapper接口,创建一个UserMapper的接口。
package com.cy.store.mapper;
import com.cy.store.entiy.User;
/**
*用户模块的持久层接口
*/
//@Mapper这种写法不建议使用,一个项目当中会有很多个mapper接口,很麻烦,所有我们采用在启动类里添加Mapper的路径,让SpringBoot知道你的mapper在哪
public interface UserMapper {
/**
* 插入用户的数据
* @param user 用户的数据
* @return 受影响的行数(增、删、改,都受影响的行数作为返回值,可根据返回值来判断是否执行成功
*/
//插入的操作,建议用Integer作为返回值,影响的行数来判断是否存在,注:这里字段太多,建议生成对象的形式,即User用户对象
Integer insert(User user);
/**
* 根据用户名来查询用户的数据
* @param username 用户名
* @return 如果找到对应的用户则返回这个用户的数据,如果没有找到则返回NULL值
*/
User findByUsername(String username);
}
1.定义xml映射文件,与对应的接口进行关联。所有的映射文件需要放在resource目录下,在这个目录下创建一个mapper文件夹,然后在这个文件夹下存放Mapper的映射文件。
2.创建一个接口对应的映射文件,遵循和接口的名称保持一致即可。创建一个UserMapper.xml文件。
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cy.store.mapper.UserMapper">
mapper>
3.配置接口中的方法对应上的SQL语句上,需要借助标签来完成,insert\update\delete\select,对应的是SQL语句的增删改查操作。
4.单元测试:每个独立的层编写完毕后需要编写单元测试,来测试当前的功能,在test包结构下创建一个mapper包,在这个包下在创建持久层的功能测试
5.在测试的过程中发现有一个错误一直出现, 最后发现在实体类的基类中,createdUser我写成了createUser,少了一个d就导致后面一直找不到createdUser在User实体类中。
RuntimeException异常,作为异常的子类,然后再去定义具体的异常类型来继承这个异常。业务层异常的基类,ServeiceException异常。这个异常继承RuntimeException异常,异常机制的建立。
package com.cy.store.service.ex;
/*
* @Author: jun
* @Date:2022/11/1 14:23
* @概述:
*/
/**
* 业务层异常的基类: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异常类。
用户在进行注册时可能会产生用户名被占用的错误,抛出一个异常:
用户名被占用:UsernameDuplitedException异常。
package com.cy.store.service.ex;
/*
* @Author: jun
* @Date:2022/11/1 15:32
* @概述:
*/
/**用户名被占用异常**/
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);
}
}
正在执行数据插入操作的时候,服务器、数据库宕机。处于正在执行插入的过程中所产生的异常
package com.cy.store.service.ex;
/*
* @Author: jun
* @Date:2022/11/1 15:35
* @概述:
*/
/**
* 数据在插入的过程中所产生的异常
*/
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.cy.store.service;
import com.cy.store.entity.User;
/**
* 用户模块业务层接口
*/
public interface IUserService {
/**
* 用户注册方法
* @param user 用户的额数据对象
*/
void reg(User user);
}
创建一个实现类UserServicempl类,需要实现这个接口,并且实现抽象方法
然后在单元测试
状态码、状态描述信息、数据。这部分功能封装到一个类中,将这类作为方法放回值,返回给前端浏览器。
package com.cy.store.util;
import java.io.Serializable;
/*
* @Author: jun
* @Date:2022/11/2 10:08
* @概述:
*/
/**
* Json格式的数据进行响应
* @param
*/
public class JsonResult<E> implements Serializable {
/**
* 状态码
*/
private Integer state;
/**
* 描述信息
*/
private String message;
/**
* 数据,不知道类型就泛型描述
*/
private E 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;
}
}
依据当前的业务功能模块进行请求的设计
请求路径:/users/reg
请求参数:User user
请求类型:POST
响应结果:JsonResult
1.创建一个控制层对应的类UserController类。依赖于业务层的接口。
package com.cy.store.controller;
import com.cy.store.entity.User;
import com.cy.store.service.IUserService;
import com.cy.store.service.ex.InsertException;
import com.cy.store.service.ex.UsernameDuplicatedException;
import com.cy.store.util.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/*
* @Author: jun
* @Date:2022/11/2 10:47
* @概述:
*/
//@Controller
@RestController //这个注解的功能等效于@Controller+@ResponseBody
@RequestMapping("users")
public class UserController {
@Autowired
private IUserService userService;
@RequestMapping("reg")
// @ResponseBody //表示此方法的响应结果以json格式进行数据的响应给到前端,比较麻烦
public JsonResult<Object> reg(User user){
//创建响应返回的结果对象
JsonResult<Object> jsonResult = new JsonResult<>();
try {
userService.reg(user);
jsonResult.setState(200); //注册成功状态码为200
jsonResult.setMessage("用户注册成功"); //提示成功
} catch (UsernameDuplicatedException e) {
jsonResult.setState(4000); //当用户名被占用时,设置状态码为4000
jsonResult.setMessage("用户名被占用"); //设置描述信息
} catch (InsertException e){
jsonResult.setState(5000); //插入数据异常,设置状态码为5000
jsonResult.setMessage("注册时产生未知的异常");
}
return jsonResult;
}
}
以上代码业务逻辑比较繁琐,每一层的异常都要重新写一份捕获异常。所以我们用另一种方法来实现。如下:
在控制层抽离一个父类,在这个父类中统一的去处理关于异常的相关操作。故需要编写一个BaseController类,统一处理异常。(用继承思想实现捕获异常的方法)
package com.cy.store.controller;
/*
* @Author: jun
* @Date:2022/11/2 11:15
* @概述:
*/
import com.cy.store.service.ex.InsertException;
import com.cy.store.service.ex.ServiceException;
import com.cy.store.service.ex.UsernameDuplicatedException;
import com.cy.store.util.JsonResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.util.Objects;
/**
* 控制层的基类
*/
public class BaseController {
/**
* 操作成功的状态码
*/
public static final int OK = 200;
//请求处理的方法,这个方法的返回值就是需要传递给前端的数据
//自动将异常对象传递给此方法的参数列表上
//当前项目中产生了异常,被统一拦截到此方法中,这个方法此时就充当的是请求处理方法,方法的返回值自接给到前端
@ExceptionHandler(ServiceException.class) //用于统一处理抛出的异常
public JsonResult<Void> handleException(Throwable e){ //自定义了一个handException方法
JsonResult<Void> result = new JsonResult<>(e);
if (e instanceof UsernameDuplicatedException){ //判断e的数据类型是否属于
result.setState(4000);
result.setMessage("用户名已经被占用");
}else if(e instanceof InsertException){
result.setState(5000);
result.setMessage("注册时产生未知的异常");
}
return result;
}
}
重新构建了reg()方法
@RequestMapping("reg")
public JsonResult<Void> reg(User user){
userService.reg(user);
return new JsonResult<>(OK);//这里尖括号爆红显示java无法推断com.store.util.JsonResult<>的类型参数,
// 解决办法:在JsonResult中重写public JsonResult(Integer state)方法
}
这里补充一下访问权限修饰符
public——对所有类可见,任何人都可以使用;
private——仅对本类可见,除类型创建者和类型的内部方法外,其他人不得使用;
protected——对本包和所有子类可见(与private相当,差别是继承的类可以访问protected成员,而不能访问private成员)(继承的类即子类)。
默认访问权限(default)——对本包可见,可以访问同一个包中的其他类的成员,而不能访问其他包的成员。
1.在register页面中编写发送请求的方法,点击事件来完成。选中对应的按钮( ( " 选 择 器 " ) ) , 再 去 添 加 点 击 的 事 件 。 ("选择器")),再去添加点击的事件。 ("选择器")),再去添加点击的事件。.ajax()函数发送异步请求。
2.JQuery封装了一个函数,称之为$.ajax()函数,美元符相当于通过对象调用ajax()函数,可以异步加载相关的请求。依靠的是JavaScript提供的一个对象XHR(XmlHttpResponse),封装了这个对象。
3.Ajax()使用方式,需要传递一个方法体作为方法的参数来使用,一对大括号称之为方法体。ajax接收多个参数,参数与参数之间要求使用”,“进行分割,每一组参数之间使用”:“进行分割,参数的组成部分一个是参数的名称(不能随意定义),是参数的值,参数的值要求使用用字符串来表识。参数的声明顺序没有要求。语法结构:
$.ajax({
url:"",
type:"",
data:"",
dataType:"",
seccess:function(){
},
error:function(){
}
});
ajax()函数参数的含义:
参数 | 功能描述 |
---|---|
url | 标识请求的地址(url地址),不能包含参数列表部分的内容。例如:url:“localhost:8080/user/reg" |
type | 请求类型(GET和POST请求的类型)。例如:type:”POST“ |
data | 向指定的请求url地址提交的数据。例如:data:“username=tom&pwd=123” |
dataType | 提交的数据类型。数据的类型一般指定为json类型。dataType:“json” |
success | 当服务器正常响应客户端时,会自动调用success参数的方法,并且将服务器返回的数据以参数的形式传递给这个方法的参数上。 |
error | 当服务器未正常响应客户端时,会自动调用error参数的方法,并且将服务器放回的数据以参数的形式传递给这个方法的参数上。 |
js代码可以独立存放在一个js的文件里或者声明在一个script标签中。
js代码无法正常被服务器解析执行,体现在点击页面中的按钮没有任何的响应。解决方案:
当用户输入用户名和密码将数据提交给后台数据库进行查询,如果存在对应的用户名和密码则表示登录成功,登录成功之后跳转到系统的主页就是index.html页面,跳转在前端使用jquery来完成。
依据用户提交的用户名和密码做select查询。密码的比较在业务成执行。
select * from t_user where username=?
说明:如果分析过程中发现某一个功能模块已经被开发完成,就可以省略当前的开发步骤,这个分析过程不能够省略。
不用重复开发,单元测试也是无需单独执行。
1.用户名对应的密码错误,密码匹配失败的异常:PasswordNotMatchException异常,运行时异常,业务异常。
2.用户名没有被找到,抛出异常:UsernameNotFoundException。运行时异常,业务异常
3.异常的编写:
设计业务层接口和抽象方法
1.直接在IUserSerivce接口中编写抽象方法,login(String username,String password)。
将当前登录成功的用户数据以当前用户对象的形式进行返回。状态管理:可以将数据保存在Cookie或者session中,可以避免重复度很高的数据多次频繁操作数据进行获取(用户名、用户id-存放在session中,用户头像-cookie中)。
2.需要在实现类中实现父接口中抽象方法。
3.在测试类中测试业务层登录的方法是否可以执行通过。
@Override
public User login(String username, String password) {
//根据用户名称来查询用户的数据是否存在,如果不存在则抛异常
User result = userMapper.findByUsername(username);//这里我们将找到的结果放在result中,包含用户的所有信息
if (result == null){
throw new UserNotFoundException("用户未找到!");
}
//检测用户的密码是否匹配
//1.先获取到数据库中的加密之后的密码,在result中get
String oldPassword = result.getPassword();
//2.和用户的传递过来的密码进行比较
//2.1先获取盐值:要获取的是上一次在注册时所自动生成的盐值,怎么获取?很简单,盐值在上面的result中
String salt = result.getSalt();
//2.2将用户的密码按照相同的md5算法的规则进行加密
String newMd5Password = getMD5Password(password, salt);
//3.将密码进行比较,判断密码输入是否正确
if (!newMd5Password.equals(oldPassword)){
throw new PasswordNotMatchException("用户密码错误!");//这里同样要抛出异常
}
//还没完,哈哈,严谨的系统有多种情况需要捕获异常,接下来是将已注销的用户进行判断
//判断is_delete字段的值是否为1,如果为1则表示被标记为删除
if (result.getIsDelete() == 1){
throw new UserNotFoundException("用户已经注销或不存在!");
}
//如果上面三个判断都返回的false,则表示已经找到用户的数据,那么我们返回对象数据出去
//返回不需要全部的用户数据,所以我们将上面的result对象中的数据提取出关键所需的Uid,Username,Avatar
//这样有一个好处:可以提升系统的性能
User user = new User(); //new一个user对象来存放三个信息
user.setUid(result.getUid());
user.setUsername(result.getUsername());
user.setAvatar(result.getAvatar());
return user;
}
业务层抛出的异常是什么,需要在统一异常处理类中进行统一的捕获和处理,如果曾经抛出过的异常类型已经在统一异常处理类中曾经处理过,则不需要重新添加。
else if(e instanceof UserNotFoundException){
result.setState(5001);
result.setMessage("用户数据不存在的异常");
}else if(e instanceof PasswordNotMatchException){
result.setState(5002);
result.setMessage("用户名的密码错误的异常");
}
请求路径:/users/login
请求方式:POST
请求数据:String username ,String password ,HttpSession session
响应结果:JsonResult
在UserController类中编写请求处理请求的方法。
1.在login.html页面中依据前面所设置的请求来发送ajax请求。
<script type="text/javascript">
$("#btn-login").click(function () {
$.ajax({
url: "/users/login",
type: "POST",
//serialize自动化
//这里搞半天没跳转成功,原因是serialize少了(),无语
data: $("#form-login").serialize(),
dataType: "JSON",
success: function (json) {
if (json.state == 200){
alert("登录成功")
//登录成功之后要跳转到主页index
//相对路径来跳转的页面
//window.location.href和location.href是一样的
window.location.href = "index.html";
}else {
alert("登录成功")
}
},
error: function (xhr) {
alert("登录时产生未知的异常"+xhr.message)
}
});
});
script>
session对象主要存放在服务器端,可以用于保存服务器的临时数据的对象,所保存的数据可以在整个项目中都可以通过访问来获取,把session的数据看做一个共享的数据。首次登录的时候所获取的用户的数据,转移到session对象即可。session.getAttribute(“key”)可以将获取session中的数据这种行为进行封装,封装在BaseController类中。
1.封装session对象中数据的获取(封装在父类中),数据的设置(当用户登录成功后进行数据的设置,设置到全局的session对象)。
2.在父类中封装两个数据:获取uid和获取username对应的两个方法,用户头像暂时不考虑,将来封装在cookie中使用。
/**
* 获取session对象中的uid
* @param session session对象
* @return 当前登录的用户uid的值
*/
protected final Integer getuidFromSession(HttpSession session){
return Integer.valueOf(session.getAttribute("uid").toString());
}
/**
* 获取当前登入用户的username
* @param session session对象
* @return 当前登录用户的用户名
*/
protected final String getUsernameFromSession(HttpSession session){
return session.getAttribute("username").toString();
}
3.在登录的方法中将数据封装在session对象中。服务器本身自动创建有session对象,已经是一个全局的session对象。SpringBoot直接使用session对象,直接将HttpSession类型的对象作为请求处理方法的参数,会自动将全局的session对象注入到请求处理方法的session形参上。
拦截器:首先将所有的请求统一拦截到拦截器中,可以拦截器中定义过滤的规则,如果不满足系统的设置过滤规则,统一的处理是重新去打开login.html页面(重定向和转发),推荐使用重定向。
在SpringBoot项目中拦截器的定义和使用。SpringBoot是依靠springMVC来完成的,SpringMVC提供了一个HandlerInterceptor接口,用于表示定义一个拦截器,受限制自定义各类,在这个类实现这给接口。
1.首先自定义一个类,在这个类中实现这个HandlerInterceptor接口
package com.cy.store.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*
* @Author: jun
* @Date:2022/11/4 13:45
* @概述:
*/
/**
* 定义一个拦截器
*/
public class LoginInterceptor implements HandlerInterceptor {
/**
* 检测全局session对象中是否有uid数据,如果有数据则放行,如果没有则重定向到登录页面
* @param request 请求对象
* @param response 响应对象
* @param handler 处理器(把url和controller映射到一块)
* @return 如果返回值为true表示放行当前的请求,如果返回值为false则表示拦截
* @throws Exception
*/
//在DispatcherServlet调用所有的请求方法前被自动调用执行的方法
//SpringBoot会自动把请求对象给request,响应对象给到response,适配器给到handler
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception{
Object uid = request.getSession().getAttribute("uid");
if (uid == null){//为空说明用户没有登录过系统,则重定向到login.html页面
//不能用相对路径,因为这里是要告诉前端访问的新页面是在哪个目录下的新页面
//但是前面的localhost:8080可以省略,因为在同一个目录下
response.sendRedirect("/web/login.html");
//结束后续的调用
return false;
}
//放行这个请求
return true;
}
}
2.一个过滤器的开发分为两步,这步我们来注册过滤器,我们需要添加白名单,添加黑名单,否则所有的页面都会被拦截,白名单(哪些资源可以不登录就可以访问:login.html\register.html\login.html\index.html\produce.html),黑名单(在用户登录后才可以访问的页面资源)。这里使用的注册过滤器的技术:借助WebMvcconfigure接口,可以将用户定义的拦截器进行注册,才可以保证拦截器能够生效和使用,定义一个类,然后让这个类实现WebMvcConfigure接口。配置信息,建议存放在项目的config包结构下。
//将自定义的拦截器进行注册
default void addInterceptors(InterceptorRegistry registry) {
}
(提示重定向次数过多,login.html页面无法打开,将浏览器cookie清除,再将浏览器初始化(不建议,没了cookie很多页面账户得重新登录麻烦,建议弄另一个ip的谷歌浏览器,随便清除)
package com.cy.store.config;
/*
* @Author: jun
* @Date:2022/11/4 14:23
* @概述:
*/
import com.cy.store.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.List;
/**
* 处理拦截器的注册
*/
@Configuration//加载当前的拦截器并进行注册
public class LoginInterceptorConfigurer implements WebMvcConfigurer {
//配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//1.创建自定义的拦截器对象
HandlerInterceptor interceptor = new LoginInterceptor();
//2.配置白名单:存放在一个List集合
List<String> patterns = new ArrayList<>();
patterns.add("/bootstrap3/**");//这里的地址非常容易写错,写错会登录异常
patterns.add("/css/**");
patterns.add("/images/**");
patterns.add("/js/**");
patterns.add("/web/register.html");
patterns.add("/web/login.html");
patterns.add("/web/index.html");
patterns.add("/web/product.html");
patterns.add("/users/reg");
patterns.add("/users/login");
//这一步完成了拦截器的注册
registry.addInterceptor(interceptor)
.addPathPatterns("/**")
.excludePathPatterns(patterns);//表示要拦截的url是什么
}
}
需要用户提交原始密码和新密码,再根据当前登录的用户进行信息的修改操作。
根据用户的uid修改用户password值。
update t_user set password=?,modified_user=?,modified_time=? where uid=?
根据uid查询用户的数据。在修改密码前,首先要保证当前的这个用户的数据存在,检测时被标记为已经删除、检测输入的原始密码是否正确。
select * from t_user where uid=?
UserMapper接口,将以上的两个方法的抽象定义出来,将来映射到sql语句上。
/**
* 根据用户的uid来修改用户密码
* @param uid 用户的id
* @param password 用户输入的新密码
* @param modifiedUser 表示修改的执行者
* @param modifiedTime 表示修改数据的时间
* @return 返回值为受影响的行数
*/
Integer updatePasswordByUid(Integer uid,
String password,
String modifiedUser,
Date modifiedTime);
/**
*根据用户的id查询用户的数据
* @param uid 用户的id
* @return 如果找到则返回对象,反之返回null值
*/
User findByUid(Integer uid);
SQL的映射
配置到映射文件User Mapper.xml中
<update id="updatePasswordByUid">
update t_user set
password=#{password},
modified_user=#{modifiedUser},
modified_time=#{modifiedTime},
where uid=${uid}
update>
<select id="findByUid" resultMap="UserEntityMap">
select * from t_user where uid =#{uid}
select>
以上任务完成后做单元测试功能,检查刚刚的是否功能完善。
@Test
public void updatePasswordByUidTest(){
userMapper.updatePasswordByUid(10,"321","管理员",new Date());
}
@Test
public void findByUidTest(){
System.out.println(userMapper.findByUid(10));
}
规划异常:
1.用户的原密码错误,is_delete==1,uid找不到,在用户没有发现的异常。
2.update在更新的时候,有可能产生未知的异常,UpdateException.
设计接口和抽象方法:
执行用户修改密码的核心方法。
@Override
public void changePassword(Integer uid,
String username,
String oldPassword,
String newPassword) {
//重写接口中的方法
User byUid = userMapper.findByUid(uid);
if (byUid == null || byUid.getIsDelete() == 1){
throw new UserNotFoundException("用户数据不存在");
}
//以上判断通过后,将当前用户输入的密码与数据库中的密码进行比较
String oldMd5Password = getMD5Password(oldPassword, byUid.getSalt());
if (!byUid.getPassword().equals(oldMd5Password)){
throw new PasswordNotMatchException("输入的原密码错误");
}
//将新的密码设置到数据库中,所以我们将新的密码进行加密后再去更新到数据库中
String newMd5Password = getMD5Password(newPassword, byUid.getSalt());
Integer rows = userMapper.updatePasswordByUid(
uid, newPassword,
username, new Date());
if (rows != 1){
throw new UpdateException("更新时产生未知的异常");
}
}
在单元测试类中编写测试方法
处理异常:
UpdateException需要配置统一的异常处理方法中。
设计请求:
/user/change_password
post
String oldPassword,String newPassword,HttpSession session//需要和表单中的name属性值保持一致
JsonResult
修改页面中输入:http://localhost:8080/users/change_password?oldPassword=321&newPassword=123
处理请求:
/**
* 修改页面控制层
* @param oldPassword
* @param newPassword
* @param session
* @return
*/
@RequestMapping("chang_password")
public JsonResult<Void> changPassword(String oldPassword,
String newPassword,
HttpSession session){
Integer uid = getuidFromSession(session);
String username = getUsernameFromSession(session);
userService.changePassword(uid,username,oldPassword,newPassword);
return new JsonResult<>(OK);
}
题外话:navicat中DDL是指数据库模式定义语言DDL(Data Definition Language)
password.html中添加ajax请求的处理,不再手动去编写ajax结构,自接复制,然后再微调修改参数即可。
(1)需要规划SQL语句
1.更新用户信息的SQL语句
update t_user set phone=?,email=?,gender=?,modified_user=?,modified_time=? where uid=?
2.根据用户名查询用户的数据
select * from t_user where uid=?
(2)接口与抽象方法
更新用户sql语句
<!-- if表示条件判断标签,test接收的是一个返回值为boolean类型的条件,
如果test条件的结果为true则执行if标签内部的语句-->
<update id="updateInfoByUid">
update t_user set
<if test="phone!=null">phone = #{phone},
<if test="email!=null">email = #{email},
<if test="gender!=null">gender = #{gender},
modified_user = #{modifiedUser},
modified_time = #{modifiedTime}
where uid = ${uid}
</update>
单元测试:
@Test
public void updateInfoByUid(){
User user = new User();
user.setUid(12);
user.setPhone("123456789");
user.setEmail("[email protected]");
user.setGender(1);
userMapper.updateInfoByUid(user);
}
1.设计两个功能:
2.打开页面的时候可能找不到用户的数据,点击删除按钮之前需要再次的去检测用户的数据是否存在。
主要有两个功能的模块,对应的是两个抽象的方法的设计
/**
* 根据用户id查询用户的数据
* @param uid
* @return
*/
User getByUid(Integer uid);
/**
* 更新用户的数据操作
* @param uid 用户的id
* @param username 用户的名称
* @param user 用户对象数据
*/
void changeInfo(Integer uid ,String username,User user);
在UserSerivcelmpl类中添加两个抽象方法的具体实现。
业务层对用户操作异常的处理
@Override
public User getByUid(Integer uid) {
User result = userMapper.findByUid(uid);
if(result==null || result.getIsDelete()==1){
throw new UserNotFoundException("用户数据不存在");
}
User user = new User();
user.setUsername(result.getUsername());
user.setPhone(result.getPhone());
user.setEmail(result.getEmail());
user.setGender(result.getGender());
return user;
}
/**
*user对象中的数据phone\email\gender,手动再将uid、username封装在user对象中
*/
@Override
public void changeInfo(Integer uid, String username, User user) {
User result = userMapper.findByUid(uid);
if(result==null||result.getIsDelete()==1){
throw new UserNotFoundException("用户数据不存在");
}
user.setUid(uid);
user.setModifiedUser(username);
user.setModifiedTime(new Date());
Integer rows = userMapper.updateInfoByUid(user);
if(rows!=1){
throw new UpdateException("更新数据时产生未知的异常");
}
}
单元测试:
@Test
public void getByUidTest(){
System.out.println(userService.getByUid(12));
}
@Test
public void changeInfo(){
User user = new User();
user.setPhone("13197881160");
user.setEmail("[email protected]");
user.setGender(0);
userService.changeInfo(12,"管理员",user);
}
暂无
1.设置一打开页面就发送当前用户的数据查询
/users/get_by_uid
GET
HttpSession session
JsonResult
2.点击修改按钮发送用户的数据修改操作请求的设计
/users/change_info
POST
User user,HttpSession session
JsonResult
@RequestMapping("get_by_uid")
public JsonResult<User> getByUid(HttpSession session){
//从session中拿数据
User data = userService.getByUid(getuidFromSession(session));
return new JsonResult<>(OK,data);
}
@RequestMapping("change_info")
public JsonResult<Void> changeInfo(User user,
HttpSession session){
//user对象有四个部分的数据:username、phone、email、gender
//uid数据需要再次封装到user对象中
Integer uid = getuidFromSession(session);
String username = getUsernameFromSession(session);
userService.changeInfo(uid,username,user);
//继续返回一个合适的Json结果,告诉用户操作成功了
return new JsonResult<>(OK);
}
1.在打开userdata.html的页面自动发送ajax请求,(get_by_uid)
sql语句的规划:
将对象文件保存在操作系统中,然后再把这个文件的路径记录下来,记录路径比较方便,如果要打开文件就只需依据路径去找到这个文件,在数据库中需要保存这个文件的路径即可,将静态的资源(图片、文件、其他资源文件)放在某台电脑上,把这台电脑作为一台单独的服务器使用。
对应的是一个更新用户avatar字段的sql语句。
update t_user set avatar=?,modified_user=?,modified_time=? where uid=?
设计接口和抽象方法:
UserMapper接口中来定义一个抽象方法用于修改用户的头像。
接口的映射:
<update id="updateAvatarBByUid">
update t_user
set
avatar = #{avatar},
modified_user = #{modifiedUser},
modified_time = #{modifiedTime}
where
uid = #{uid}
update>
编写一个测试类来测试:
@Test
public void updateAvatarByUidTest(){
userMapper.updateAvatarByUid(12,"/image/touxiang1.png","管理员",new Date());
}
规划异常:
1.用户数据不存在,找不到对应的用户数据
2.更新的时候,各种未知异常产生
设计业务层的接口和抽象方法
/**
* 修改用户的头像
* @param uid 用户的id
* @param avatar 用户头像的路径
* @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(12,"/upload/test.png","张三");
}
规划异常:
文件异常基类的子类有:
FileEmptyException:文件为空的异常(没有选择上传的文件就提交了表单,或选择的文件是0字节的空文件)
FileSizeException:文件大小超出限制
FileTypeException:文件类型异常(上传的文件类型超出了限制)
FileUploadIOException:文件读写异常
FileStateException:文件状态异常(上穿文件时该文件正在打开状态)
在controller包下创子包ex,在ex包里面创建文件异常类的基类和上述五个文件异常类,创建的六个类都重写其父类的五个构造方法
处理异常:
在BaseController类中进行统一的编写和统一的处理
else if(e instanceof FileSizeException){
result.setState(6000);
result.setMessage("文件大小异常");
}else if(e instanceof FileEmptyException){
result.setState(6001);
result.setMessage("文件为空异常");
}else if (e instanceof FileStateException){
result.setState(6002);
result.setMessage("文件状态异常");
}else if (e instanceof FileTypeException){
result.setState(6003);
result.setMessage("文件类型异常");
}else if (e instanceof FileUploadIOException){
result.setState(6004);
result.setMessage("文件读写异常");
}
在异常统一处理的参数列表上添加新的异常处理作为它的参数
@ExceptionHandler({ServiceException.class,FileUnloadException.class})
设计请求:
/users/change_avatar
Post(get请求提交的数据大概2kb)
HttpSession session,MutipartFile file
JsonResult
实现请求:
//设置上传文件的最大值为10M
public static final int AVATAR_MAX_SIZE = 10*1024*1024;
//设置上传文件的类型
public static final List<String> AVATAR_TYPE = new ArrayList<>();
static {
AVATAR_TYPE.add("images/jpeg");
AVATAR_TYPE.add("images/png");
AVATAR_TYPE.add("images/bmp");
AVATAR_TYPE.add("images/gif");
}
/**
* MultipartFile接口是SpringMVC提供的一个接口,这个接口为我们包装了
* 获取文件类型的数据(任何类型的file都可以接收),SpringBoot它整合了SpringMVC,只需要在
* 处理请求的方法参数列表上声明一个参数类型为MultipartFile的参数,然后SpringBoot自动将
* 传递给服务的文件数据赋值给这个参数
*
* @RequestParam("file") 表示请求中的参数,将请求中的参数注入请求的处理方法的某个参数上,
* 如果名称不一致则可以使用@RequestParam注解进行标记和映射
*
* @param session
* @param file
* @return
*/
@RequestMapping("change_avatar")
public JsonResult<String> 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();
if (!AVATAR_TYPE.contains(contentType)){
throw new FileTypeException("文件类型不支持");
}
//上传的文件
String parent = session.getServletContext().getRealPath("upload");
//File对象指向这个路径,File是否存在
File dir = new File(parent);
if (!dir.exists()){//检测目录是否存在
dir.mkdirs();//创建当前的目录
}
//获取到文件的名称,使用UUID工具类生成一个新的字符串作为文件名
//例如:avatar01.png
String originalFilename = file.getOriginalFilename();
System.out.println("originalFilename:"+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"声明出来,不会将目标文件的数据结构做修改再上传,不同字符串。
解决Bug
更改默认的大小限制:
springMVC默认1MB文件可以进行上传,手动的去修改SpringMVC默认上传文件的大小。
方式1:直接在配置文件当中配置
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=15MB
方式2:需要采用java代码的形式来设置文件的上传大小的限制。主类当中进行配置,可以定义一个方法,必须使用@Bean修饰符来修饰,在类的前边添加@Configration注解 。。。麻烦,弃之。
显示头像:
在页面中通过ajax请求来提交文件,提交完成后返回了json串,解析出了data中的数据,设置到img头像标签的src属性就可以了。
serialize():可以将表单数据自动拼接成key=value的结构进行提交服务器,一般提交是普通的控件类型中的数据(text\password\radio\checkbox)等等
FormData类:将表单中数据保持原有的结构进行数据的条件。
new FormData($("#form")[0]);//文件类型的数据可以使用FormData对象进行存储
ajax默认处理数据时按照字符串的形式进行处理,以及默认会采用字符串的形式提交数据。关闭这两个默认的功能。
processData:false,//处理数据的形式,关闭处理数据
contentType:false,//提交数据的形式,关闭默认提交数据的形式
登录后显示头像:
可以更新头像成功后,将服务器返回的头像路径保存在客户端cookie对象,然后每次检测到用户打开上传头像页面,在中国页面中通过ready()方法来自动检测去读取cookie中头像并设置到src属性上。
1.设置cookie中的值:
导入cookie.js文件
<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script>
调用cookie方法:
$.cookie(key,value,time);//这里的time单位为天
2.在upload.html页面先引入cookie.js文件
<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script>
3.在upload.html页面通过ready()自动读取cookie中的数据。
登录后或者刷新页面头像又恢复默认,bug待修改。。
数据库表的创建:
CREATE TABLE t_address (
aid INT AUTO_INCREMENT COMMENT '收货地址id',
uid INT COMMENT '归属的用户id',
`name` VARCHAR(20) COMMENT '收货人姓名',
province_name VARCHAR(15) COMMENT '省-名称',
province_code CHAR(6) COMMENT '省-行政代号',
city_name VARCHAR(15) COMMENT '市-名称',
city_code CHAR(6) COMMENT '市-行政代号',
area_name VARCHAR(15) COMMENT '区-名称',
area_code CHAR(6) COMMENT '区-行政代号',
zip CHAR(6) COMMENT '邮政编码',
address VARCHAR(50) COMMENT '详细地址',
phone VARCHAR(20) COMMENT '手机',
tel VARCHAR(20) COMMENT '固话',
tag VARCHAR(6) COMMENT '标签',
is_default INT COMMENT '是否默认:0-不默认,1-默认',
created_user VARCHAR(20) COMMENT '创建人',
created_time DATETIME COMMENT '创建时间',
modified_user VARCHAR(20) COMMENT '修改人',
modified_time DATETIME COMMENT '修改时间',
PRIMARY KEY (aid)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
name是关键字,需要单引号圈起来。
实体类创建:
/**收货地址额实体类*/
public class Address extends BaseEntity {
private Integer aid;
private Integer uid;
private String name;
private String provinceName;
private String provinceCode;
private String cityName;
private String cityCode;
private String areaName;
private String areaCode;
private String zip;
private String address;
private String phone;
private String tel;
private String tag;
private Integer isDefault;
/**
* get,set
* equals和hashCode
* toString
*/
}
各功能的开发顺序:
当前收货地址功能模块:列表的展示、修改、删除、设置默认、新增收货地址。开发顺序:新增收货地址-列表展示-设置默认的收货地址-删除收货地址-修改收货地址。
package com.cy.store.mapper;
import com.cy.store.entity.Address;
//收货地址持久层的接口
public interface AddressMapper {
/**
* 插入用户的收货地址数据
* @param address
* @return
*/
Integer insert(Address address);
/**
* 根据用户的id统计收货地址数量
* @param uid
* @return
*/
Integer countByUid(Integer uid);
}
配置SQL映射
创建一个AddressMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cy.store.mapper.AddressMapper">
<resultMap id="AddressEntityMap" type="com.cy.store.entity.Address">
<id column="aid" property="aid"/>
<result column="province_name" property="provinceName"/>
<result column="province_code" property="provinceCode"/>
<result column="city_name" property="cityName"/>
<result column="city_code" property="cityCode"/>
<result column="area_name" property="areaName"/>
<result column="area_code" property="areaCode"/>
<result column="is_default" property="isDefault"/>
<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>
<insert id="insert" useGeneratedKeys="true" keyProperty="aid">
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}
)
insert>
<select id="countByUid" resultType="java.lang.Integer">
select count(*) t_address where uid={#uid}
select>
mapper>
在test下的mapper文件夹下创建AddressMapperTests的测试类。
package com.cy.store.mapper;
import com.cy.store.entity.Address;
import com.cy.store.entity.User;
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 java.util.Date;
/*
* @Author: jun
* @Date:2022/11/10 10:25
* @概述:
*/
//@SpringBootTest:表示标注当前的类是一个测试类,不会随同项目一块打包
@SpringBootTest
//@RunWith:表示启动这个单元测试类(单元测试是不能够运行的),需要传递一个参数,必须是SpringRunner的实例类型
@RunWith(SpringRunner.class)
public class AddressMapperTests {
@Autowired
private AddressMapper addressMapper;
@Test
public void insertTest(){
Address address = new Address();
address.setUid(13);
address.setPhone("123134");
address.setName("女朋友");
addressMapper.insert(address);
}
@Test
public void countByUidTest(){
Integer integer = addressMapper.countByUid(13);
System.out.println(integer);
}
}
规划异常:
如果用户是第一次插入用户的收货地址,规则:当用户插入的是第一条时,需要将这唯一一条设置为默认的收货地址,如果查询到统计的总数为0则将当前的地址中的is_defualt值设置为1.查询统计结果为0不代表异常。
查询结果大于20时需要抛出异常AddressCountLimitException
接口与抽象方法:
1.创建一个AddressService接口,在当中定义业务的抽象方法
package com.cy.store.service;
//收货地址接口
public interface IAddressService {
void addNewAddress(Integer uid,String username,Address address);
}
2.创建一个AddressServiceImpl实现类,去实现接口中的抽象方法
在配置文件中定义数据。
#Spring读取配置文件中数据:@Value("${user.address.max-count}")
User.address.max-count=20
在实现类中显现业务控制
@Value("${user.address.max-count}")
private Integer maxCount;
3.测试业务层的功能是否正常。
处理异常:
业务层抛出了收货地址总数超标的异常,在BaseController中
else if (e instanceof AddressCountLimitException){
result.setState(6005);
result.setMessage("用户的收货地址超出上限的异常");
设计请求:
/address/add_new_address
post
Address address,HttpSession Session
JsonResult
处理请求:
package com.cy.store.controller;
import com.cy.store.entity.Address;
import com.cy.store.service.IAddressService;
import com.cy.store.util.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
/*
* @Author: jun
* @Date:2022/11/10 22:12
* @概述:
*/
@RequestMapping("addresses")
@RestController
public class AddressController extends BaseController{
@Autowired
private IAddressService iAddressService;
@RequestMapping("add_new_address")
public JsonResult<Void> addNewAddress(Address address, HttpSession session){
Integer uid = getuidFromSession(session);
String username = getUsernameFromSession(session);
iAddressService.addNewAddress(uid,username,address);
return new JsonResult<>(OK);
}
}
先登录再测试,端口为http://localhost:8080/addresses/add_new_address?name=tom&phone=123,返回一个session200
CREATE TABLE t_dict_district (
id INT(11) NOT NULL AUTO_INCREMENT,
parent VARCHAR(6) DEFAULT NULL,
`code` VARCHAR(6) DEFAULT NULL,
`name` VARCHAR(16) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
parent属性表示的是父区域代码号,省的父代码号+86
创建Distinct实体类
private Integer id;
private String parent;
private String code;
private String name;
查询语句,根据父代号进行查询。
select * from t_dict_district where parent=? order by code ASC
抽象方法定义。DistrictMapper
1.创建接口IDistrictService,并定义抽象方法
package com.cy.store.service.impl;
import com.cy.store.entity.District;
import java.util.List;
public interface IDistrictService {
/**
* 根据父代号来查询区域信息(省市区)
* @param parent 父代码
* @return 多个区域的信息
*/
List<District> getByParent(String parent);
}
2.创建DistrictServiceImpl实现类,实现抽象方法
package com.cy.store.service;
import com.cy.store.entity.District;
import com.cy.store.service.impl.IDistrictService;
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 java.util.List;
/*
* @Author: jun
* @Date:2022/11/11 16:58
* @概述:
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class DistrictServiceTests {
@Autowired
private IDistrictService idistrictService;
@Test
public void getByParent() {
//86代表中国,所有的省父代码号都是86
List<District> list = idistrictService.getByParent("86");
for (District district : list) {
System.err.println(district);
}
}
}
标准错误输出流: System.err.println
设计请求:
/districts/
GET
String parent
JsonResult>
处理请求的方法:
创建一个DistrictController类,在类中编写处理请求的方法
(这里添加两个地址需要用中括号括起来)
@RequestMapping({"/",""})
public JsonResult<List<District>> getByParent(String parent){
List<District> data = iDistrictService.getByParent(parent);
return new JsonResult<>(OK,data);
}
district请求添加白名单中,
最后直接请求服务器,访问/districts?parent=86
1.规划根据当前的code来获取当前的省市区名称,对应就是一条查询语句
select * from t_dist_district where code=?
2.在DistrictMapper接口定义出来
1.添加地址层依赖于IDistrictService层
@Autowired
private IDistrictService iDistrictService;
2.在AddressServiceImpl的方法中将DistrictService接口中获取到的省市区数据封装到address对象,此时address就包含了所有用户收货地址的数据
/**
* 对address对象中的数据进行补全:省市区的名字看前端代码发现前端传递过来的省市区的name分别为:
* provinceCode,cityCode,areaCode,所以这里可以用address对象的get方法获取这三个的数据
*/
String provinceName = districtService.getNameByCode(address.getProvinceCode());
String cityName = districtService.getNameByCode(address.getCityCode());
String areaName = districtService.getNameByCode(address.getAreaCode());
address.setProvinceName(provinceName);
address.setCityName(cityName);
address.setAreaName(areaName);
数据库数据的查询操作
select * from t_address where uid=? order by is_defualt DESC,created_time
接口和抽象方法
/**
* 根据用户的id查询用户的收货地址数据
* @param uid
* @return 收货地址数据
*/
List<District> findByUid(Integer uid);
在xml文件中添加对应的sql语句映射
<select id="findByUid">
select * from t_address where uid=#{uid}
order by is_default DESC ,created_time DESC
select>
完成单元测试方法
@Test
public void findByUidTest(){
List<Address> byUid = addressMapper.findByUid(13);
System.out.println(byUid);
}
1.不用抛出相关的异常,不要进行异常的设计
2.设计业务层的接口和抽象方法
List<Address> getByUid(Integer uid);
3.需要在实现类中实现此方法的逻辑
@Override
public List<Address> getByUid(Integer uid) {
List<Address> list = addressMapper.findByUid(uid);
/**
* 收货地址列表在前端只展示了四个数据,这里让其他值为空降低数据量
* ProvinceName,CityName,AreaName,aid,tel(确认订单页展示展示用户地
* 址时用到tel)在展示地址列表用不到,但是后面提交订单时的地址会用到,所以这里不设为null
* */
for (Address address : list) {
//address.setAid(null);
address.setUid(null);
//address.setProvinceName(null);
address.setProvinceCode(null);
//address.setCityName(null);
address.setCityCode(null);
//address.setAreaName(null);
address.setAreaCode(null);
address.setZip(null);
//address.setTel(null);
address.setIsDefault(null);
address.setCreatedTime(null);
address.setCreatedUser(null);
address.setModifiedTime(null);
address.setModifiedUser(null);
}
return list;
}
4.此处省略单元测试。
1.请求设计
/address
HttpSession session
GET
JsonResult>
2.实现请求方法的编写
@RequestMapping({"","/"})
public JsonResult<List<Address>> getByUid(HttpSession session){
Integer getuid = getuidFromSession(session);
List<Address> data = iAddressService.getByUid(getuid);
return new JsonResult<>(OK,data);
}
3.先登录,再访问请求地址(http://localhost:8080/addresses)进行数据的测试
不赘述,全在代码注释里。。
规划需要执行的SQL语句:
无论选择哪一条数据,都把所有的数据设为非默认,再把当前数据设为默认
我们可能会想着把第一条设为非默认,再将该条设为默认,但这样处理的话需要额外做一条查询语句拿到默认地址的数据
1.检测当前用户想设置为默认收货地址的这条数据是否存在
select * from t_address where aid=?
2.在修改用户的默认收货地址之前先将所有的收货地址设置为非默认
update t_address set is_default=0 where uid=?
3.将用户选中的这条记录设置为默认收货地址
update t_address set is_default=1,modified_user=?,modified_time=? where aid=?
设计接口和抽象方法:
在AddressMapper接口中来定义实现该模块所需的三个方法
/**
* 根据aid查询收货地址数据
* @param aid 收货地址aid
* @return 收货地址数据,如果没有找到则返回null值
*/
Address findByAid(Integer aid);
/**
* 根据用户uid修改用户的收货地址统一设置为非默认
* @param uid 用户uid
* @return 受影响的行数
*/
Integer updateNonDefault(Integer uid);
Integer updateDefaultByAid(
@Param("aid") Integer aid,
@Param("modifiedUser") String modifiedUser,
@Param("modifiedTime") Date modifiedTime);
编写映射:
在AddressMapper.xml中编写映射
<select id="findByAid" resultMap="AddressEntityMap">
select * from t_address where aid=#{aid}
select>
<update id="updateNonDefault">
update t_address
set is_default=0
where uid=#{uid}
update>
<update id="updateDefaultByAid">
update t_address
set is_default=1,
modified_user=#{modifiedUser},
modified_time=#{modifiedTime}
where aid=#{aid}
update>
单元测试:
省略。。
规划异常:
在执行更新时产生未知的UpdateException异常,已经创建无需重复创建
访问的数据不是当前登录用户的收货地址数据,属于非法访问,AccessDeniedException异常(就比如说,展示收货地址列表的sql语句写错了,然后这里展示的是别人的收货地址,此时想要将某个收货地址改为默认就属于非法访问了)
收货地址可能不存在的AddressNotFoundException异常,(比如,刚展示完收货地址列表,管理员误删地址了,此时地址就不存在了)
在业务层的ex包下创建如下两个异常类,并继承ServiceException类
AddressNotFoundException;AccessDeniedException
设计接口和抽象方法:
1.在IAddressService接口中编写抽象方法setDefault,并使其在方法内部统一实现持久层的三个方法
分析一下该方法需要什么参数:
先看持久层的三个方法需要什么参数:aid,uid,modifiedUser,modifiedTime.
其中aid是从前端一步一步传到业务层的,所以需要该参数
uid和modifiedUser是一样的,都是由控制层从session获取的uid并传给业务层,所以需要该参数
modifiedTime可以在业务层new Date,所以不需要该参数
/**
* 修改某个用户的某条收货地址数据为默认收货地址
* @param aid 收货地址的id
* @param uid 用户id
* @param username 修改执行人
*/
void setDefault(Integer aid,Integer uid,String username);
@Override
public void setDefault(Integer aid, Integer uid, String username) {
//1.检测是否有该条收货地址数据
Address result = addressMapper.findByAid(aid);
if (result == null) {
throw new AddressNotFoundException("收货地址不存在");
}
//2.检测当前获取到的收货地址数据的归属
if (!result.getUid().equals(uid)) {
throw new AccessDeniedException("非法数据访问");
}
//3.先将所有的收货地址设置为非默认
Integer rows = addressMapper.updateNonDefault(uid);
if (rows < 1) {
throw new UpdateException("更新数据时产生未知的异常");
}
//4.将用户选中的地址设置为默认收货地址
rows = addressMapper.updateDefaultByAid(aid, username, new Date());
if (rows != 1) {
throw new UpdateException("更新数据时产生未知的异常");
}
}
单元测试:
省略。。
处理异常:
在BaseController类中处理业务层抛出两个异常
else if (e instanceof AddressNotFoundException) {
result.setState(4004);
result.setMessage("用户的收货地址数据不存在的异常");
} else if (e instanceof AccessDeniedException) {
result.setState(4005);
result.setMessage("收货地址数据非法访问的异常");
}
设计请求:
处理请求:
在addressController类中编写请求处理方法
//RestFul风格的请求编写
@RequestMapping("{aid}/set_default")
public JsonResult<Void> setDefault(
@PathVariable("aid") Integer aid,HttpSession session) {
addressService.setDefault(
aid,
getUidFromSession(session),
getUsernameFromSession(session));
return new JsonResult<>(OK);
}
启动服务,登录账号后在地址栏输入http://localhost:8080/addresses/8/set_default进行测试
<td><a onclick="setDefault(#{aid})" class="btn btn-xs add-def btn-default">设为默认</a></td>
在for循环中为占位符赋值:
tr = tr.replace("#{aid}",list[i].aid);
完成声明:
function setDefault(aid) {
$.ajax({
url: "/addresses/"+aid+"/set_default",
type: "POST",
//data: $("#form-change-password").serialize(),
dataType: "JSON",
success: function (json) {
if (json.state == 200) {
//重新加载收货地址列表页面
showAddressList();
} else {
alert("设置默认收货地址失败")
}
},
error: function (xhr) {
alert("设置默认收货地址时产生未知的异常!"+xhr.message);
}
});
}
规划需要执行的SQL语句:
1.在删除之前判断该数据是否存在,需要执行查询语句看能否查到该数据,还需要根据返回的aid获取uid并和session中的uid进行比较判断归属是否正确,这一条SQL语句在设置收货地址时已经开发,无需重复开发
2.开发执行删除的SQL语句
delete from t_address where aid=?
3.需要判断删除的地址是否是默认地址(使用aid查询到的地址对象的getIsDefault方法),如果判断出删的是默认地址,则还需要定义把哪个地址设为默认,这里定义最新修改的为默认地址.
开发该SQL语句
select * from t_address where uid=? order by modified_time DESC limit 0,1
其中limit 0,1表示查询到的第一条数据(limit (n-1),pageSize),这样查询后就只会获得第一条数据
4.如果用户本身就只有一条地址,那么删除后其他操作就可以不进行了,所以需要查询该用户的所有地址数量,在设置收货地址时已经开发,无需重复开发
设计接口和抽象方法:
在AddressMapper接口中进行抽象方法的设计
/**
* 根据收货地址id删除收货地址数据
* @param aid 收货地址的id
* @return 受影响的行数
*/
Integer deleteByAid(Integer aid);
/**
* 根据用户uid查询用户最后一次被修改的收货地址数据
* @param uid 用户id
* @return 收货地址数据
*/
Address findLastModified(Integer uid);
编写映射:
<delete id="deleteByAid">
delete from t_address where aid=#{aid}
delete>
<select id="findLastModified" resultMap="AddressEntityMap">
select * from t_address
where uid=#{uid}
order by modified_time DESC limit 0,1
select>
单元测试:
@Test
public void deleteByAid() {
addressMapper.deleteByAid(11);
}
@Test
public void findLastModified() {
System.out.println(addressMapper.findLastModified(11));
}
规划异常:
/**删除数据时产生的异常*/
public class DeleteException extends ServiceException{
/**重写ServiceException的所有构造方法*/
}
设计接口和抽象方法及实现:
1.在IAddressService接口中定于抽象方法
需要给抽象方法声明参数
根据分析可得,该抽象方法的实现依赖于持久层的以下方法:
1.findByAid:查询该条地址数据是否存在,参数是aid
3.deleteByAid:删除地址数据,参数是aid
5.countByUid:统计用户地址数量,参数是uid
6.findLastModified:查询得到最后修改的一条地址,参数是uid
7.updateDefaultByAid:设置默认收货地址,参数是aid,modifiedUser,modifiedTime
稍加分析可以得出接下来定义的抽象方法的参数是:aid,uid,username
/**
* 删除用户选中的收货地址数据
* @param aid 收货地址id
* @param uid 用户id
* @param username 用户名
*/
void delete(Integer aid,Integer uid,String username);
@Test
public void delete(){
iAddressService.deleteByAid(2,13,"11.12删除");
}
处理异常:
需要在BaseController类中处理异常类
else if (e instanceof DeleteException) {
result.setState(5002);
result.setMessage("删除数据时产生未知的异常");
}
设计请求:
@RequestMapping("{aid}/delete")
public JsonResult<Void> delete(@PathVariable("aid") Integer aid,HttpSession session) {
iAddressService.deleteByAid(
aid,
getuidFromSession(session),
getUsernameFromSession(session));
return new JsonResult<>(OK);
}
function setDefault(aid) {
$.ajax({
url: "/addresses/"+aid+"/set_default",
type: "POST",
//data: $("#form-change-password").serialize(),
dataType: "JSON",
success: function (json) {
if (json.state == 200) {
//重新加载收货地址列表页面
showAddressList();
} else {
alert("删除收货地址失败")
}
},
error: function (xhr) {
alert("删除收货地址时产生未知的异常!"+xhr.message);
}
});
}
创建数据库表:
CREATE TABLE t_product (
id int(20) NOT NULL COMMENT '商品id',
category_id int(20) DEFAULT NULL COMMENT '分类id',
item_type varchar(100) DEFAULT NULL COMMENT '商品系列',
title varchar(100) DEFAULT NULL COMMENT '商品标题',
sell_point varchar(150) DEFAULT NULL COMMENT '商品卖点',
price bigint(20) DEFAULT NULL COMMENT '商品单价',
num int(10) DEFAULT NULL COMMENT '库存数量',
image varchar(500) DEFAULT NULL COMMENT '图片路径',
`status` int(1) DEFAULT '1' COMMENT '商品状态 1:上架 2:下架 3:删除',
priority int(10) DEFAULT NULL COMMENT '显示优先级',
created_time datetime DEFAULT NULL COMMENT '创建时间',
modified_time datetime DEFAULT NULL COMMENT '最后修改时间',
created_user varchar(50) DEFAULT NULL COMMENT '创建人',
modified_user varchar(50) DEFAULT NULL COMMENT '最后修改人',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
向表插入数据:
规划需要执行的SQL语句:
select * from t_product where status=1 order by priority DESC LIMT 0,4
设计接口和抽象方法:
public interface ProductMapper {
/**
* 查询热销商品的前四名
* @return 热销商品前四名的集合
*/
List<Product> findHotList();
}
编写映射:
在main\resources\mapper文件夹下创建ProductMapper.xml文件,并在文件中配置findHotList()方法的映射。
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cy.store.mapper.ProductMapper">
<resultMap id="ProductEntityMap" type="com.cy.store.entity.Product">
<id column="id" property="id"/>
<result column="category_id" property="categoryId"/>
<result column="item_type" property="itemType"/>
<result column="sell_point" property="sellPoint"/>
<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>
<select id="findHotList" resultMap="ProductEntityMap">
select * from t_product where status=1 order by priority desc limit 0,4
select>
mapper>
规划异常:
(只要是查询,不涉及增删改的,都没有异常,无非就是没有该数据然后返回空)
接口设计和抽象方法:
public interface IProductService {
/**
* 查找热门商品
* @return
*/
List<Product> findHotList();
}
业务层编写:
@Service
public class ProductServiceImpl implements IProductService {
@Autowired
private ProductMapper productMapper;
@Override
public List<Product> findHotList() {
List<Product> list = productMapper.findHotList();
for (Product product : list) {
product.setPriority(null);
product.setCreatedUser(null);
product.setCreatedTime(null);
product.setModifiedUser(null);
product.setModifiedTime(null);
}
return list;
}
}
异常规划:
//商品未找到的异常
public class ProductNotFoundException extends ServiceException {}
设计请求:
/profucts/host_list
GET
这里没有参数
返回的是商品(JsonResult>
处理请求:
@RestController
@RequestMapping("porducts")
public class ProductController extends BaseController {
@Autowired
private IProductService productService;
@RequestMapping("hot_list")
public JsonResult<List<Product>> getHostList(){
List<Product> data = productService.findHotList();
return new JsonResult<List<Product>>(OK,data);
}
}
白名单的添加:
patterns.add("/products/**");
index.html
<script type="text/javascript">
$(document).ready(function () {
showHotList();
})
function showHotList() {
$("#hot-list").empty();
$.ajax({
url: "/products/hot_list",
type: "GET",
dataType: "JSON",
success: function(json) {
var list = json.data;
for (var i = 0; i < list.length; i++) {
console.log(list[i].title);//调试用
var html = '';
html = html.replace(/#{id}/g, list[i].id);
html = html.replace(/#{title}/g, list[i].title);
html = html.replace(/#{price}/g, list[i].price);
html = html.replace(/#{image}/g, list[i].image);
$("#hot-list").append(html);
}
}
});
}
</script>
关于image标签里面的属性src=“…#{image}collect.png” class=“img-responsive”
SQL语句规划:
select * from t_product where id=#{id}
设计接口和抽象方法
Mapper:
/**
* 根据商品id查询商品详情
* @param id 商品id
* @return 匹配的商品详情,如果没有匹配的数据则返回null
*/
Product findById(Integer id);
编写映射:
<select id="findById" resultMap="ProductEntityMap">
select * from t_product where id=#{id}
select>
规划异常:
商品不存在抛出异常:
//商品未找到的异常
public class ProductNotFoundException extends ServiceException {}
设计接口和抽象方法:
@Override
public Product findById(Integer id) {
Product product = productMapper.findById(id);
//判断商品存在与否
if (product==null){
throw new ProductNotFoundException("查询的商品不存在");
}
product.setPriority(null);
product.setCreatedUser(null);
product.setCreatedTime(null);
product.setModifiedUser(null);
product.setModifiedTime(null);
return product;
}
处理异常:
每个异常都要在基类中添加状态码。
else if (e instanceof ProductNotFoundException){
result.setState(4006);
result.setMessage("访问的商品数据不存在的异常");
}
设计请求:
这里也称之为接口文档
处理请求;
编写控制层的逻辑方法。
@GetMapping("{id}/details")
public JsonResult<Product> getByid(@PathVariable("id") Integer id){
Product data = productService.findById(id);
return new JsonResult<>(OK,data);
}
SpringMVC内容:
@GetMapping是对@RequestMapping的限定,限定了只有Get请求才能访问的路径。
导入写好的JS
<script type="text/javascript" src="../js/jquery-getUrlParam.js"></script>
<script type="text/javascript">
$(document).ready(function() {
showHotList();
});
function showHotList() {
$("#hot-list").empty();
$.ajax({
url: "/products/hot_list",
type: "GET",
dataType: "JSON",
success: function(json) {
var list = json.data;
for (var i = 0; i < list.length; i++) {
console.log(list[i].title);//调试用
var html = '';
html = html.replace(/#{id}/g, list[i].id);
html = html.replace(/#{title}/g, list[i].title);
html = html.replace(/#{price}/g, list[i].price);
html = html.replace(/#{image}/g, list[i].image);
$("#hot-list").append(html);
}
}
});
}
</script>
数据表:
CREATE TABLE t_cart (
cid INT AUTO_INCREMENT COMMENT '购物车数据id',
uid INT NOT NULL COMMENT '用户id',
pid INT NOT NULL COMMENT '商品id',
price BIGINT COMMENT '加入时商品单价',
num INT COMMENT '商品数量',
created_user VARCHAR(20) COMMENT '创建人',
created_time DATETIME COMMENT '创建时间',
modified_user VARCHAR(20) COMMENT '修改人',
modified_time DATETIME COMMENT '修改时间',
PRIMARY KEY (cid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
实体类:
//购物车实体类
public class Cart extends BaseEntity {
private Integer cid;
private Integer uid;
private Integer pid;
private Long price;
private Integer num;
}
规划需要执行的SQL语句:
1.向购物车表中插入商品数据的SQL语句
insert into t_cart values;
2.如果是当前的商品已经在购物车存在,则直接更新num即可。
update t_cart set num=? where cid=?
3.在插入或者更新具体执行哪个语句,取决于数据库中是否有当前的这个购物车商品的数据,得去查询才能确定
select * from t_cart where cid=? and uid=?
设计接口和抽象方法
CartMapper接口
public interface CartMapper {
/**
* 插入购物车
* @param cart 购物车数据
* @return
*/
Integer insert(Cart cart);
/**
* 更新购物车某件商品的数量
* @param cid 购物车数据id
* @param num 更新的数量
* @param modifiedUser 修改者
* @param modifiedTime 修改时间
* @return
*/
Integer updateNumByCid(Integer cid,
Integer num,
String modifiedUser,
Date modifiedTime);
/**
* 根据用户的id和商品的id来查询购物车中的数据
* @param uid
* @param pid
* @return
*/
Cart findByUidAndPid(Integer uid,Integer pid);
}
sql映射:
new CartMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cy.store.mapper.CartMapper">
<resultMap id="CartEntityMap" type="com.cy.store.entity.Cart">
<id column="cid" property="cid"/>
<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>
<insert id="insert" useGeneratedKeys="true" keyProperty="cid">
insert into t_cart (uid, pid, price, num, created_user, created_time, modified_user, modified_time)
values (#{uid}, #{pid}, #{price}, #{num}, #{createdUser}, #{createdTime}, #{modifiedUser}, #{modifiedTime})
insert>
<update id="updateNumByCid">
update t_cart
set num=#{num},
modified_user=#{ modifiedUser},
modified_time=#{modifiedTime}
where
cid={#cid}
update>
<select id="findByUidAndPid" resultMap="CartEntityMap">
select * from t_cart where uid=#{uid} and pid=#{pid}
select>
mapper>
测试:
package com.cy.store.mapper;
import com.cy.store.entity.Cart;
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 java.util.Date;
/*
* @Author: jun
* @Date:2022/11/12 22:05
* @概述:
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class CartMapperTests {
@Autowired
private CartMapper cartMapper;
@Test
public void insertTest(){
Cart cart = new Cart();
cart.setCid(1);
cart.setUid(12);
cart.setPid(1);
//价格一般用double或者Long,不会用float,因为计算机本身原因
cart.setPrice(5000L);
cartMapper.insert(cart);
}
@Test
public void updateNumByCid(){
cartMapper.updateNumByCid(1,2,"中正电脑",new Date());
}
@Test
public void findByUidAndPid(){
Cart cart = cartMapper.findByUidAndPid(12, 1);
System.err.println(cart);
}
}
规划异常;
1.插入数据异常:InsertException
2.更新数据时异常:UpdateException
新建ICatService&CartServiceImpl
一般情况下,出现空指针异常是由于参数没给到位。
1.没有需要处理的异常
2.设计请求处理设计
/carts/add_to_cart
GET
pid,amount,seesion
JsonResult
3.完成请求处理的方式
package com.cy.store.controller;
import com.cy.store.service.ICartService;
import com.cy.store.util.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
/*
* @Author: jun
* @Date:2022/11/13 10:39
* @概述:
*/
@RequestMapping("carts")
@RestController
public class CartController extends BaseController {
@Autowired
private ICartService cartService;
@RequestMapping("add_to_cart")
public JsonResult<Void> addToCart(Integer pid,
Integer amount,
HttpSession session){
cartService.addToCart(
getuidFromSession(session),
pid,
amount,
getUsernameFromSession(session));
return new JsonResult<>(OK);
}
}
在product.html页面中的body标签内的script标签里为“加入购物车”按钮添加点击事件
在ajax函数中data参数的数据设置的方式
data:$(“选择的form表单”).serialize()。当需要提交的参数过多并且在同一个表单中时使用
data:new FormData($(“选择的form表单”)[0])。只适用提交文件
data:“username=TOM”。手动拼接,适合参数值固定并且参数值列表有限.等同于
let user = "tom"
data:"username="+user
适合用于JSON格式提交数据:
data:{
”username":"tom"
"age":12;
"sex":0
}
这里加入购物车前端页面有问题,还有那个热销榜是写死的,需要改为文件夹中的
java.lang.NullPointerException: null
检查sql
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oClb1ugi-1669430458789)(C:\Users\30500\AppData\Roaming\Typora\typora-user-images\1668318364859.png)]
规划SQL语句:
多表查询如果字段不重复则不需要显示声明该字段属于哪张表
select
cid,
uid,
pid,
t_cart.price,
t_cart.num,
t_product.title,
t_product.image
t_product.price as realprice
from
t_cart ledt join t_product on t_cart.pid = t_product.id
where
uid=#{uid}
order by
t_cart.createdTime DESC
设计接口和抽象方法:
VO全称Value Object,值对象。当进行select查询时,查询的结果属于多张表中的内容,此时发现结果集不能直接使用某个POJO实体类来接收,因为POJO实体类不能包含多表查询出来的信息,解决方式是:重新去构建一个新的对象,这个对象用于存储所查询出来的结果集对应的映射,所以把这个对象称之为值对象.
//购物车数据的VO类(Value Object)
public class CartVO implements Serializable {
private Integer cid;
private Integer uid;
private Integer pid;
private Long price;
private Integer num;
private String title;
private Long realPrice;
private String image;
}
<select id="findVOByUid" resultType="com.cy.store.vo.CartVO">
select
cid,
uid,
pid,
t_cart.price,
t_cart.num,
title,
t_product.price as realPrice,
image
from t_cart
left join t_product on t_cart.pid = t_product.id
where
uid = #{uid}
order by
t_cart.created_time desc
select>
List<CartVO> getVOByUid(Integer uid);
@Override
public List<CartVO> getVOByUid(Integer uid) {
return cartMapper.findVOByUid(uid);
}
1.设计请求:
/carts/
session
GET
JsonResult>
2.实现请求代码的编写:
@RequestMapping({"","/"})
public JsonResult<List<CartVO>> getVOByUid(HttpSession session){
List<CartVO> data = cartService.getVOByUid(getuidFromSession(session));
return new JsonResult<>(OK,data);
}
登录后访问http://localhost:8080/carts
1.注释掉cart.js
2.from表单结构,按钮修改属性
3.ready()函数来完成请求
规划需要执行的SQL语句:
1.执行更新t_cart表记录的num值,无需重复开发
update t_cart set num=?,modified_user=?,modified_time=?
2.根据cid的值来查询当前购物车这条数据是否存在
select * from t_cart where cid=#{cid}
接口和抽象方法:
Cart findByCid(Integer cid)
配置sql映射:
<select id="findByCid" resultType="CartEntityMap">
select * from t_cart where cid=#{cid}
select>
编写一个单元测试方法:
Cause: java.lang.ClassNotFoundException: Cannot find class:CartEntityMap
这个错误原因就是resultMap写错了,写成了resultType
规划异常:
1.在更新时会产生异常
2.查询的数据是否有访问的权限
3.要查询的数据不存在,抛出异常
设计请求:
设计接口和抽象方法:
处理异常:
else if (e instanceof CartNotFoundException){
result.setState(4007);
result.setMessage("购物车数据不存在的异常");
}
@RequestMapping({"{cid}/num/add"})
public JsonResult<Integer> addNum(Integer cid,HttpSession session){
Integer data = cartService.addNum(cid,
getuidFromSession(session),
getUsernameFromSession(session));
return new JsonResult<>(OK,data);
}
先登录,再访问url地址对应的请求。
1.确认showCartList()函数中动态拼接增加购物车按钮是绑定addNum()事件,如果已经添加则无需重复。
<input class="num-btn" type="button" value="+" onclick="addNum{#{cid}}" />
2.添加逻辑代码:
function addNum(cid) {
$.ajax({
url: "/carts/"+cid+"/num/add",
type: "POST",
dataType: "JSON",
success: function (json) {
if (json.state == 200) {
$("#goodsCount"+cid).val(json.data);//字符串+整数cid后结果为字符串
//更新该商品总价
/*
html()方法:
不传参:是获取某个标签内部的内容(文本或标签)
传参:将参数放到标签里面替换掉该标签原有内容
* */
var price = $("#goodsPrice"+cid).html();
var totalPrice = price * json.data;
//将商品总价更新到控件中
$("#goodsCast"+cid).html(totalPrice);
} else {
alert("增加购物车商品数量失败"+json.message);
}
},
error: function (xhr) {
alert("增加购物车商品数量时产生未知的异常!"+xhr.message);
}
});
}
规划SQL语句:
用户在购物车列表页中通过随机勾选相关的商品,在点击”结算“
规划需要执行的SQL语句:
select
cid,
uid,
pid,
t_cart.price,
t_cart.num,
title,
t_product.price as realPrice,
image
from t_cart
left join t_product on t_cart.pid = t_product.id
where
cid in (?,?,?)
order by
t_cart.created_time desc
注意where cid in (?,?,?),这里是需要传入cid的集合
配置sql映射:
<select id="findVOByCids" resultType="com.cy.store.vo.CartVO">
select
cid,
uid,
pid,
t_cart.price,
t_cart.num,
title,
t_product.price as realPrice,
image
from t_cart
left join t_product on t_cart.pid = t_product.id
where
cid in (
<foreach collection="array" item="cid" separator=",">
#{cid}
foreach>
)
order by
t_cart.created_time desc
select>
List findVOByCids(Integer[] cids);
单元测试:
@Test
public void findVOByCids(){
Integer[] cids ={1,2,3,5,10};
List<CartVO> list = cartMapper.findVOByCids(cids);
for (CartVO a:list
) {
System.out.println(a);
}
}
1.没有需要规划的异常
2.接口和抽象方法的实现
List<CartVO> getVOByCids(Integer uid,Integer[] cids);
@Override
public List<CartVO> getVOByCids(Integer uid, Integer[] cids) {
List<CartVO> list = cartMapper.findVOByCids(cids);
//迭代器遍历
Iterator<CartVO> iterator = list.iterator();
while (iterator.hasNext()){
CartVO cart = iterator.next();
if (!cart.getUid().equals(uid)){
iterator.remove();//不使用list.remove(cart),在迭代器遍历不能使用集合移除,用迭代器自己的方法
}
}
return list;
}
处理异常:
无异常。。
设计请求:
处理请求:
@RequestMapping("list")
public JsonResult<List<CartVO>> findVOByCids(Integer[] cids,HttpSession session){
List<CartVO> data = cartService.getVOByCids(getuidFromSession(session), cids);
return new JsonResult<>(OK,data);
}
登录后输入http://localhost:8080/carts/list?cids=1&cids2
$(document).ready(function() {
showCartList();
showAddressList();
});
function showAddressList() {
$("#address-list").empty();
$.ajax({
url: "/addresses",
type: "GET",
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
var list = json.data;
for (var i = 0; i < list.length; i++) {
/*
value="#{aid}"在该模块没有用,但是扔写上,只要是从数据库查到到的数据,都要让前端页
面的该条数据和id绑定(因为可能干别的什么时需要用到,就比如说下一个"创建订单"模块
就需要根据前端传给后端的aid查询用户选中的是哪一个地址然后将其加入订单表)
* */
var opt = '';
opt = opt.replace("#{aid}",list[i].aid);
opt = opt.replace("#{name}",list[i].name);
opt = opt.replace("#{tag}",list[i].tag);
opt = opt.replace("#{provinceName}",list[i].provinceName);
opt = opt.replace("#{cityName}",list[i].cityName);
opt = opt.replace("#{areaName}",list[i].areaName);
opt = opt.replace("#{address}",list[i].address);
opt = opt.replace("#{tel}",list[i].tel);
$("#address-list").append(opt);
}
}
},
error: function (xhr) {
alert("在确认订单页加载用户地址时发生未知的异常"+xhr.status);
}
});
}
function showCartList() {
$("#cart-list").empty();
$.ajax({
url: "/carts/list",
type: "GET",
data: location.search.substr(1),
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
var list = json.data;
console.log(location.search.substr(1));//调试用
//声明两个变量用于更新"确认订单"页的总件数和总价
var allCount = 0;
var allPrice = 0;
for (var i = 0; i < list.length; i++) {
var tr = '\n' +
' \n' +
'#{title} \n' +
'¥#{price} \n' +
'#{num} \n' +
'#{totalPrice} \n' +
' ';
tr = tr.replace("#{image}",list[i].image);
tr = tr.replace("#{title}",list[i].title);
tr = tr.replace("#{price}",list[i].realPrice);
tr = tr.replace("#{num}",list[i].num);
tr = tr.replace("#{totalPrice}",list[i].realPrice*list[i].num);
$("#cart-list").append(tr);
//更新"确认订单"页的总件数和总价
allCount += list[i].num;
allPrice += list[i].realPrice*list[i].num;
}
$("#all-count").html(allCount);
$("#all-price").html(allPrice);
}
},
error: function (xhr) {
alert("在确认订单页加载勾选的购物车数据时发生未知的异常"+xhr.status);
}
});
}
CREATE TABLE t_order (
oid INT AUTO_INCREMENT COMMENT '订单id',
uid INT NOT NULL COMMENT '用户id',
recv_name VARCHAR(20) NOT NULL COMMENT '收货人姓名',
recv_phone VARCHAR(20) COMMENT '收货人电话',
recv_province VARCHAR(15) COMMENT '收货人所在省',
recv_city VARCHAR(15) COMMENT '收货人所在市',
recv_area VARCHAR(15) COMMENT '收货人所在区',
recv_address VARCHAR(50) COMMENT '收货详细地址',
total_price BIGINT COMMENT '总价',
status INT COMMENT '状态:0-未支付,1-已支付,2-已取消,3-已关闭,4-已完成',
order_time DATETIME COMMENT '下单时间',
pay_time DATETIME COMMENT '支付时间',
created_user VARCHAR(20) COMMENT '创建人',
created_time DATETIME COMMENT '创建时间',
modified_user VARCHAR(20) COMMENT '修改人',
modified_time DATETIME COMMENT '修改时间',
PRIMARY KEY (oid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE t_order_item (
id INT AUTO_INCREMENT COMMENT '订单中的商品记录的id',
oid INT NOT NULL COMMENT '所归属的订单的id',
pid INT NOT NULL COMMENT '商品的id',
title VARCHAR(100) NOT NULL COMMENT '商品标题',
image VARCHAR(500) COMMENT '商品图片',
price BIGINT COMMENT '商品价格',
num INT COMMENT '购买数量',
created_user VARCHAR(20) COMMENT '创建人',
created_time DATETIME COMMENT '创建时间',
modified_user VARCHAR(20) COMMENT '修改人',
modified_time DATETIME COMMENT '修改时间',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
//订单数据的实体类
public class Order extends BaseEntity implements Serializable {
private Integer oid;
private Integer uid;
private String recvName;
private String recvPhone;
private String recvProvince;
private String recvCity;
private String recvArea;
private String recvAddress;
private Long totalPrice;
private Integer status;
private Date orderTime;
private Date payTime;
}
public class OrderItem extends BaseEntity implements Serializable {
private Integer id;
private Integer oid;
private Integer pid;
private String title;
private String image;
private Long price;
private Integer num;
}
规划SQL:
1.将数据插入到订单表中
insert in t_order
2.将数据插入到订单项的表中
insert in t_order_item
3.编写mapper:
public interface OrderMapper{
/**
* 插入订单的数据
* @param order
* @return
*/
Integer insertOrder(Order order);
/**
* 插入订单项的数据
* @param orderItem
* @return
*/
Integer insertOrderItem(OrderItem orderItem);
}
4.xml编写
<insert id="insertOrder" useGeneratedKeys="true" keyProperty="oid">
insert into t_order (
uid, recv_name, recv_phone, recv_province, recv_city, recv_area, recv_address,
total_price,status, order_time, pay_time, created_user, created_time, modified_user,
modified_time
) values (
#{uid}, #{recvName}, #{recvPhone}, #{recvProvince}, #{recvCity}, #{recvArea},
#{recvAddress}, #{totalPrice}, #{status}, #{orderTime}, #{payTime}, #{createdUser},
#{createdTime}, #{modifiedUser}, #{modifiedTime}
)
insert>
<insert id="insertOrderItem" useGeneratedKeys="true" keyProperty="id">
insert into t_order_item (
oid, pid, title, image, price, num, created_user,
created_time, modified_user, modified_time
) values (
#{oid}, #{pid}, #{title}, #{image}, #{price}, #{num}, #{createdUser},
#{createdTime}, #{modifiedUser}, #{modifiedTime}
)
insert>
5.测试类
public class OrderMapperTests {
@Autowired
private OrderMapper orderMapper;
@Test
public void insertOrderTest(){
Order order = new Order();
order.setUid(12);
order.setRecvName("张某");
order.setRecvPhone("123456");
orderMapper.insertOrder(order);
}
@Test
public void insertOrderItemTest(){
OrderItem orderItem = new OrderItem();
orderItem.setOid(1);
orderItem.setPid(1000003);
orderItem.setTitle("名称");
orderMapper.insertOrderItem(orderItem);
}
}
1.在IAddressService接口中定义根据收货地址
@Override
public Address getByAid(Integer aid, Integer uid) {
Address address = addressMapper.findByAid(aid);
if (address == null){
throw new AddressNotFoundException("收货地址不存在");
}
if (!address.getUid().equals(uid)){
throw new AccessDeniedException("非法数据异常");
}
address.setProvinceCode(null);
address.setCityCode(null);
address.setAreaCode(null);
address.setCreatedUser(null);
address.setCreatedTime(null);
address.setModifiedUser(null);
address.setModifiedTime(null);
return address;
}
设计请求:
/orders/create
GET
Integer aid, Integer[] cids, HttpSession session
JsonResult
处理请求:
@RequestMapping("orders")
@RestController
public class OrderController extends BaseController {
@Autowired
private IOrderService orderService;
@RequestMapping("create")
public JsonResult<Order> create(Integer aid, Integer[] cids, HttpSession session){
Integer uid = getuidFromSession(session);
String username = getUsernameFromSession(session);
Order data = orderService.create(aid, uid, username, cids);
return new JsonResult<>(OK,data);
}
}
$("#btn-create-order").click(function() {
let aid = $("#address-list").val();//12
let cids = location.search.substr(1);//cids=4&cids=6&cids=8
$.ajax({
url: "/orders/create",
data: "aid=" + aid + "&" + cids,//aid=12&cids=4&cids=6&cids=8
type: "GET",
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
location.href = "payment.html";
} else {
alert("创建订单失败!" + json.message);
}
},
error: function(xhr) {
alert("创建订单数据时产生未知的异常" + xhr.status);
}
});
});
equals()的重写。。
快捷键补充:alt+鼠标左键,一键选中多行
Nginx 是什么、为什么、怎么用? - 知乎 (zhihu.com)
*/
Integer insertOrderItem(OrderItem orderItem);
}
4.xml编写
```xml
insert into t_order (
uid, recv_name, recv_phone, recv_province, recv_city, recv_area, recv_address,
total_price,status, order_time, pay_time, created_user, created_time, modified_user,
modified_time
) values (
#{uid}, #{recvName}, #{recvPhone}, #{recvProvince}, #{recvCity}, #{recvArea},
#{recvAddress}, #{totalPrice}, #{status}, #{orderTime}, #{payTime}, #{createdUser},
#{createdTime}, #{modifiedUser}, #{modifiedTime}
)
insert into t_order_item (
oid, pid, title, image, price, num, created_user,
created_time, modified_user, modified_time
) values (
#{oid}, #{pid}, #{title}, #{image}, #{price}, #{num}, #{createdUser},
#{createdTime}, #{modifiedUser}, #{modifiedTime}
)
5.测试类
public class OrderMapperTests {
@Autowired
private OrderMapper orderMapper;
@Test
public void insertOrderTest(){
Order order = new Order();
order.setUid(12);
order.setRecvName("张某");
order.setRecvPhone("123456");
orderMapper.insertOrder(order);
}
@Test
public void insertOrderItemTest(){
OrderItem orderItem = new OrderItem();
orderItem.setOid(1);
orderItem.setPid(1000003);
orderItem.setTitle("名称");
orderMapper.insertOrderItem(orderItem);
}
}
1.在IAddressService接口中定义根据收货地址
@Override
public Address getByAid(Integer aid, Integer uid) {
Address address = addressMapper.findByAid(aid);
if (address == null){
throw new AddressNotFoundException("收货地址不存在");
}
if (!address.getUid().equals(uid)){
throw new AccessDeniedException("非法数据异常");
}
address.setProvinceCode(null);
address.setCityCode(null);
address.setAreaCode(null);
address.setCreatedUser(null);
address.setCreatedTime(null);
address.setModifiedUser(null);
address.setModifiedTime(null);
return address;
}
设计请求:
/orders/create
GET
Integer aid, Integer[] cids, HttpSession session
JsonResult
处理请求:
@RequestMapping("orders")
@RestController
public class OrderController extends BaseController {
@Autowired
private IOrderService orderService;
@RequestMapping("create")
public JsonResult<Order> create(Integer aid, Integer[] cids, HttpSession session){
Integer uid = getuidFromSession(session);
String username = getUsernameFromSession(session);
Order data = orderService.create(aid, uid, username, cids);
return new JsonResult<>(OK,data);
}
}
$("#btn-create-order").click(function() {
let aid = $("#address-list").val();//12
let cids = location.search.substr(1);//cids=4&cids=6&cids=8
$.ajax({
url: "/orders/create",
data: "aid=" + aid + "&" + cids,//aid=12&cids=4&cids=6&cids=8
type: "GET",
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
location.href = "payment.html";
} else {
alert("创建订单失败!" + json.message);
}
},
error: function(xhr) {
alert("创建订单数据时产生未知的异常" + xhr.status);
}
});
});
equals()的重写。。
快捷键补充:alt+鼠标左键,一键选中多行
Nginx 是什么、为什么、怎么用? - 知乎 (zhihu.com)