```
#### 处理请求
```java
/**
* MultipartFile是SpringMVC提供的一个接口,用于封装上传的文件对象,SprintBoot整合了Spring MVC,
* 只需要在请求处理的参数列表上设置MultipartFile类型的参数,然后SpringBoot会自动将上传的文件封装到MultipartFile类型的参数中
*
* @RequestParam 表示请求中的参数,将请求的参数注入到方法的参数列表中,
* @RequestParam("name")表示从请求参数里获取名为 name 的值,并将其绑定到方法参数 name 上
*
* 处理头像上传的请求
* @param session session对象
* @param file 上传的文件对象,SpringBoot会将上传的文件封装到MultipartFile类型的参数中
* @return JsonResult对象,包含头像的路径
*/
@RequestMapping("change_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("文件超出" + AVATAR_MAX_SIZE + "限制!" );
}
// 判断文件类型是否匹配
String contentType = file.getContentType();
if (!AVATAR_TYPE.contains(contentType)) {
throw new FileTypeException("文件类型不支持" + contentType + "");
}
// 上传的文件../upload/文件.png
String parent =
session.getServletContext().
getRealPath("upload");
// file对象指向这个路径,file是否存在
File dir = new File(parent);
if (!dir.exists()) {
dir.mkdirs(); // 创建目录
}
// 获取文件的原始文件名
String originalFilename = file.getOriginalFilename();
// 获取文件的后缀名
int index = originalFilename.lastIndexOf(".");
String suffix = originalFilename.substring(index);
String newFileName = System.currentTimeMillis() + suffix;
// 创建一个新的文件对象,用于保存上传的文件
File dest = new File(dir, newFileName); // 空文件
// 参数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/文件.png
String avatar = "/upload" + newFileName;
userService.changeAvatar(uid, avatar, username);
// 返回头像的路径, 将来可以通过头像的路径访问头像
return new JsonResult<>(OK, avatar);
}
```
### 4. 上传头像-前端页面
在upload页面编写上传头像的代码
> 说明:如果使用表单进行文件上传,需要给表单显示添加一个属性enctype="multipart/from-data"声明出来,不会将目标文件的数据结构做修改再上传,不同于字符串
### 5. 解决bug
#### 5.1更改默认的大小限制
SpringMVC默认1MB文件可以上传,手动修改SpringMVC默认上传文件的大小
方式1:直接在配置文件中修改
```properties
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=15MB
```
方式2:需要采用Java代码的形式来设置文件的上传限制。主类当中配置,可以定义一个方法,必须使用@bean修饰。在类的前面添加@configuration进行修饰。方法返回值是MultipartConfigElement类型
```java
@Bean
// public MultipartConfigElement getMultipartConfigElement() {
// // 创建一个配置的工厂类对象
// MultipartConfigFactory factory = new MultipartConfigFactory();
//
// // 设置文件的最大大小
// factory.setMaxFileSize(DataSize.of(10, DataUnit.MEGABYTES));
// factory.setMaxRequestSize(DataSize.of(15, DataUnit.MEGABYTES));
//
// //通过工厂类来创建一个配置对象
// return factory.createMultipartConfig();
// }
```
#### 5.2 显示头像
在页面中提交Ajax请求来提交文件,提交完成后返回json串,解析出data中的数据,设置到imag头像标签的src属性中
- serialize():可以将表单中的数据自动拼接为key=value的结果进行提交给服务器,一般是提交的普通的控件类型中的数据(text\password\radio\chekbx等)
- FormData类:将表单中的数据保持原有的结构进行数据的提交。
```js
new FormData($("#form")[0]); //文件类型的数据可以使用FormData对象进行存储
```
- Ajax默认处理数据是按照字符串的形式进行处理,以及默认采用字符串的形式提交数据。关闭这两个默认
```js
processData: false, // 告诉jQuery不要去处理发送的数据
contentType: false, // 告诉jQuery不要去设置Content-Type请求头
```
#### 5.3 登录后显示头像
可以在更新头像成功后,将服务器返回的头像路径保存在客户端的cookie对象中,每次检测到用户打开上传头像页面,这个页面中通过read()方法自动去读取cookie中头像路径并设置到src属性中
1.设置cookie中的头像路径
导入cookie.js文件
```
```
调用cookie的方法
```
$.cookie(key, value, time); // 单位:天
```
2.在upload.html中先引入cookie.js文件
```
```
3.登录成功后将avatar设置到cookie中
```js
if (json.data.avatar) {
// 定义 cookie 配置
const cookieOptions = { expires: 7 }; // 单位:天
try {
// 将服务器返回的头像路径保存到 cookie 中
$.cookie("avatar", json.data.avatar, cookieOptions);
} catch (error) {
console.error("保存头像路径到 cookie 时出错:", error);
}
} else {
console.log("服务器未返回有效的头像路径");
}
console.log(json.data.username)
```
4.在upload.html中通过read()函数自动读取cookie的数据。
```js
$(document).ready(function () {
let avatar = $.cookie("avatar");
console.log(avatar)
if (avatar) {
$("#img-avatar").attr("src", avatar);
}
})
```
#### 5.4 显示最新头像
更新完头像后,将最新的头像地址,再次保存到cookie中,同名保存回家覆盖原有cookie中的值
```js
$.cookie("avatar", json.data, {expirse: 7})
```
## 商品热销排名
### 1. 商品-创建数据表
```mysql
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;
```
### 2. 商品-创建实体类
在com.cy.store包下的entity包下创建Product类
```java
public class Product extends BaseEntity implements Serializable {
private Integer id;
private Integer categoryId;
private String itemType;
private String title;
private String sellPoint;
private Long price;
private Integer num;
private String image;
private Integer status;
private Integer priority;
// generate: Getter and Setter, generate hashCode() and equals(),toStirng()
}
```
### 3. 商品热销排行-持久层
#### 3.1 规划SQL语句
```mysql
select * from t_product where status=1 order by priority DESC limit 0,4
```
#### 3.2 接口和抽象方法
在com.cy.store.mapper文件下创建ProductMapper接口文件
```java
package com.cy.store.mapper;
import com.cy.store.entity.Product;
import java.util.List;
/**
* 处理商品数据的持久层接口
*/
public interface ProductMapper {
/**
* 查询热销商品列表
* @return 热销商品列表
*/
List findHotList();
}
```
#### 3.3 编写SQL映射
在Resource包下的mapper包下创建ProductMapper.xml
```xml
```
#### 3.4 单元测试
```java
package com.cy.store.mapper;
import com.cy.store.entity.Address;
import com.cy.store.entity.Product;
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;
import java.util.List;
// 测试类 不会随同项目一起打包
@SpringBootTest
@RunWith(SpringRunner.class)
// @RunWith()表示启动这个单元测试类,必须要加的注解,括号中内容是一个类,是SpringRunner的子类
public class ProductMapperTests {
@Autowired
private ProductMapper productMapper;
@Test
public void findHotList() {
List list = productMapper.findHotList();
for (Product product : list) {
System.err.println(product);
}
}
}
```
### 4. 商品热销排行-业务层
#### 4.1 异常规划
无(查询功能一般不需要额外的异常)
#### 4.2 接口和抽象方法
在com.cy.store.service包下创建接口IProductService
```java
package com.cy.store.service;
import com.cy.store.entity.Product;
import java.util.List;
public interface IProductService {
/**
* 查询热销商品列表
* @return 热销商品列表
*/
List findHostList();
}
```
#### 4.3 实现方法
在com.cy.store.service.impl包下创建ProductServiceImpl类
```java
package com.cy.store.service.impl;
import com.cy.store.entity.Product;
import com.cy.store.mapper.ProductMapper;
import com.cy.store.service.IProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ProductServiceImpl implements IProductService {
@Autowired
private ProductMapper productMapper;
@Override
public List findHostList() {
List list = productMapper.findHotList();
for (Product product : list) {
product.setPriority(null);
product.setCreatedTime(null);
product.setCreatedUser(null);
product.setModifiedTime(null);
product.setModifiedUser(null);
}
return list;
}
}
```
#### 4.4 单元测试
```java
package com.cy.store.service;
import com.cy.store.entity.District;
import com.cy.store.entity.Product;
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;
// 测试类 不会随同项目一起打包
@SpringBootTest
@RunWith(SpringRunner.class)
// @RunWith()表示启动这个单元测试类,必须要加的注解,括号中内容是一个类,是SpringRunner的子类
public class ProductServiceTests {
@Autowired
private IProductService productService;
@Test
public void findHotList() {
List list = productService.findHotList();
for (Product product : list) {
System.err.println(product);
}
}
}
```
### 5. 控制层
#### 5.1 处理异常
无(查询操作一般不做处理)
#### 5.2 设计请求
```
url: /product/hot_list
type: get
data: 无
return: JsonResult>
是否拦截:否,需要将index.html和product/** 添加到白名单
```
在LoginInterceptorConfigurer类中将index.html和product/** 添加到白名单
```java
patterns.add("/web/index.html)
patterns.add("products/**")
```
#### 5.3 处理请求
```java
package com.cy.store.controller;
import com.cy.store.entity.Product;
import com.cy.store.service.IProductService;
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 java.util.List;
@RestController
@RequestMapping("products")
public class ProductController extends BaseController{
@Autowired
private IProductService productService;
@RequestMapping("hot_list")
public JsonResult> getHotList() {
List data = productService.findHotList();
return new JsonResult<>(OK, data);
}
}
```
单元测试
访问http://localhost:8080/products/hot_list
### 6. 前端页面
1.Index.html页面183行代码给热销排行的div标签设置id属性
```html
```
加载页面时发送请求展示热销商品
```
```
## 展示商品详情
### 1. 持久层
#### 1.1 规划SQL语句
```mysql
select * from t_pruduct where id=?
```
#### 1.2 接口和抽象方法
```java
/**
* 根据id查询商品数据
* @param id 商品id
* @return 匹配的商品数据,如果没有匹配的数据,则返回null
*/
Product findById(Integer id);
```
#### 1.3 编写SQL语句
```mysql
```
单元测试
```java
@Test
public void findById() {
Product product = productMapper.findById(10000001);
System.err.println(product);
}
```
### 2. 业务层
#### 2.1 规划异常
商品数据不存在,抛出ProductNotFoundException
```java
package com.cy.store.service.ex;
public class ProductNotFoundException extends RuntimeException {
public ProductNotFoundException() {
}
public ProductNotFoundException(String message) {
super(message);
}
public ProductNotFoundException(String message, Throwable cause) {
super(message, cause);
}
public ProductNotFoundException(Throwable cause) {
super(cause);
}
public ProductNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
```
#### 2.2 接口和抽象方法
```java
/**
* 根据id查询商品数据
* @param id 商品id
* @return 匹配的商品数据,如果没有匹配的数据,则返回null
*/
Product findById(Integer id);
```
#### 2.3 实现方法
```java
@Override
public Product findById(Integer id) {
Product product = productMapper.findById(id);
if (product == null) {
throw new ProductNotFoundException("商品数据不存在!");
}
product.setPriority(null);
product.setCreatedTime(null);
product.setCreatedUser(null);
product.setModifiedTime(null);
product.setModifiedUser(null);
return product;
}
```
单元测试
### 3. 控制层
#### 3.1 处理异常
BaseController中t添加
```
else if (e instanceof ProductNotFoundException) {
result.setState(5006);
result.setMessage("插入数据产生了未知的异常!!!");
}
```
#### 3.2 设计请求
```
url:/products/details/{id}
data: @PathVariable("id") Integer id
type:GET
return: JsonResult
```
#### 3.3 实现请求
```java
else if (e instanceof ProductNotFoundException) {
result.setState(5006);
result.setMessage("插入数据产生了未知的异常!!!");
}
```
单元测试
访问http://localhost:8080/products/details/10000042
### 4. 前端页面
拦截器释放,非登录加载../js/jquery-getUrlParam.js,以便使用$.getUrlParam("id")函数获取网页的id属性发送给后端请求
```
patterns.add("/js/**");
```
页面加载时发送Ajax请求
```js
```
# 新增收货地址
### 1. 设计数据表
```mysql
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;
```
### 2. 新增收货地址-创建实体类
```java
package com.cy.store.entity;
import java.util.Objects;
public class Address extends BaseEntity{
private Integer aid;
private Integer uid;
private String name;
private String provinceNme;
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;
public Integer getAid() {
return aid;
}
public void setAid(Integer aid) {
this.aid = aid;
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getProvinceNme() {
return provinceNme;
}
public void setProvinceNme(String provinceNme) {
this.provinceNme = provinceNme;
}
public String getProvinceCode() {
return provinceCode;
}
public void setProvinceCode(String provinceCode) {
this.provinceCode = provinceCode;
}
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
public String getCityCode() {
return cityCode;
}
public void setCityCode(String cityCode) {
this.cityCode = cityCode;
}
public String getAreaName() {
return areaName;
}
public void setAreaName(String areaName) {
this.areaName = areaName;
}
public String getAreaCode() {
return areaCode;
}
public void setAreaCode(String areaCode) {
this.areaCode = areaCode;
}
public String getZip() {
return zip;
}
public void setZip(String zip) {
this.zip = zip;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
public Integer getIsDefault() {
return isDefault;
}
public void setIsDefault(Integer isDefault) {
this.isDefault = isDefault;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Address address1 = (Address) o;
return Objects.equals(getAid(), address1.getAid()) && Objects.equals(getUid(), address1.getUid()) && Objects.equals(getName(), address1.getName()) && Objects.equals(getProvinceNme(), address1.getProvinceNme()) && Objects.equals(getProvinceCode(), address1.getProvinceCode()) && Objects.equals(getCityName(), address1.getCityName()) && Objects.equals(getCityCode(), address1.getCityCode()) && Objects.equals(getAreaName(), address1.getAreaName()) && Objects.equals(getAreaCode(), address1.getAreaCode()) && Objects.equals(getZip(), address1.getZip()) && Objects.equals(getAddress(), address1.getAddress()) && Objects.equals(getPhone(), address1.getPhone()) && Objects.equals(getTel(), address1.getTel()) && Objects.equals(getTag(), address1.getTag()) && Objects.equals(getIsDefault(), address1.getIsDefault());
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), getAid(), getUid(), getName(), getProvinceNme(), getProvinceCode(), getCityName(), getCityCode(), getAreaName(), getAreaCode(), getZip(), getAddress(), getPhone(), getTel(), getTag(), getIsDefault());
}
@Override
public String toString() {
return "Address{" +
"aid=" + aid +
", uid=" + uid +
", name='" + name + '\'' +
", provinceNme='" + provinceNme + '\'' +
", provinceCode='" + provinceCode + '\'' +
", cityName='" + cityName + '\'' +
", cityCode='" + cityCode + '\'' +
", areaName='" + areaName + '\'' +
", areaCode='" + areaCode + '\'' +
", zip='" + zip + '\'' +
", address='" + address + '\'' +
", phone='" + phone + '\'' +
", tel='" + tel + '\'' +
", tag='" + tag + '\'' +
", isDefault=" + isDefault +
'}';
}
}
```
### 3. 新增收货地址-持久层
#### 3.1 各功能的开发顺序
当前收货地址模块:列表的展示、修改、删除、设置默认、新增收货地址。开发顺序:新增收货地址-列表的展示-设置默认收货地址-删除收货地址-修改收货地址
#### 3.2 规划需要执行的SQL语句
1.新增收货地址-插入语句
```mysql
insert into t_address (除aid外字段列表) values (字段值列表);
```
2.一个用户的收货地址规定最多只能有20条数据对应.插入用户数据之前先做查询操作.收货地址逻辑控制的异常
```mysql
select count(*) from t_address where uid=?
```
#### 3.3 接口和抽象方法
创建一个接口AddressMapper,在这个接口定义两个SQL语句的抽象方法
```java
package com.cy.store.mapper;
import com.cy.store.entity.Address;
/* 表示收货地址的持久层接口 */
public interface AddressMapper {
/**
* 插入用户的收货地址数据
* @param address 收货地址数据
* @return 受影响的行数
*/
Integer insert(Address address);
/**
* 根据uid统计用户的收货地址数据条数
* @param uid 用户的id
* @return 匹配的收货地址数据总数,如果没有匹配的数据,则返回null
*/
Integer countByUid(Integer uid);
}
```
#### 3.4 配置SQL映射
创建一个AddressMapper.xml映射文件,在这个方法中添加抽象方法映射
```xml
INSERT INTO t_address (
uid, name, province_name, province_code, city_name, city_code, area_name, area_code,
zip, address, phone, is_default, created_user, created_time, modified_user, modified_time
) VALUES (
#{aid}, #{name}, #{provinceNme}, #{provinceCode}, #{cityName}, #{cityCode}, #{areaName},
#{areaCode}, #{zip}, #{address}, #{phone}, #{tel}, #{tag}, #{isDefault},
d#{createdUser},
#{createdTime},
#{modifiedUser},
#{modifiedTime}
)
```
#### 3.5 单元测试
```java
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;
// 测试类 不会随同项目一起打包
@SpringBootTest
@RunWith(SpringRunner.class)
// @RunWith()表示启动这个单元测试类,必须要加的注解,括号中内容是一个类,是SpringRunner的子类
public class AddressMapperTests {
@Autowired
private AddressMapper addressMapper;
@Test
public void insert() {
Address address = new Address();
address.setUid(7);
address.setName("江熠");
address.setPhone("1234567");
addressMapper.insert(address);
}
@Test
public void countByUid() {
Integer count = addressMapper.countByUid(7);
System.out.println(count);
}
}
```
### 4. 新增收货地址-业务层
##### 4.1 规划异常
如果用户是第一次插入用户的收货地址,规则:当用户插入的数据是第一条,需要将当前的地址作为默认的收货地址,如果查询到的统计总数为0则将当前地址的is_default设置为1.查询统计的结果为0不代表异常
查询到的结果大于20,这时候需要抛出业务控制的异常AddressCountLimitException异常。
```java
package com.cy.store.controller.ex;
import com.cy.store.service.ex.ServiceException;
/* 表示收货地址数量超出限制的异常(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);
}
}
```
插入数据时异常,需要重复创建
#### 4.2 接口与抽象方法
1.创建一个IAddressService接口,在其中定义业务的抽象方法。
```java
package com.cy.store.service;
import com.cy.store.entity.Address;
/* 处理收货地址数据的业务层接口 */
public interface IAddressService {
/**
* 新增收货地址
* @param uid 用户的id
* @param username 用户名
* @param address 收货地址数据
*/
void addNewAddress(Integer uid, String username, Address address);
}
```
#### 4.3 实现抽象方法
创建一个AddressServiceImpy实现类,去实现接口的抽象方法
配置项设置最多地址条数
```
# spring读取配置文件的数据: @value("${user.address.max-count}"}
user.address.max-count=20
```
```java
package com.cy.store.service.impl;
import com.cy.store.controller.ex.AddressCountLimitException;
import com.cy.store.entity.Address;
import com.cy.store.mapper.AddressMapper;
import com.cy.store.service.IAddressService;
import com.cy.store.service.ex.InsertException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class AddressServiceImpl 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 isDefault
address.setUid(uid);
Integer isDefault = count == 0 ? 1 : 0; // 0: 不是默认地址 1: 是默认地址
address.setIsDefault(isDefault);
// 补全四项日志
address.setCreatedUser(username);
address.setModifiedUser(username);
address.setCreatedTime(new Date());
address.setModifiedTime(new Date());
// 插入数据
Integer row = addressMapper.insert(address);
if (row != 1) {
throw new InsertException("插入用户的收货地址数据时产生未知的异常!");
}
}
}
```
#### 4.4 单元测试
新建一个AddressService测试类
```java
package com.cy.store.service;
import com.cy.store.entity.Address;
import com.cy.store.entity.User;
import com.cy.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;
// 测试类 不会随同项目一起打包
@SpringBootTest
@RunWith(SpringRunner.class)
// @RunWith()表示启动这个单元测试类,必须要加的注解,括号中内容是一个类,是SpringRunner的子类
public class AddressServiceTests {
@Autowired
private IAddressService addressService;
@Test
public void addNewAddress() {
Address address = new Address();
address.setPhone("123456789");
address.setName("江熠");
addressService.addNewAddress(6, "管理员", address);
}
}
```
### 5. 新增收货地址-控制层
#### 5.1 处理异常
业务层抛出了收货地址总数超标的异常,在BaseController中进行捕获
```java
else if (e instanceof AddressCountLimitException) {
result.setState(4003);
result.setMessage("用户的收货地址超出上限的异常!");
}
```
#### 5.2 设计请求
```
/address/add_new_address
post
Address address, HttpSession session
JsonResult
```
#### 5.3 处理请求
在控制层创建AddressController来处理用户收货地址的请求和响应
```java
package com.cy.store.controller;
import com.cy.store.entity.Address;
import com.cy.store.service.IAddressService;
import com.cy.store.util.JsonResult;
import jakarta.servlet.http.HttpSession;
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;
@RequestMapping("address")
@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);
}
}
```
#### 5.4 单元测试
登录后,访问http://localhost:8080/address/add_new_address?name=Tom&phone=17284345进行测试
### 6. 新增收货地址-前端页面
```js
```
# 获取省市区列表
### 1. 获取省市区列表-数据库
```mysql
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:父区域代码号,code:自身代码号,name:自身名字,省的父代码号+86.
### 2. 获取省市区列表-实体类
创建一个District实体类
```java
/* 省、市、区的实体类 */
public class District {
private Integer id;
private String parent;
private String code;
private String name;
```
### 2. 获取省市区列表-持久层类
#### 2.1 规划SQL语句
查询语句,根据父代号进行查询
```mysql
select * from t_dict_district where parent=? order by code ASC
```
#### 2.2. 接口和抽象方法
创建DistrictMapper接口
```java
package com.cy.store.mapper;
import com.cy.store.entity.District;
import java.util.List;
public interface DistrictMapper{
/**
* 根据父级代号查询子级数据列表
* @param parent 父级代号
* @return 子级数据列表
*/
List findByParent(String parent);
}
```
#### 2.3 编写SQL映射
```xml
```
#### 2.4 单元测试
```java
package com.cy.store.mapper;
import com.cy.store.entity.Address;
import com.cy.store.entity.District;
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;
// 测试类 不会随同项目一起打包
@SpringBootTest
@RunWith(SpringRunner.class)
// @RunWith()表示启动这个单元测试类,必须要加的注解,括号中内容是一个类,是SpringRunner的子类
public class DistrictMapperTests {
@Autowired
private DistrictMapper districtMapper;
@Test
public void findByParentId() {
List list = districtMapper.findByParent("210100");
for(District d : list) {
System.out.println(d);
}
}
}
```
### 3. 获取省市区列表-业务层
#### 3.1 创建接口IDistrictService,定义抽象方法
```java
package com.cy.store.service;
import com.cy.store.entity.Address;
import com.cy.store.entity.District;
import java.util.List;
/* 处理收货地址数据的业务层接口 */
public interface IDistrictService {
/**
* 根据父级代号查询子级数据列表(省市区)
* @param parent 父级代号
* @return 子级数据列表
*/
List getByParent(String parent);
}
```
#### 3.2 实现接口
创建DistrictServiceImpl实现类,实现抽象方法
```java
package com.cy.store.service.impl;
import com.cy.store.entity.District;
import com.cy.store.mapper.DistrictMapper;
import com.cy.store.service.IDistrictService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DistrictServiceImpl implements IDistrictService {
@Autowired
private DistrictMapper districtMapper;
/**
* 根据父级代号查询子级数据列表(省市区)
* @param parent 父级代号
* @return 子级数据列表
*/
@Override
public List getByParent(String parent) {
List list = districtMapper.findByParent(parent);
/**
* 网络数据传输时为了尽量避免无效数据的传输,需要将无效数据设置为null
* 可以节省流量,另一方面提高程序的性能
* id和parent属性设置为null,只传输name属性和code属性
*/
for (District d : list) {
d.setId(null);
d.setParent(null);
}
return list;
}
}
```
#### 3.3 单元测试
```java
package com.cy.store.service.impl;
import com.cy.store.entity.District;
import com.cy.store.mapper.DistrictMapper;
import com.cy.store.service.IDistrictService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DistrictServiceImpl implements IDistrictService {
@Autowired
private DistrictMapper districtMapper;
/**
* 根据父级代号查询子级数据列表(省市区)
* @param parent 父级代号
* @return 子级数据列表
*/
@Override
public List getByParent(String parent) {
List list = districtMapper.findByParent(parent);
/**
* 网络数据传输时为了尽量避免无效数据的传输,需要将无效数据设置为null
* 可以节省流量,另一方面提高程序的性能
* id和parent属性设置为null,只传输name属性和code属性
*/
for (District d : list) {
d.setId(null);
d.setParent(null);
}
return list;
}
}
```
### 4. 获取省市区列表-控制层
#### 4.1 规划异常
无
#### 4.2 设计请求
```
/districts/
get
String parent
JsonResult>
```
#### 4.3 实现请求
创建DistrictController类,来编写处理请求的方法
```java
package com.cy.store.controller;
import com.cy.store.entity.District;
import com.cy.store.service.IDistrictService;
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 java.util.List;
@RestController
@RequestMapping("districts")
public class DistrictController extends BaseController {
@Autowired
private IDistrictService districtService;
// districts开头的请求,都会被拦截,然后执行下面的方法
@RequestMapping({"/", ""})
public JsonResult> getByParent(String parent) {
List data = districtService.getByParent(parent);
return new JsonResult<>(OK, data);
}
}
```
districtsd请求添加白名单
```java
patterns.add("districts/**");
```
#### 单元测试
访问http://localhost:8080/districts?parent=86或http://localhost:8080/districts**/**?parent=610000进行测试
### 5. 获取省市区列表-前端页面
1.注释掉通过js完成省市区列表加载的js代码
```js
```
2.检查前端页面再提交省市区数据时是否有相关name和id属性
3.运行前端看是否可以正常保存数据(除省市区外)
## 获取省市区的名称
### 1. 获取省市区的名称-持久层
#### 1.1 规划SQL语句
根据当前code来获取当前省市区的名称
```mysql
select name from t_dict_district where code=?
```
#### 1.2 抽象接口和方法
DistrictMapper中定义抽象方法
```java
String findNameByCode(String code);
```
#### 1.3 编写映射
DistrictMapper.xml中
```xml
```
#### 1.4 单元测试
```java
@Test
public void findByCode() {
String name = districtMapper.findNameByCode("610000");
System.out.println(name);
}
```
### 2. 获取省市区的名称-业务层
#### 2.1 规划异常
无
#### 2.2 接口和抽象方法
```java
/**
* 根据代号查询名称
* @param code 代号
* @return 名称
*/
String getNameByCode(String code);
```
#### 2.3 方法实现
```java
@Override
public String getNameByCode(String code) {
return districtMapper.findNameByCode(code);
}
```
### 3. 获取省市区的名称-控制层
#### 3.1 添加依赖
AdderssServiceimpl添加地址层依赖于IDistrictService层
```java
// 添加用户的收货地址数据时业务层依赖于IDistrictService接口,所以需要将IDistrictService接口的实现类注入到业务层
@Autowired
private IDistrictService districtService;
```
#### 3.2 增添依赖数据
在addNewAddress方法中将districtService接口中获取的省市区数据转移到address对象,这个对象就包含收货地址的所有数据了. code值通过前端传输包含在Address对象里
```java
// 补全数据:省市区的名称
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);
```
### 4. 获取省市区-前端页面
1.addAddress.html页面中来编写对应的省市区展示及根据用户的不同选择来限制对应标签的内容。
2.编写相关事件代码
```js
```
## 收货地址列表展示
### 1. 收货地址列表展示-持久层
#### 1.1 规划SQL语句
数据库查询操作
```mysql
select * from t_address where uid=? order by is_default DESC,created_time DESC;
```
#### 1.2 接口和抽象方法
```java
/**
* 根据uid查询用户的收货地址数据列表
* @param uid 用户的id
* @return 匹配的收货地址数据列表,如果没有匹配的数据,则返回null
*/
List findByUid(Integer uid);
```
#### 1.3 SQL映射
```xml
```
#### 1.4 单元测试
```java
@Test
public void findByUid() {
List list = addressMapper.findByUid(7);
for (Address a : list) {
System.out.println(a);
}
}
```
### 2. 收货地址列表展示-业务层
#### 2.1 规划异常
无
#### 2.2 接口和抽象方法
```java
/**
* 根据用户的id查询收货地址列表
* @param uid 用户的id
* @return 该用户的收货地址列表
*/
List getAddressByUid(Integer uid);
```
#### 2.3 实现方法
```java
@Override
public List getAddressByUid(Integer uid) {
List list = addressMapper.findByUid(uid);
for (Address address : list) {
// address.setAid(null);
// address.setUid(null);
address.setProvinceCode(null);
address.setCityCode(null);
address.setAreaCode(null);
address.setZip(null);
address.setTel(null);
address.setCreatedTime(null);
address.setModifiedTime(null);
address.setCreatedUser(null);
address.setModifiedUser(null);
address.setIsDefault(null);
}
return list;
}
```
#### 2.4 单元测试
### 3. 收货地址列表展示-控制层
#### 3.1 设计请求
```
/addresses
HttpSession session
GET
JsonResult>
```
#### 3.2 实现请求
```java
@RequestMapping({"/", ""})
public JsonResult> getAddressByUid(HttpSession session) {
Integer uid = getUidFromSession(session);
List data = addressService.getAddressByUid(uid);
return new JsonResult<>(OK, data);
}
```
#### 3.3 单元测试
先登录后访问http://localhost:8080/address得到返回的数据列表
### 4. 收货地址列表展示-前端页面
在address.html页面编写查询用户收货地址的展示列表
```js
```
## 设置默认收货地址
### 1. 持久层
#### 1.1 规划SQL语句
1.检测当前用户设置为默认收货地址的数据是否存在
```mysql
select * from t_address where aid=?
```
2.修改用户的某人地址之前,现将所有的收货地址设置为非默认
```mysql
update t_address set is_default=0 where uid=?
```
3.将用户当前选中的这条记录设为默认
```mysql
update t_address set is_default=1, modified_user=?, modified_time=? where aid=?
```
#### 1.2 设计抽象方法
AddressMapper接口中
```java
/**
* 根据aid查询收货地址数据
* @param aid 收货地址数据的id
* @return 匹配的收货地址数据,如果没有匹配的数据,则返回null
*/
Address findByAid(Integer aid);
/**
* 根据uid查询用户的最后修改的收货地址数据
* @param uid 用户的id
* @return 返回受影响的行数,如果没有匹配的数据,则返回null
*/
Integer updateNonDefault(Integer uid);
/**
* 根据aid更新收货地址数据为默认
* @param aid 收货地址数据的id
* @param modifiedUser 修改执行人
* @param modifiedTime 修改时间
* @return 返回受影响的行数,如果没有匹配的数据,则返回null
*/
Integer updateDefaultByAid(@Param("aid") Integer aid,
@Param("modifiedUser") String modifiedUser,
@Param("modifiedTime") Date modifiedTime);
```
#### 1.3 配置SQL映射
```xml
UPDATE t_address
SET is_default = 0
WHERE uid = #{uid}
UPDATE t_address
SET is_default = 1, modified_time = #{modifiedTime}, modified_user = #{modifiedUser}
WHERE aid = #{aid}
```
#### 1.4 单元测试
```java
@Test
public void findByAid() {
Address address = addressMapper.findByAid(9);
System.err.println(address);
}
@Test
public void update() {
addressMapper.updateNonDefault(7);
}
@Test
public void delete() {
addressMapper.updateDefaultByAid(9, "管理员", new Date());
}
```
### 2. 业务层
#### 2.1 规划异常
1.更新时产生未知的UpdateException异常。
2.访问的数据不是当前登录用户的收货数据,非法访问:AccessDeniedException
3.收货地址可能不存在异常:AddressNotFoundException
#### 2.2 接口和抽象方法
在接口IAddressService中编写抽象方法
```java
/**
* 修改某个用户的某收货地址为默认收货地址
* @param aid 收货地址id
* @param uid 用户的id
* @param username 修改执行人的用户名
*/
void setDefault(Integer aid,
Integer uid,
String username);
```
#### 2.3 方法实现
AddressServiceImpl开发
```java
@Override
public void setDefault(Integer aid, Integer uid, String username) {
Address address = addressMapper.findByAid(aid);
if (address == null) {
throw new AddressNotFoundException("收货地址不存在!");
}
// 检测当前获取的收货地址数据的归属是否为当前登录的用户
if (!address.getUid().equals(uid)) {
throw new AccessDeniedException("非法访问!");
}
// 将所有的收货地址设置为非默认
Integer rows = addressMapper.updateNonDefault(uid);
if (rows < 1) {
throw new UpdateException("更新用户的收货地址数据时产生未知的异常!");
}
// 将指定的收货地址设置为默认
rows = addressMapper.updateDefaultByAid(aid, username, new Date());
if (rows != 1) {
throw new UpdateException("更新用户的收货地址数据时产生未知的异常!");
}
}
```
#### 2.4 单元测试
```java
@Test
public void setDefaultAddress() {
addressService.setDefault(8, 7, "管理员");
}
```
### 3. 控制层
#### 3.1 处理异常
BaseController中统一处理
```java
else if (e instanceof AddressNotFoundException) {
result.setState(4004);
result.setMessage("用户的收货地址不存在的异常!");
} else if (e instanceof AccessDeniedException) {
result.setState(4005);
result.setMessage("非法访问的异常!");
}
```
#### 3.2 设计请求
```
/address/{aid}/set_default // Restful风格
// @PathVariable("aid") Integer aid 把参数aid注入到@PathVariable("aid")
@PathVariable("aid) Integer aid, HttpSession session
POST
JsonResult
```
#### 3.3 实现请求
```java
@RequestMapping("{aid}/set_default")
public JsonResult setDefault(@PathVariable("aid") Integer aid,
HttpSession session) {
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
addressService.setDefault(aid, uid, username);
return new JsonResult<>(OK);
}
```
#### 3.4 单元测试
登录后访问http://localhost:8080/address/9/set_default
### 前端页面
1.给设置默认收货地址按钮添加一个onclick属性,指向一个方法的调用,在这个方法中完成Ajax请求
```js
let tr = '\n' +
'| #{tag} | \n' +
'#{name} | \n' +
'#{address} | \n' +
'#{phone} | \n' +
' 修改 | \n' +
' 删除 | \n' +
// 更改
'设为默认 | \n' +
'
';
tr = tr.replace(/#{tag}/g, list[i].tag);
tr = tr.replace(/#{name}/g, list[i].name);
tr = tr.replace(/#{address}/g, list[i].provinceName + list[i].cityName + list[i].areaName + list[i].address);
tr = tr.replace(/#{phone}/g, list[i].phone);
// 更改
tr = tr.replace(/#{aid}/g, list[i].aid);
```
address.html页面点击“设置默认”按钮,发送Ajax请求.完成setDefault()方法声明和定义
```js
function setDefault(aid) {
$.ajax({
url: "/address/" + aid + "/set_default",
type: "POST",
dataType: "json",
success: function(json) {
if (json.state === 200) {
// 重新加载收货列表
showAddressList()
} else {
alert("设置默认收货地址失败!");
}
},
error: function (xhr) {
alert("设置默认收货地址时产生未知的错误或异常!" + xhr.status);
}
})
}
```
单元测试
## 删除收货地址
### 1. 持久层
#### 1.1 规划SQL语句
1.删除之前判断是否存在,判断该条地址的归属是否是当前用户。不用重复开发
2.执行删除收货地址信息
```
delete * from t_address where aid=?
```
3.如果用户删除的是默认的收货地址,将剩下的地址中的某一条设置为默认的收货地址。规则可以定义为最新修改的收货地址设为默认(根据modified_time字段)
```
limit n(n-1), pageSize n:页数(展示哪一页) pageSize:每页条数
select * from t_address where uid=? order by modified_time DESC limit 0,1
```
4.如果用户本身只有一条收货地址数据,删除后,其他操作就不进行
#### 1.2 设计抽象方法
AddressMapper下
```java
/**
* 根据aid删除收货地址数据
* @param aid 收货地址数据的id
* @return 返回受影响的行数,如果没有匹配的数据,则返回null
*/
Integer deleteByAid(Integer aid);
/**
* 根据uid查询用户的最后修改的收货地址数据
* @param uid 用户的id
* @return 匹配的收货地址数据,如果没有匹配的数据,则返回null
*/
Address findLastModifiedTime(Integer uid);
```
#### 1.3 编写SQL映射
```xml
DELETE from t_address
WHERE aid = #{aid}
```
#### 1.4 单元测试
```java
@Test
public void deleteByAid() {
addressMapper.deleteByAid(1);
}
@Test
public void findLastModifiedTime() {
System.out.println(addressMapper.findLastModifiedTime(7));
}
```
### 2. 业务层
#### 2.1 规划异常
在执行删除操作时可能产生未知的异常导致数据删除失败,抛出DeleteException,需要创建
```java
package com.cy.store.service.ex;
/**
* 数据删除异常
*/
public class DeleteException extends ServiceException{
public DeleteException() {
super();
}
public DeleteException(String message) {
super(message);
}
public DeleteException(String message, Throwable cause) {
super(message, cause);
}
public DeleteException(Throwable cause) {
super(cause);
}
protected DeleteException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
```
#### 2.2 设计抽象方法
IAddressService接口中
```
/**
* 删除用户选中的收货地址数据
* @param aid 收货地址id
* @param uid 用户id
* @param username 用户名
*/
void delete(Integer aid, Integer uid, String username);
```
#### 2.3 实现抽象方法
```
@Override
public void delete(Integer aid, Integer uid, String username) {
Address result = addressMapper.findByAid(aid);
if (result == null) {
throw new AddressNotFoundException("收货地址不存在!");
}
if (!result.getUid().equals(uid)) {
throw new AccessDeniedException("非法访问!");
}
Integer rows = addressMapper.deleteByAid(aid);
if (rows != 1) {
throw new DeleteException("删除数据产生未知的异常!");
}
Integer count = addressMapper.countByUid(uid);
if (count == 0) {
return;
}
// 判断是否是默认地址
// 需要将前面方法中的返回 isDefault=null注释掉
if (result.getIsDefault() == 0) {
return;
}
Address address = addressMapper.findLastModifiedTime(uid);
rows = addressMapper.updateDefaultByAid(
address.getAid(), username, new Date());
if (rows != 1) {
throw new UpdateException("更新用户的默认收货地址数据时产生未知的异常!");
}
}
```
#### 2.4 单元测试
```java
@Test
public void delete() {
// addressService.delete(2, 7, "管理员");
addressService.delete(8, 7, "管理员");
}
```
### 3. 控制层
#### 3.1 处理异常
1.需要处理DeleteException异常
BaseController中
```java
else if (e instanceof DeleteException) {
result.setState(5002);
result.setMessage("删除数据时产生未知的异常!");
}
```
#### 3.2 设计请求
```
/address/delete/{aid}
POST
Integer aid, HttpSession session
JsonResult
```
#### 3.3 实现请求
```java
@RequestMapping("delete/{aid}")
public JsonResult delete(@PathVariable("aid") Integer aid,
HttpSession session) {
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
addressService.delete(aid, uid, username);
return new JsonResult<>(OK);
}
```
### 4. 前端页面
1.给设置删除按钮添加一个onclick属性,指向一个方法的调用,在这个方法中完成Ajax请求
```js
let tr = '\n' +
'| #{tag} | \n' +
'#{name} | \n' +
'#{address} | \n' +
'#{phone} | \n' +
' 修改 | \n' +
// 修改
' 删除 | \n' +
'设为默认 | \n' +
'
';
tr = tr.replace(/#{tag}/g, list[i].tag);
tr = tr.replace(/#{name}/g, list[i].name);
tr = tr.replace(/#{address}/g, list[i].provinceName + list[i].cityName + list[i].areaName + list[i].address);
tr = tr.replace(/#{phone}/g, list[i].phone);
// 更改
tr = tr.replace(/#{aid}/g, list[i].aid);
```
在address.html页面添加
```js
function deleteByAid(aid) {
$.ajax({
url: "/address/delete" + aid,
type: "POST",
dataType: "json",
success: function(json) {
if (json.state === 200) {
// 重新加载收货列表
showAddressList()
} else {
alert("删除收货地址失败!");
}
},
error: function (xhr) {
alert("删除收货地址时产生未知的错误或异常!" + xhr.status);
}
})
}
```
单元测试
# 购物车
功能实现分两步:
1. 选择商品数量
2. 点击加入购物车后将用户所有购物车数据列表展示
3. 结算
## 加入购物车
### 数据表创建
```
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;
```
### 创建实体类
entity类创建实体类
```java
package com.cy.store.entity;
import java.util.Objects;
public class Cart extends BaseEntity{
private Integer cid;
private Integer uid;
private Integer pid;
private Long price;
private Integer num;
//...
}
```
### 1. 持久层
#### 1.1 规划SQL语句
1.向购物车表中插入数据
```mysql
insert into t_cart (cid除外) values (值列表)
```
2.当前商品已经在购物车中存在,则直接更新num即可
```mysql
update t_cart set num=? where cid=?
```
3.在插入或者更新具体执行哪个语句取决于数据库是否已有当前数据,先做查询操作
```mysql
select * from t_cart where pid=? and uid=?
```
#### 1.2 接口和抽象方法
CartMapper接口
```java
package com.cy.store.mapper;
import com.cy.store.entity.Cart;
import com.cy.store.entity.User;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
/** 购物车的持久层接口 */
//@Mapper
public interface CartMapper {
/**
* 插入购物车的数据
* @param cart 购物车数据
* @return 受影响的行数
*/
Integer insert(Cart cart);
/**
* 更新购物车某件商品的数量
* @param cid 购物车数据id
* @param num 更新数量
* @param modifiedUser 修改人
* @param modifiedTime 修改时间
* @return 受影响的行数
*/
Integer updateNumByCid(@Param("cid") Integer cid,
@Param("num") Integer num,
@Param("modifiedUser") String modifiedUser,
@Param("modifiedTime") Date modifiedTime);
/**
* 根据用户的id和商品的id查询购物车中的数据
* @param uid 用户id
* @param pid 商品id
* @return 购物车数据
*/
Cart findByUidAndPid(Integer uid, Integer pid);
}
```
#### 1.3 编写SQL映射文件
CartMapper.xml
```xml
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})
UPDATE t_cart
SET num=#{num}, modified_user=#{modifiedUser}, modified_time=#{modifiedTime}
WHERE cid=#{cid}
```
#### 1.4 单元测试
```java
package com.cy.store.mapper;
import com.cy.store.entity.Cart;
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;
// 测试类 不会随同项目一起打包
@SpringBootTest
@RunWith(SpringRunner.class)
// @RunWith()表示启动这个单元测试类,必须要加的注解,括号中内容是一个类,是SpringRunner的子类
public class CartMapperTests {
@Autowired
private CartMapper cartMapper;
@Test
public void insert() {
Cart cart = new Cart();
cart.setUid(7);
cart.setPid(10000011);
cart.setNum(2);
cart.setPrice(1000L);
cartMapper.insert(cart);
}
@Test
public void updateNumByCid() {
cartMapper.updateNumByCid(1, 4, "管理员", new Date());
}
@Test
public void findByUidAndPid() {
Cart cart = cartMapper.findByUidAndPid(7, 10000011);
System.err.println(cart);
}
}
```
### 2. 业务层
#### 2.1 异常处理
1. 插入时异常:InsertException
2. 更新数据(num)异常:UpdateException
#### 2.2 接口和抽象方法
创建一个IcartService接口文件
```
package com.cy.store.service;
public interface ICartService {
/**
* 将商品添加到购物车中
* @param uid 用户id
* @param pid 商品id
* @param num 新增之后的数量
* @param username 用户用
*/
void addToCart(Integer uid, Integer pid, Integer num, String username);
}
```
#### 2.3 实现接口
创建CartServiceImpl实现类
```java
package com.cy.store.service.impl;
import com.cy.store.entity.Cart;
import com.cy.store.mapper.CartMapper;
import com.cy.store.mapper.ProductMapper;
import com.cy.store.service.ICartService;
import com.cy.store.service.ex.InsertException;
import com.cy.store.service.ex.UpdateException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class CartServiceImpl implements ICartService {
// 购物车的业务层依赖于购物车的持久层和商品的持久层
@Autowired
private CartMapper cartMapper;
// 依赖于商品表
@Autowired
private ProductMapper productMapper;
@Override
public void addToCart(Integer uid, Integer pid,
Integer num, String username) {
// 查询当前添加的购物车是否已经存在
Cart result = cartMapper.findByUidAndPid(uid, pid);
Date date = new Date();
if (result == null) { // 表示这个商品没有被添加到购物车
// 填充数据
Cart cart = new Cart();
cart.setUid(uid);
cart.setPid(pid);
cart.setNum(num); // 加完之后的数量
cart.setPrice(productMapper.findById(pid).getPrice()); // 根据商品查询价格
cart.setCreatedUser(username);
cart.setCreatedTime(date);
cart.setModifiedUser(username);
cart.setModifiedTime(date);
Integer rows = cartMapper.insert(cart);
if (rows != 1) {
throw new InsertException("插入购物车数据时异常");
}
} else {
Integer amount = result.getNum() + num;
Integer rows = cartMapper.updateNumByCid(result.getCid(), amount,
username, new Date());
if (rows != 1) {
throw new UpdateException("更新购物车商品数量时异常!");
}
}
}
}
```
#### 2.4 单元测试
```java
package com.cy.store.service;
import com.cy.store.entity.User;
import com.cy.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;
// 测试类 不会随同项目一起打包
@SpringBootTest
@RunWith(SpringRunner.class)
// @RunWith()表示启动这个单元测试类,必须要加的注解,括号中内容是一个类,是SpringRunner的子类
public class CartServiceTests {
@Autowired
private ICartService cartService;
@Test
public void addToCart() {
// cartService.addToCart(7, 10000005, 1, "管理员");
cartService.addToCart(7, 10000005, 1, "管理员");
}
}
// 执行两次测试数量更改是否正确
```
### 3. 控制层
#### 3.1 异常处理
> 无
#### 3.2 设计请求
```
url:carts/add_to_cart
type:get
data:Interger pid, Integer num, HttpSession session
return:Json
```
#### 3.3 实现请求
创建CartController类
```java
package com.cy.store.controller;
import com.cy.store.service.ICartService;
import com.cy.store.util.JsonResult;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("carts")
public class CartController extends BaseController{
@Autowired
private ICartService cartService;
@RequestMapping("add_to_cart")
public JsonResult addToCart(Integer pid,
Integer num,
HttpSession session) {
cartService.addToCart(getUidFromSession(session),
pid,
num,
getUsernameFromSession(session));
return new JsonResult<>(OK);
}
}
```
单元测试:先登录后访问:http://localhost:8080/carts/add_to_cart?pid=10000015&num=5
### 4. 前端页面
在produce.html页面给【加入购物车】按钮加入点击时间,并发送Ajax请求
```js
$("#btn-add-to-cart").click(function () {
$.ajax({
url: "/carts/add_to_cart",
type: "get",
data: {
"pid": id,
"num": $("#num").val()
},
dataType: "json",
success: function (json) {
if (json.state === 200) {
alert("加入购物车成功!")
}else {
alert("加入购物车失败!")
}
},
error: function (xhr) {
alert("加入购物车时产生未知的异常" + xhr.status)
}
})
})
```
Ajax函数中data参数的设置方式:
- data: $("#form表单选择").serialize() 参数过多并且在一个表单中,字符串的提交
- data: new FormData($("#form表单选择")[0]). 适用于提交文件
- data: "username=tom". 适合参数值固定,并且参数值列表有限,可以手动拼接
- ```
let user = "tom"
data: "username=" + user
```
- 使用json格式提交
```
data: {
"username": "tom",
"age": 18,
"sex": 1
}
```
## 显示购物车列表
### 1. 持久层
#### 1.1 规划SQL语句
查询语句
```mysql
SELECT cid,
uid,
pid,
t_cart.price,
cart.num,
image,
title,
t_product.price as realPrice
FROM t_cart LEFT JOIN t_product ON t_cart.pid = t_product.id
WHERE uid=?
ORDER BY t_cart.created_time DESC;
```
VO: value object 值对象。当进行select查询时,查询的结果属于多张表中的内容,此时发现结果集不能使用pojo实体类来接收(pojo实体类不能包含**多表查询**出来的结果)。解决方法:重新构建一个新的对象用来存储查询出来的结果集对应的映射,所以把这个结果集的对象称为值对象
在store包下创建vo包,创建CartVO
```java
package com.cy.store.vo;
import java.io.Serializable;
import java.util.Objects;
/* 购物车数据的VO类 */
public class CartVo implements Serializable {
private Integer cid;
private Integer uid;
private Integer pid;
private Long price;
private Integer num;
private String image;
private String title;
private Long realPrice;
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public Integer getPid() {
return pid;
}
public void setPid(Integer pid) {
this.pid = pid;
}
public Long getPrice() {
return price;
}
public void setPrice(Long price) {
this.price = price;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Long getRealPrice() {
return realPrice;
}
public void setRealPrice(Long realPrice) {
this.realPrice = realPrice;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
CartVo cartVo = (CartVo) o;
return Objects.equals(cid, cartVo.cid) && Objects.equals(uid, cartVo.uid) && Objects.equals(pid, cartVo.pid) && Objects.equals(price, cartVo.price) && Objects.equals(num, cartVo.num) && Objects.equals(image, cartVo.image) && Objects.equals(title, cartVo.title) && Objects.equals(realPrice, cartVo.realPrice);
}
@Override
public int hashCode() {
return Objects.hash(cid, uid, pid, price, num, image, title, realPrice);
}
@Override
public String toString() {
return "CartVo{" +
"cid=" + cid +
", uid=" + uid +
", pid=" + pid +
", price=" + price +
", num=" + num +
", image='" + image + '\'' +
", title='" + title + '\'' +
", realPrice=" + realPrice +
'}';
}
}
```
#### 1.2 接口和抽象方法
```java
/**
* 通过uid查询购物车列表所需的数据
* @param uid 用户id
* @return CartVo数据
*/
List findByUid(Integer uid);
```
#### 1.3 编写SQL映射
```xml
```
单元测试
```java
@Test
public void findByUid() {
List list = cartMapper.findByUid(7);
for (CartVo cartVo : list) {
System.err.println(cartVo);
}
}
```
### 2. 业务层
#### 2.1 规划异常
无
#### 2.2 接口和抽象方法
```java
/**
* 查询购物车列表
* @param uid 用户id
* @return CartVo列表
*/
List getVOByUid(Integer uid);
```
#### 2.3 接口实现
```java
@Override
public List getVOByUid(Integer uid) {
return cartMapper.findByUid(uid);
}
```
单元测试
### 3. 控制层
#### 3.1 处理异常
无
#### 3.2 设计请求
```
url:/carts/
type: get
data: HttpSession session
return: JsonResult>
```
#### 3.3 实现请求
```java
@RequestMapping({"/", ""})
public JsonResult> getVOByUid(HttpSession session) {
Integer uid = getUidFromSession(session);
List data = cartService.getVOByUid(uid);
return new JsonResult<>(OK, data);
}
```
单元测试
登录后访问:http://localhost:8080/carts/
### 前端页面
1.注释掉cart.js文件
```js
```
```js
$(document).ready(function () {
showCartList();
});
// 展示购物车数据
function showCartList() {
// 清空tbody标签数据
$("#cart-list").empty()
$.ajax({
url: "/carts",
type: "GET",
dataType: "json",
success: function (json) {
let list = json.data;
for (let i = 0; i < list.length; i++) {
let tr = '\n' +
'| \n' +
'\t\n' +
' | \n' +
' | \n' +
'#{title}#{msg} | \n' +
'¥#{singlePrice} | \n' +
'\n' +
'\t\n' +
'\t\n' +
'\t\n' +
' | \n' +
'#{totalPrice} | \n' +
'\n' +
'\t\n' +
' | \n' +
'
';
tr = tr.replace(/#{cid}/g, list[i].cid);
tr = tr.replace(/#{image}/g, list[i].image);
tr = tr.replace(/#{title}/g, list[i].title);
tr = tr.replace(/#{msg}/g, list[i].realPrice);
tr = tr.replace(/#{num}/g, list[i].num);
tr = tr.replace(/#{singlePrice}/g, list[i].price);
tr = tr.replace(/#{totalPrice}/g, list[i].price * list[i].num);
$("#cart-list").append(tr)
}
},
error: function (xhr) {
alert("购物车列表数据加载产生未知的异常" + xhr.status)
}
});
}
```
## 增加购物车商品数量
### 1. 持久层
#### 1.1 规划SQL语句
1.执行更新t_cart记录的num值,无需重复开发
2.根据cid的值查询当前购物车数据是否存在
```mysql
select * from t_cart where cid=?
```
#### 1.2 接口和抽象方法
```java
/**
* 根据cid查询购物车数据是否存在
* @param cid 购物车id
* @return 购物车数据
*/
CartVo findByCid(Integer cid);
```
#### 1.3 编写SQL映射
```xml
```
单元测试
```java
@Test
public void findByCid() {
System.err.println(cartMapper.findByCid(1));
}
```
### 2. 业务层
#### 2.1 规划异常
更新时异常:updateException
查询的数据是否有访问权限:AccessException
查询数据不存在,抛出:CartNotFoundException
```
package com.cy.store.controller.ex;
public class CartNotFoundException extends RuntimeException{
public CartNotFoundException() {
super();
}
public CartNotFoundException(String message) {
super(message);
}
public CartNotFoundException(String message, Throwable cause) {
super(message, cause);
}
public CartNotFoundException(Throwable cause) {
super(cause);
}
protected CartNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
```
#### 2.2 接口和抽象方法
```java
/**
* 更新用户的购物车数据的数量
* @param cid
* @param uid
* @param username
* @return 增加成功后新的数量
*/
Integer addNum(Integer cid, Integer uid, String username);
```
#### 2.3 实现方法
```java
@Override
public Integer addNum(Integer cid, Integer uid, String username) {
Cart result = cartMapper.findByCid(cid);
if (result == null) {
throw new CartNotFoundException("购物车数据不存在!");
}
if (!result.getUid().equals(uid)) {
throw new AccessDeniedException("数据非法访问!");
}
Integer num = result.getNum() + 1;
Integer rows = cartMapper.updateNumByCid(cid, num, username, new Date());
if (rows != 1) {
throw new UpdateException("更新数据失败!");
}
return num;
}
```
单元测试
### 3. 控制层
#### 3.1 处理异常
```
else if (e instanceof CartNotFoundException) {
result.setState(4007);
result.setMessage("购物车数据不存在的异常!");
}
```
#### 3.2 设计请求
```
/carts/{cid}/num/add
Post
Interger cid, HttpSession session
JsonResult
```
#### 3.3 实现请求
```java
@RequestMapping("{cid}/add/num")
public JsonResult addNum(Integer cid, HttpSession session) {
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
Integer data = cartService.addNum(cid, uid, username);
return new JsonResult<>(OK, data);
}
```
单元测试
登录后访问:http://localhost:8080/carts/1/add/num
### 前端页面
```
'\n' +
'\n' +
```
```
function addNum(cid) {
$.ajax({
url: "/carts/" + cid + "/add/num",
type: "POST",
dataType: "json",
success: function (json) {
if (json.state === 200) {
$("#goodsCount" + cid).val(json.data);
let price = $("#goodsPrice" + cid).html();
let totalPrice = price * json.data;
$("#goodsCast" + cid).html(totalPrice);
} else {
alert("增加购物车数据失败," + json.message)
}
},
error: function (xhr) {
alert("增加购物车商品数量产生未知的异常" + xhr.status)
}
});
}
function reduceNum(cid) {
$.ajax({
url: "/carts/" + cid + "/reduce/num",
type: "POST",
dataType: "json",
success: function (json) {
if (json.state === 200 && json.data >= 1) {
$("#goodsCount" + cid).val(json.data);
let price = $("#goodsPrice" + cid).html();
let totalPrice = price * json.data;
$("#goodsCast" + cid).html(totalPrice);
} else {
alert("减少购物车数据失败," + json.message)
}
},
error: function (xhr) {
alert("增加购物车商品数量产生未知的异常" + xhr.status)
}
});
}
```
单元测试
## 显示勾选的购物车数据
### 1. 持久层
#### 1.1 规划SQL语句
用户在购物车列表随机勾选相关的商品,点击【结算】按钮后,跳转到结算页面,在这个页面展示用户在上一个页面勾选的数据。列表的展示,内容还是购物车表。两个页面需要将多个cid传递
```mysql
SELECT cid,
uid,
pid,
t_cart.price,
cart.num,
image,
title,
t_product.price as realPrice
FROM t_cart LEFT JOIN t_product ON t_cart.pid = t_product.id
WHERE cid IN (?,?,?)
ORDER BY t_cart.created_time DESC;
```
#### 1.2 接口和抽象方法
```
List findVOByCid(Integer[] cids);
```
#### 1.3 编写SQL映射
```mysql
```
单元测试
```
@Test
public void findVOBycid() {
Integer[] cids = {1,2,3,4,5,6,7};
System.out.println(cartMapper.findVOByCid(cids));
}
```
### 2. 业务层
#### 2.1 规划异常
无
#### 2.2 接口和抽象方法
```java
List getVOByCid(Integer uid, Integer[] cids);
```
#### 2.3 实现方法
```java
@Override
public List getVOByCid(Integer uid, Integer[] cids) {
List result = cartMapper.findVOByCid(cids);
Iterator it = result.iterator();
while (it.hasNext()) {
CartVo cartVo = it.next();
if (!cartVo.getUid().equals(uid)) { // 表示当前用户不属于当前用户
result.remove(cartVo); // 从集合中移除这个元素
}
}
return result;
}
```
单元测试
### 3. 控制层
#### 3.1 处理异常
无
#### 3.2 设计请求
```
/carts/list
Integer[] cids, HttpSession session
POST
JsonResult>
```
#### 3.3 实现请求
```
@RequestMapping("list")
public JsonResult> getVOByCid(Integer[] cids, HttpSession session) {
Integer uid = getUidFromSession(session);
List data = cartService.getVOByCid(uid, cids);
return new JsonResult<>(OK, data);
}
```
单元测试
登录后访问:http://localhost:8080/carts/list?cids=1&cids=2&cids=3
### 4. 前端页面
#### 4.1 显示勾选的购物车数据-前端页面
cart.html页面中结算按钮type属性更改为submit
```
```
在orderConfirm.html页面中添加自动加载从上个页面传递过来的cids数据,再发送Ajax请求
```js
```
#### 4.2 购物车页面显示收货地址列表-前端页面
1.收货地址存放在select下拉列表中,将查询到的当前用户的收货地址动态加载到这个下来列表中。从数据库角度看,是一个select查询语句。已经编写了根据uid查询收货地址
2.OrderConfirm页面中,收货地址数据需要自动进行加载,将方法逻辑在ready()函数中
```js
$(document).ready(function () {
showCartList();
showAddressList();
});
```
3.定义showAddressList方法
```js
function showAddressList() {
$("#address-list").empty()
$.ajax({
url: "/address",
type: "POST",
dataType: "json",
success: function (json) {
if (json.state === 200) {
let list = json.data;
for (let i = 0; i < list.length; i++) {
let opt = ""
opt = opt.replace(/#{aid}/g, list[i].aid);
opt = opt.replace(/#{tag}/g, list[i].tag);
opt = opt.replace(/#{receiveName}/g, list[i].name);
opt = opt.replace(/#{provinceName}/g, list[i].provinceName);
opt = opt.replace(/#{cityName}/g, list[i].cityName);
opt = opt.replace(/#{areaName}/g, list[i].areaName);
opt = opt.replace(/#{address}/g, list[i].address);
opt = opt.replace(/#{phone}/g, list[i].phone);
$("#address-list").append(opt)
}
}
},
error: function (xhr) {
alert("购物车对应收货地址列表加载产生未知的异常" + xhr.status)
}
});
}
```
## 订单
### 数据表创建
```mysql
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;
```
### 创建实体类
1.订单的实体类
```java
/** 订单数据的实体类 */
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;
```
2.订单项的实体类
```java
/** 订单中的商品数据 */
public class OrderItem extends BaseEntity {
private Integer id;
private Integer oid;
private Integer pid;
private String title;
private String image;
private Long price;
private Integer num;
```
### 1. 持久层
#### 1.1 规划SQL语句
1.插入操作
```mysql
insert into t_order (oid除外所有字段) values (???...)
```
2.讲数据插入到订单项的表中
```java
insert into t_order (id除外所有字段) values (???...)
```
#### 1.2 接口和抽象方法
创建OrderMapper接口
```java
package com.cy.store.mapper;
import com.cy.store.entity.Cart;
import com.cy.store.entity.Order;
import com.cy.store.entity.OrderItem;
import com.cy.store.vo.CartVo;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/** 订单的持久层接口 */
//@Mapper
public interface OrderMapper {
/**
* 插入订单表数据
* @param order order对象
* @return 受影响的行数
*/
Integer insertOrder(Order order);
/**
* 插入订单项表数据
* @param orderItem orderItem对象
* @return 受影响的行数
*/
Integer insertOrderItem(OrderItem orderItem);
}
```
#### 1.3 编写SQL映射
创建OrderMapper.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}
)
```
单元测试
```
@Test
public void insertOrder(){
Order order = new Order();
order.setUid(7);
order.setRecvName("管理员");
order.setRecvPhone("232423");
orderMapper.insertOrder(order);
}
@Test
public void insertOrderItme(){
OrderItem orderItem = new OrderItem();
orderItem.setOid(1);
orderItem.setPid(10000005);
orderItem.setTitle("圆珠笔");
orderMapper.insertOrderItem(orderItem);
}
```
### 2. 业务层
在IAddressService定义根据aid获取address地址数据
```
Address getByAid(Integer aid, Integer uid);
```
实现该方法
```
@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.setCreatedTime(null);
address.setModifiedTime(null);
address.setCreatedUser(null);
address.setModifiedUser(null);
return address;
}
```
#### 2.1 规划异常
插入时异常,已编写
#### 2.2 接口和抽象方法
创建IOrderService接口
```
Order create(Integer aid, Integer uid, String username, Integer[] cids);
```
#### 2.3 方法实现
```java
package com.cy.store.service.impl;
import com.cy.store.controller.ex.AddressCountLimitException;
import com.cy.store.entity.Address;
import com.cy.store.entity.Order;
import com.cy.store.mapper.AddressMapper;
import com.cy.store.mapper.OrderMapper;
import com.cy.store.service.IAddressService;
import com.cy.store.service.ICartService;
import com.cy.store.service.IDistrictService;
import com.cy.store.service.IOrderService;
import com.cy.store.service.ex.*;
import com.cy.store.vo.CartVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
@Service
public class OrderServiceImpl implements IOrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private IAddressService addressService;
@Autowired
private ICartService cartService;
@Override
public Order create(Integer aid, Integer uid, String username, Integer[] cids) {
List list = cartService.getVOByCid(uid, cids);
// 计算商品总价
Long totalPrice = 0L;
for (CartVo c : list) {
totalPrice += c.getRealPrice() + c.getNum();
}
Address address = addressService.getByAid(aid, uid);
Order order = new Order();
order.setUid(uid);
// 收货地址数据
order.setRecvName(address.getName());
order.setRecvPhone(address.getPhone());
order.setRecvProvince(address.getProvinceName());
order.setRecvCity(address.getCityName());
order.setRecvArea(address.getAreaName());
order.setRecvAddress(address.getAddress());
// 支付
order.setTotalPrice(totalPrice);
order.setStatus(0);
order.setOrderTime(new Date());
// 日志
order.setCreatedUser(username);
order.setCreatedTime(new Date());
order.setModifiedUser(username);
order.setModifiedTime(new Date());
Integer rows = orderMapper.insertOrder(order);
if (rows != 1) {
throw new InsertException("插入时异常!");
}
return order;
}
}
```
单元测试
```java
@Test
public void creat() {
Integer[] cids = {1, 2};
Order order = orderService.create(10, 7, "test002", cids);
System.out.println(order);
}
```
### 3. 控制层
#### 3.1 处理异常
无
#### 3.2 设计请求
```
/orders/create
aid, session, cids
post
JsonResult
```
#### 3.3 实现请求
```
package com.cy.store.controller;
import com.cy.store.entity.Order;
import com.cy.store.service.IOrderService;
import com.cy.store.util.JsonResult;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("orders")
public class OrderController extends BaseController{
@Autowired
private IOrderService orderService;
@RequestMapping("create")
public JsonResult 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);
}
}
```
单元测试
#### 4. 前端页面
订单确定页面OrderConfirm添加发送请求的处理方法
```js
$("#btn-create-order").click(function () {
let aid = $("#address-list").val()
let cids = location.search.substr(1)
$.ajax({
url: "/orders/create",
type: "GET",
data: "aid=" + aid + "&" + cids,
dataType: "json",
success: function (json) {
if (json.state === 200) {
location.href = "payment.html";
alert("订单创建成功!")
}
},
error: function (xhr) {
alert("订单数据加载产生未知的异常" + xhr.status)
}
});
})
```