云商城-购物车&订单
第6章 购物车、订单实现
课程目标
1、MongoDB
1)MongoDB安装
2)MongoDB常用操作
2、购物车功能实现
1)掌握购物车特征
2)掌握SpringData MongoDB操作MongoDB
3)购物车流程分析
4)购物车列表
5)购物车增加
3、订单功能实现
1)订单实现流程分析
2)添加订单功能
3)防止超卖现象解决方案
1 MongoDB
1.1 MongoDB介绍
MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。在高负载的情况下,添加更多的节点,可以保证服务器性能。MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON (简称BSON)对象。字段值可以包含其他文档,数组及文档数组。

MongoDB特点:
1、MongoDB 是一个面向文档存储的数据库,操作起来比较简单和容易。
2、你可以在MongoDB记录中设置任何属性的索引来实现更快的排序。
3、MongoDB有很强的扩展性,海量数据处理时可以根据需要进行分片。
4、Mongo支持丰富的查询表达式。
5、Mongodb中的Map/reduce主要是用来对数据进行批量处理和聚合操作。
6、GridFS是MongoDB中的一个内置功能,可以用于存放大量小文件。
7、MongoDB允许在服务端执行脚本,可以用Javascript编写某个函数,直接在服务端执行,也可以把函数的定义存储在服务端,下次直接调用即可。
8、MongoDB支持各种编程语言:RUBY,PYTHON,JAVA,C++,PHP,C#等多种语言。
9、MongoDB安装简单。
MongoDB数据类型:
Object ID:Documents 自生成的 _id
String:字符串,必须是utf-8
Boolean:布尔值,true 或者false (这里有坑哦~在我们大Python中 True False 首字母大写)
Integer:整数 (Int32 Int64 你们就知道有个Int就行了,一般我们用Int32)
Double:浮点数 (没有float类型,所有小数都是Double)
Arrays:数组或者列表,多个值存储到一个键 (list哦,大Python中的List哦)
Object:如果你学过Python的话,那么这个概念特别好理解,就是Python中的字典,这个数据类型就是字典
Null:空数据类型 , 一个特殊的概念,None Null
Timestamp:时间戳
Date:存储当前日期或时间unix时间格式 (我们一般不用这个Date类型,时间戳可以秒杀一切时间类型)
1.2 MongoDB安装
MongoDB安装非常简单,并且支持的系统非常丰富,支持OS X 32-bit,OS X 64-bit,Linux 32-bit,Linux 64-bit,Windows 32-bit,Windows 64-bit,Solaris i86pc,Solaris 64等多个操作系统,支持语言也非常丰富C,C++,C# / .NET,Erlang,Haskell,Java,JavaScript,Lisp,node.JS,Perl,PHP,Python,Ruby,Scala。
当前软件流程采用Docker安装,我们采用Docker安装方式进行安装,安装方式如下:
#安装MongoDB容器
#-e MONGO_INITDB_ROOT_USERNAME创建管理员账号
#-e MONGO_INITDB_ROOT_PASSWORD=123456创建密码
#映射容器服务的 27017 端口到宿主机的 27017 端口
docker run -d -p 27017:27017 --name mongodb -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=123456 mongo
#进入容器
docker exec -it mongodb /bin/bash
#登录mongo,登录的数据库是admin数据库
mongo 192.168.100.130:27017 -u 'admin' -p '123456' --authenticationDatabase 'admin'
#创建数据库shop
use shop
#创建用户
#账号:root
#密码:123456
#角色:root
#管理数据库:admin
db.createUser({ user: 'sh', pwd: '123456', roles: [ { role: "dbOwner", db: "shop" } ] });
效果如下:

权限说明:
read: 只能读取指定数据库
readWrite: 能读写指定数据库
dbAdmin: 能执行管理函数,如索引创建、删除,查看统计或访问 system.profile
dbOwner: 对当前数据库有全部权限
userAdmin: 能创建、删除和管理用户
clusterAdmin: 只能在 admin 数据库中可用,能赋予用户所有分片和复制集相关函数的管理权限
readAnyDatabase: 只能在 admin 数据库中可用,能赋予用户所有数据库的读权限
readWriteAnyDatabase: 只能在 admin 数据库中可用,能赋予用户所有数据库的读写权限
userAdminAnyDatabase: 只能在 admin 数据库中可用,能赋予用户所有数据库的 userAdmin 权限
dbAdminAnyDatabase: 只能在 admin 数据库中可用,能赋予用户所有数据库的 dbAdmin 权限
root: 只能在 admin 数据库中可用。超级权限
1.3 MongoDB常用操作
1)创建数据库
use 集合空间名字(数据库名字)
例如:use shop
> use shop
switched to db shop
>
查看所有数据库:show dbs
> show dbs;
admin 0.000GB
config 0.000GB
local 0.000GB
shop 0.000GB
>
2)创建文档集合(表)
db.createCollection("集合名字")
例如:db.crateCollection("item")
> db.createCollection("item")
{ "ok" : 1 }
>
查看文档集合:show tables
> show tables;
item
>
3)添加文档
db.文档集合名称.insert(数据);
例如:db.item.insert({_id:"No1",name:"华为Mate 40 Pro",price:8500})
> db.item.insert({_id:"No1",name:"华为Mate 40 Pro",price:8500})
WriteResult({ "nInserted" : 1 })
> db.item.insert({_id:"No2",name:"华为Mate 40",price:6500})
WriteResult({ "nInserted" : 1 })
> db.item.insert({_id:"No3",name:"华为Mate 40 Pro +",price:12500})
WriteResult({ "nInserted" : 1 })
>
4)查询文档
查询所有:db.文档集合名称.find()
> db.item.find()
{ "_id" : "No1", "name" : "华为Mate 40 Pro", "price" : 8500 }
{ "_id" : "No2", "name" : "华为Mate 40", "price" : 6500 }
{ "_id" : "No3", "name" : "华为Mate 40 Pro +", "price" : 12500 }
>
分页查询:db.item.find().skip(2).limit(2)
> db.item.find().skip(2).limit(2)
{ "_id" : "No3", "name" : "华为Mate 40 Pro +", "price" : 12500 }
>
模糊查询(正则匹配):db.item.find({列名:匹配规则})
> db.item.find({name:/Pro/})
{ "_id" : "No1", "name" : "华为Mate 40 Pro", "price" : 8500 }
{ "_id" : "No3", "name" : "华为Mate 40 Pro +", "price" : 12500 }
>
复杂查询:
db.item.find({name:/^x/}) 以x开始的
db.item.find({price:{$gt:7000}}) price>7000元,$gte则表示>=
db.item.find({price:{$lt:7000}}) price<7000元,$lte则表示<=
db.item.find({price:{$ne:8500}}) price!=8500
db.item.find({_id:{$in:["No1","No3"]}}) _id包含No1 No2的数据
db.item.find({_id:{$nin:["No1","No3"]}}) _id不包含No1 No2的数据
db.item.count() 总条数查询,count({条件})
5)修改文档
自增操作:db.item.update({_id:"No1"},{$inc:{price:1}})
> db.item.update({_id:"No1"},{$inc:{price:1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
>
指定列修改:db.item.update({_id:"No1"},{$set:{name:"华为P40 Pro"}})
> db.item.update({_id:"No1"},{$set:{name:"华为P40 Pro"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
>
6)删除文档
> db.item.remove({_id:"No3"})
WriteResult({ "nRemoved" : 1 })
>
2 购物车功能

2.1 购物车介绍
免登陆购物车:用户不登录也能使用购物车,以前京东使用的就是这种方案,现在已经改为身份验证购物车模式,因为这种模式数据更安全。
优点:用户不登录也能使用购物车,给用户带来了方便
缺点:数据混乱,数据丢失概率大
身份验证购物车:用户必须登录才能使用购物车,天猫一直在使用这种方案。
优点:数据安全,不易丢失。
缺点:用户使用不方便,必须有账号、必须登录。
购物车特点:
1、使用购物车的群体大
2、查询购物车的频率高、操作购物车的频率 高
3、数据不存在交易操作,安全级别不用太高
4、存储时间久
5、购物车以用户为单位进行存储
存储技术分析:
1、免登陆购物车,数据可存储在客户端,例如Cookie、LocalStorage、WebSQL,但都存在跨域问题。
2、身份校验购物车,数据可存储在非关系型数据可,例如MongoDB,tair,不建议使用Redis,因为购物车量大。如果设计永久保存购物车数据,可以使用MongoDB或者tair,他们都可以进行大规模扩容。
2.2 购物车流程分析

购物车功能主要有2个功能,分别是将商品加入购物车和查询购物车列表,功能实现起来非常简单,加入购物车的时候,请求购物车服务,购物车服务调用商品服务实现商品查询,再将数据存储到MongoDB,这就实现了商品加入购物车。购物车列表直接从MongoDB查询即可。
购物车最终效果如下:

2.2 SpringBoot操作MongoDB
Spring Data MongoDB是Spring Data项目的一部分,该项目旨在为新数据存储提供熟悉且一致的基于Spring的编程模型,同时保留特定于存储的功能。
Spring Data MongoDB项目提供了与MongoDB文档数据库的集成。Spring Data MongoDB的关键功能区域是以POJO为中心的模型,用于与MongoDB DBCollection进行交互并轻松编写存储库样式的数据访问层。
SpringData MongoDB特征:
1、Spring配置支持使用基于Java的@Configuration类或Mongo驱动程序实例和副本集的XML命名空间。
2、MongoTemplate助手类,可提高执行常见Mongo操作的效率。包括文档和POJO之间的集成对象映射。
3、异常转换为Spring的可移植数据访问异常层次结构
4、功能丰富的对象映射与Spring的转换服务集成
5、基于注解的映射元数据,但可扩展以支持其他元数据格式
6、持久性和映射生命周期事件
7、使用MongoReader/MongoWriter抽象的低级映射
8、基于Java的查询,标准和更新DSL
9、自动实现Repository接口,包括支持自定义finder方法。
10、QueryDSL集成以支持类型安全查询。
11、跨存储持久性 - 使用MongoDB透明地持久保存/检索具有字段的JPA实体的支持
12、Log4j日志appender
13、地理空间整合
14、Map-Reduce集成
15、JMX管理和监控
16、CDI对存储库的支持
17、GridFS支持
18、支持事务,4.x版本支持多文件事务操作
2.3 购物车功能
集成MongoDB需要引入依赖包spring-boot-starter-data-mongodb,并在bootstrap.yml中配置MongoDB的服务连接地址,操作MongoDB可以采用2种方式,第一种是继承MongoRepository<T,ID>的Dao来操作MongoDB,第二种是使用MongoTemplate操作MongoDB,如果有很复杂的操作使用MongoTemplate,简单的增删改查使用第一种。
2.3.1 购物车工程搭建
1)api
创建mall-cart-api,坐标如下:
<groupId>com.gupaoedu.vip.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
<artifactId>mall-cart-api</artifactId>
创建com.gupaoedu.vip.mall.cart.model.Cart代码如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Cart {
@Id
private String _id;
private String userName;
private String name;
private Integer price;
private String image;
private String skuId;
private Integer num;
}
2)service
创建mall-cart-service,坐标如下:
<groupId>com.gupaoedu.vip.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
<artifactId>mall-cart-service</artifactId>
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>mall-service</artifactId>
<groupId>com.gupaoedu.vip.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<description>购物车</description>
<artifactId>mall-cart-service</artifactId>
<dependencies>
<!--cart-->
<dependency>
<groupId>com.gupaoedu.vip.mall</groupId>
<artifactId>mall-cart-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--goodsapi-->
<dependency>
<groupId>com.gupaoedu.vip.mall</groupId>
<artifactId>goods-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--MongoDB-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
</dependencies>
</project>
bootstrap.yml:
server:
port: 8087
spring:
application:
name: mall-cart
cloud:
nacos:
config:
file-extension: yaml
server-addr: 192.168.100.130:8848
discovery:
#Nacos的注册地址
server-addr: 192.168.100.130:8848
data:
#MongoDB配置
#sh - 用户名、123456- 密码、192.168.100.130 - 数据库地址、27017- 端口号、shop - 库名
mongodb:
uri: mongodb://sh:123456@192.168.100.130:27017/shop
main:
allow-bean-definition-overriding: true
#日志配置
logging:
pattern:
console: "%msg%n"
uri属性如下:
| Component | Description |
|---|---|
mongodb:// | 一个必需的前缀,用于标识这是一个字符串 标准连接格式。 |
username:password@ | (可选) 验证凭据。 如果指定,客户端将在连接后尝试使用这些凭据登录到特定数据库。如果用户名或密码包含at符号@, 分号 :, 斜杆 /, 或者百分号% , 请记得使用百分号编码消除歧义 . |
host[:port] | 运行mongod实例(或分片群集的mongos实例)的主机(和可选端口号)。 你可以指定主机名,IP地址或 UNIX domain socket。 根据你的部署拓扑指定任意数量的主机:对于独立的,请指定独立mongod实例的主机名。对于副本集,请指定副本集配置中列出的mongod实例的主机名。对于分片群集,请指定mongos实例的主机名。如果未指定端口号,则使用默认端口27017。 |
/database | (可选) 如果连接字符串包含username:password @形式的身份验证凭据,则要验证的数据库的名称。 如果未指定/database且连接字符串包含凭据,则驱动程序将对admin数据库进行身份验证。 |
?<options> | (可选) 一个查询字符串,它将特定于连接的选项指定为<\name> = <\value>对。 有关这些选项的完整说明,请参见连接字符串选项。 |
创建启动类:com.gupao.vip.mall.MallCartApplication
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class MallCartApplication {
public static void main(String[] args) {
SpringApplication.run(MallCartApplication.class,args);
}
}
2.3.2 添加购物车
加入购物车,需要根据ID查询Sku信息,再将Sku信息转成Cart。
2.3.2.1 根据ID查询Sku
修改mall-goods-service的com.gupaoedu.vip.mall.goods.controller.SkuController,添加根据ID查询Sku详情 方法,代码如下:
/****
* 根据ID获取Sku
*/
@GetMapping(value = "/{id}")
public RespResult<Sku> one(@PathVariable(value = "id") String id){
Sku sku = skuService.getById(id);
return RespResult.ok(sku);
}
修改mall-goods-api的com.gupaoedu.vip.mall.goods.feign.SkuFeign添加根据ID查询Sku详情的Feign接口方法:
/****
* 根据ID获取Sku
*/
@GetMapping(value = "/sku/{id}")
public RespResult<Sku> one(@PathVariable(value = "id") String id);
2.3.2.2 添加购物车
1)dao:mall-cart-service中操作
创建com.gupao.vip.mall.cart.mapper.CartMapper继承MongoRepository
public interface CartMapper extends MongoRepository<Cart,String>{
}
2)Service:mall-cart-service中操作
接口:com.gupao.vip.mall.cart.service.CartService
public interface CartService {
/**
* 加入购物车
* @param id :skuid
* @param userName:用户名
* @param num : 加入购物车数量
*/
void add(String id,String userName,Integer num);
}
实现类:com.gupao.vip.mall.cart.service.impl.CartServiceImpl代码如下:
@Service
public class CartServiceImpl implements CartService {
@Autowired
private CartMapper cartMapper;
@Autowired
private SkuFeign skuFeign;
//加入购物车
@Override
public void add(String id,String userName,Integer num) {
//删除购物车中当前商品
cartMapper.deleteById(userName+id);
if(num>0){
//查询Sku
RespResult<Sku> skuResp = skuFeign.one(id);
//将Sku转换成Cart
Cart cart = new Cart();
cart.setUserName(userName);
cart.setSkuId(id);
cart.setNum(num);
sku2cart(skuResp.getData(),cart);
//批量增加
cartMapper.save(cart);
}
}
/***
* Sku转Cart
* @param sku
* @param cart
*/
public void sku2cart(Sku sku,Cart cart){
cart.setImage(sku.getImage());
cart.set_id(cart.getUserName()+cart.getSkuId());
cart.setName(sku.getName());
cart.setPrice(sku.getPrice());
cart.setSkuId(sku.getId());
}
}
3)Controller:mall-cart-service中操作
创建com.gupao.vip.mall.cart.controller.CartController
@RestController
@RequestMapping(value = "/cart")
@CrossOrigin
public class CartController {
@Autowired
private CartService cartService;
/***
* 添加购物车
* id: skuid
*/
@GetMapping(value = "/{id}/{num}")
public RespResult add(@PathVariable(value = "id")String id,@PathVariable(value = "num")Integer num){
//用户名字
String userName="gp";
//加入购物车
cartService.add(id,userName,num);
return RespResult.ok();
}
}
4)Feign接口配置
修改启动类,在启动类上添加@EnableFeignClients(basePackages = "com.gupaoedu.vip.mall.goods.feign")注解,代码如下:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableFeignClients(basePackages = "com.gupaoedu.vip.mall.goods.feign")
public class MallCartApplication {
public static void main(String[] args) {
SpringApplication.run(MallCartApplication.class,args);
}
}
请求http://localhost:8087/cart/1318596430360813570/1测试,效果如下:

购物车数据如下:

2.3.3 购物车列表
购物车列表功能只需要从MongoDB中根据用户名字查询出来即可。
1)Service
接口:修改com.gupao.vip.mall.cart.service.CartService添加购物车列表方法
/***
* 购物车列表
* @param userName
* @return
*/
List<Cart> list(String userName);
实现类:修改com.gupao.vip.mall.cart.service.impl.CartServiceImpl添加方法实现
/***
* 购物车列表
* @param userName
* @return
*/
@Override
public List<Cart> list(String userName) {
//查询条件
Cart cart = new Cart();
cart.setUserName(userName);
return cartMapper.findAll(Example.of(cart), Sort.by("_id"));
}
2)Controller
修改com.gupao.vip.mall.cart.controller.CartController添加列表方法
/****
* 购物车列表
* @return
*/
@GetMapping(value = "/list")
public RespResult<List<Cart>> list(){
String userName = "gp";
List<Cart> carts = cartService.list(userName);
return RespResult.ok(carts);
}
访问http://localhost:8087/cart/list测试效果如下:

2.4 购物车对接
把参考资料中的前台\frant\cart.html中js脚本中的注释去掉即可看到效果。

效果如下:

3 订单功能
3.1 功能分析

从上面可以看出,我们需要查询用户的收件地址信息,而用户收件地址信息来源于shop_user库,需要创建独立微服务实现用户地址加载。
CREATE TABLE `address` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL COMMENT '用户名',
`provinceid` varchar(20) DEFAULT NULL COMMENT '省',
`cityid` varchar(20) DEFAULT NULL COMMENT '市',
`areaid` varchar(20) DEFAULT NULL COMMENT '县/区',
`phone` varchar(20) DEFAULT NULL COMMENT '电话',
`address` varchar(200) DEFAULT NULL COMMENT '详细地址',
`contact` varchar(50) DEFAULT NULL COMMENT '联系人',
`is_default` int(1) DEFAULT NULL COMMENT '是否是默认 1默认 0否',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
3.2 用户收件地址查询
3.2.1 Api搭建
创建mall-user-api微服务,并创建Address实体Bean
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "address")
public class Address implements Serializable{
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
private String provinceid;
private String cityid;
private String areaid;
private String phone;
private String address;
private String contact;
private Integer isDefault;
}
3.2.2 Service搭建
1)创建mall-user-service坐标如下:
<groupId>com.gupaoedu.vip.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
<artifactId>mall-user-service</artifactId>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>mall-service</artifactId>
<groupId>com.gupaoedu.vip.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mall-user-service</artifactId>
<dependencies>
<dependency>
<groupId>com.gupaoedu.vip.mall</groupId>
<artifactId>mall-user-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
bootstrap.yml
server:
port: 8088
spring:
application:
name: mall-user
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.100.130:3306/shop_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 123456
cloud:
nacos:
config:
file-extension: yaml
server-addr: 192.168.100.130:8848
discovery:
#Nacos的注册地址
server-addr: 192.168.100.130:8848
# ====================MybatisPlus====================
mybatis-plus:
mapper-locations: mapper/*.xml
type-aliases-package: com.gupaoedu.vip.mall.*.model
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#日志配置
logging:
pattern:
console: "%msg%n"
启动类
@SpringBootApplication
public class MallUserApplication {
public static void main(String[] args) {
SpringApplication.run(MallUserApplication.class,args);
}
}
3.2.3 用户收件地址列表
1)Dao
创建com.gupaoedu.vip.mall.user.mapper.AddressMapper
public interface AddressMapper extends BaseMapper<Address> {
}
在启动类上添加注解@MapperScan(basePackages = {"com.gupaoedu.vip.mall.user.mapper"})
2)Service
接口:创建com.gupaoedu.vip.mall.user.service.AddressService,代码如下
public interface AddressService extends IService<Address>{
//根据用户名查询地址列表集合
List<Address> list(String userName);
}
实现类:创建com.gupaoedu.vip.mall.user.service.impl.AddressServiceImpl
@Service
public class AddressServiceImpl extends ServiceImpl<AddressMapper,Address> implements AddressService {
@Autowired
private AddressMapper addressMapper;
/***
* 根据用户名查询地址列表集合
* @param userName
* @return
*/
@Override
public List<Address> list(String userName) {
QueryWrapper<Address> queryWrapper = new QueryWrapper<Address>();
queryWrapper.eq("username",userName);
return addressMapper.selectList(queryWrapper);
}
}
3)Controller
创建com.gupaoedu.vip.mall.user.controller.AddressController代码如下:
@RestController
@RequestMapping(value = "/address")
@CrossOrigin
public class AddressController {
@Autowired
private AddressService addressService;
/***
* 用户地址列表查询
* @return
*/
@GetMapping(value = "/list")
public RespResult<List<Address>> list(){
String userName = "gp";
List<Address> addresses = addressService.list(userName);
return RespResult.ok(addresses);
}
}
3.3 下单实现
3.3.1 指定购物车查询

需要根据指定购物车ID(用户勾选的购物车商品数据)集合查询购物车列表信息。
1)Service
修改com.gupao.vip.mall.cart.service.CartService添加根据ID集合查询购物车列表方法
/***
* 根据ID集合查询购物车列表
* @param ids
* @return
*/
Iterable<Cart> list(List<String> ids);
修改com.gupao.vip.mall.cart.service.impl.CartServiceImpl添加实现购物车列表查询方法
/***
* 根据ID集合查询购物车列表
* @param ids
* @return
*/
@Override
public Iterable<Cart> list(List<String> ids) {
return cartMapper.findAllById(ids);
}
2)Controller
修改com.gupao.vip.mall.cart.controller.CartController添加查询方法
/***
* 购物车数据
*/
@PostMapping(value = "/list")
public RespResult<List<Cart>> list(@RequestBody List<String> ids){
//购物车集合
List<Cart> carts = Lists.newArrayList(cartService.list(ids));
return RespResult.ok(carts);
}
效果如下:

3)Feign接口
在mall-cart-api中创建com.gupaoedu.vip.mall.cart.feign.CartFeign
@FeignClient(value = "mall-cart")
public interface CartFeign {
/***
* 购物车数据
*/
@PostMapping(value = "/cart/list")
RespResult<List<Cart>> list(@RequestBody List<String> ids);
}
3.3.2 删除指定购物车
用户如果下单了,是需要将用户下单的商品从购物车中移除,我们采用MongoTemplate实现对Mongodb的操作。
1)Service
修改com.gupao.vip.mall.cart.service.CartService添加根据ID集合删除文档方法
//删除购物车集合
void delete(List<String> ids);
修改com.gupao.vip.mall.cart.service.impl.CartServiceImpl添加根据ID集合删除文档方法
@Autowired
private MongoTemplate mongoTemplate;
/***
* 根据ID删除
* @param ids
*/
@Override
public void delete(List<String> ids) {
mongoTemplate.remove(Query.query(Criteria.where("_id").in(ids)),Cart.class);
}
2)Controller
修改com.gupao.vip.mall.cart.controller.CartController添加删除方法:
/***
* 删除指定购物车
*/
@DeleteMapping
public RespResult delete(@RequestBody List<String> ids){
//删除购物车集合
cartService.delete(ids);
return RespResult.ok();
}
3)Feign接口
在mall-cart-api中修改com.gupaoedu.vip.mall.cart.feign.CartFeign添加如下方法
/***
* 删除指定购物车
*/
@DeleteMapping(value = "/cart")
RespResult delete(@RequestBody List<String> ids);
3.3.3 下单实现
3.3.3.1 表结构分析
商品下单,分为2张表存储,分别为订单表和订单明细表。表结构如下:
订单表:
CREATE TABLE `order_info` (
`id` varchar(50) COLLATE utf8_bin NOT NULL COMMENT '订单id',
`total_num` int(11) DEFAULT NULL COMMENT '数量合计',
`moneys` int(11) DEFAULT NULL COMMENT '金额合计',
`pay_type` varchar(1) COLLATE utf8_bin DEFAULT NULL COMMENT '支付类型,1、在线支付、0 货到付款',
`create_time` datetime DEFAULT NULL COMMENT '订单创建时间',
`update_time` datetime DEFAULT NULL COMMENT '订单更新时间',
`pay_time` datetime DEFAULT NULL COMMENT '付款时间',
`consign_time` datetime DEFAULT NULL COMMENT '发货时间',
`end_time` datetime DEFAULT NULL COMMENT '交易完成时间',
`username` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '用户名称',
`recipients` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '收货人',
`recipients_mobile` varchar(12) COLLATE utf8_bin DEFAULT NULL COMMENT '收货人手机',
`recipients_address` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '收货人地址',
`weixin_transaction_id` varchar(30) COLLATE utf8_bin DEFAULT NULL COMMENT '交易流水号',
`order_status` int(1) DEFAULT NULL COMMENT '订单状态,0:未完成,1:已完成,2:已退货',
`pay_status` int(1) DEFAULT NULL COMMENT '支付状态,0:未支付,1:已支付,2:支付失败',
`is_delete` int(1) DEFAULT NULL COMMENT '是否删除',
PRIMARY KEY (`id`),
KEY `create_time` (`create_time`),
KEY `status` (`order_status`),
KEY `payment_type` (`pay_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
订单明细表:
CREATE TABLE `order_sku` (
`id` varchar(50) COLLATE utf8_bin NOT NULL COMMENT 'ID',
`sku_id` varchar(60) COLLATE utf8_bin DEFAULT NULL COMMENT 'SKU_ID',
`order_id` varchar(50) COLLATE utf8_bin NOT NULL COMMENT '订单ID',
`name` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '商品名称',
`price` int(20) DEFAULT NULL COMMENT '单价',
`num` int(10) DEFAULT NULL COMMENT '数量',
`money` int(20) DEFAULT NULL COMMENT '总金额',
`image` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '图片地址',
PRIMARY KEY (`id`),
KEY `item_id` (`sku_id`),
KEY `order_id` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
3.3.3.2 工程搭建
1)api工程
创建mall-order-api,并创建com.gupaoedu.vip.mall.order.model.Order和com.gupaoedu.vip.mall.order.model.OrderSku
Order:
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "order_info")
public class Order implements Serializable {
@TableId(type = IdType.ASSIGN_ID)
private String id;
private String payType;
private Date createTime;
private Date updateTime;
private Date payTime;
private Date consignTime;
private Date endTime;
private String username;
private String recipients;
private String recipientsMobile;
private String recipientsAddress;
private String weixinTransactionId;
private Integer totalNum;
private Integer moneys;
private Integer orderStatus;
private Integer payStatus;
private Integer isDelete;
//购物车ID集合
@TableField(exist = false)
private List<String> cartIds;
}
OrderSku:
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "order_sku")
public class OrderSku {
@TableId(type = IdType.ASSIGN_ID)
private String id;
private String image;
private String skuId;
private String orderId;
private String name;
private Integer price;
private Integer num;
private Integer money;
}
2)Service工程
创建mall-order-service工程,坐标如下:
<groupId>com.gupaoedu.vip.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
<artifactId>mall-order-service</artifactId>
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>mall-service</artifactId>
<groupId>com.gupaoedu.vip.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mall-order-service</artifactId>
<dependencies>
<dependency>
<groupId>com.gupaoedu.vip.mall</groupId>
<artifactId>mall-order-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--goodsapi-->
<dependency>
<groupId>com.gupaoedu.vip.mall</groupId>
<artifactId>goods-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.gupaoedu.vip.mall</groupId>
<artifactId>mall-cart-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
bootstrap.yml:
server:
port: 8089
spring:
application:
name: mall-order
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.100.130:3306/shop_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 123456
cloud:
nacos:
config:
file-extension: yaml
server-addr: 192.168.100.130:8848
discovery:
#Nacos的注册地址
server-addr: 192.168.100.130:8848
main:
allow-bean-definition-overriding: true
feign:
client:
config:
default:
connectTimeout: 10000
readTimeout: 600000
# ====================MybatisPlus====================
mybatis-plus:
mapper-locations: mapper/*.xml
type-aliases-package: com.gupaoedu.vip.mall.*.model
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#日志配置
logging:
pattern:
console: "%msg%n"
启动类创建com.gupaoedu.vip.mall.MallOrderApplication
@SpringBootApplication
@MapperScan(basePackages = "com.gupaoedu.vip.mall.order.mapper")
@EnableFeignClients(basePackages = {"com.gupaoedu.vip.mall.goods.feign","com.gupaoedu.vip.mall.cart.feign"})
public class MallOrderApplication {
public static void main(String[] args) {
SpringApplication.run(MallOrderApplication.class,args);
}
}
3.3.3.3 库存递减
库存递减,我们需要根据用户选择的购物车集合对象实现库存递减操作,所以mall-goods-api工程需要引入mall-cart-api依赖:
<dependencies>
<dependency>
<groupId>com.gupaoedu</groupId>
<artifactId>mall-cart-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
修改mall-goods-service添加库存递减方法
1)Dao
修改com.gupaoedu.vip.mall.goods.mapper.SkuMapper添加库存递减方法,这里使用语句级操作数据库,不可使用内存判断,操作时为了防止超卖现象, 需要判断库存数量是否足够。
public interface SkuMapper extends BaseMapper<Sku> {
//库存递减
@Update("UPDATE sku SET num=num-#{num} WHERE id=#{id} AND num>=#{num}")
int decount(@Param("id") String skuId,@Param("num") Integer num);
}
2)Service
修改com.gupaoedu.vip.mall.goods.service.SkuService添加库存递减接口方法
/***
* 库存递减
* @param carts
*/
void decount(List<Cart> carts);
修改com.gupaoedu.vip.mall.goods.service.impl.SkuServiceImpl添加库存递减实现方法
/***
* 库存递减
* @param carts
*/
@Override
public void decount(List<Cart> carts) {
for (Cart cart : carts) {
//语句级控制,防止超卖
int count = skuMapper.decount(cart.getSkuId(),cart.getNum());
if(count<=0){
throw new RuntimeException("库存不足!");
}
}
}
3)Controller
修改com.gupaoedu.vip.mall.goods.controller.SkuController添加库存递减方法调用
/***
* 库存递减
* @param carts
* @return
*/
@PostMapping(value = "/dcount")
public RespResult decount(@RequestBody List<Cart> carts){
skuService.decount(carts);
return RespResult.ok();
}
4)Feign接口创建
修改goods-api的com.gupaoedu.vip.mall.goods.feign.SkuFeign添加接口方法
/***
* 库存递减
* @param carts
* @return
*/
@PostMapping(value = "/sku/dcount")
RespResult decount(List<Cart> carts);
3.3.3.4 下单操作
下单流程比较复杂,我们需要做很多个步骤,步骤如下:
1、根据用户勾选的购物车ID查询购物车记录
2、实现库存递减
3、库存递减成功后,将购物车商品存入到订单明细中
4、添加订单
5、删除当前操作的购物车数据
1)Dao
创建com.gupaoedu.vip.mall.order.mapper.OrderMapper
public interface OrderMapper extends BaseMapper<Order> {
}
创建com.gupaoedu.vip.mall.order.mapper.OrderSkuMapper
public interface OrderSkuMapper extends BaseMapper<OrderSku> {
}
2)Service
接口:创建com.gupaoedu.vip.mall.order.service.OrderService并创建下单方法
public interface OrderService extends IService<Order> {
//添加订单
Boolean add(Order order);
}
实现类:创建com.gupaoedu.vip.mall.order.service.impl.OrderServiceImpl,并创建下单方法实现过程
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper,Order> implements OrderService {
@Autowired
private OrderSkuMapper orderSkuMapper;
@Autowired
private OrderMapper orderMapper;
@Autowired
private SkuFeign skuFeign;
@Autowired
private CartFeign cartFeign;
/***
* 添加订单
* 作业:
* 1)价格如何校验?
*/
@Override
public Boolean add(Order order) {
//1.查询购物车记录
RespResult<List<Cart>> cartResp = cartFeign.list(order.getCartIds());
List<Cart> carts = IterableConverter.toList(cartResp.getData());
if(carts.size()==0){
return false;
}
//2.库存递减 20000 成功
skuFeign.decount(carts);
//3.增加订单明细
int totlNum = 0; //商品个数
int payMoney = 0; //支付总金额
for (Cart cart : carts) {
//类型转换
OrderSku orderSku = JSON.parseObject(JSON.toJSONString(cart), OrderSku.class);
orderSku.setId(IdWorker.getIdStr());
orderSku.setMoney(orderSku.getPrice()*orderSku.getNum());
orderSku.setSkuId(cart.getSkuId());
orderSku.setOrderId(order.getId());
orderSkuMapper.insert(orderSku);
//统计数据
totlNum+=cart.getNum();
payMoney+=orderSku.getMoney();
}
//4.增加订单
order.setTotalNum(totlNum);
order.setMoneys(payMoney);
orderMapper.insert(order);
//5.删除购物车记录
cartFeign.delete(order.getCartIds());
return true;
}
}
3)Controller
创建com.gupaoedu.vip.mall.order.controller.OrderController,添加下单方法,并完善订单数据
@RestController
@RequestMapping(value = "/order")
public class OrderController {
@Autowired
private OrderService orderService;
/***
* 添加订单
*/
@PostMapping
public RespResult add(@RequestBody Order order){
String userName = "gp";
order.setUsername(userName);
order.setCreateTime(new Date());
order.setUpdateTime(order.getCreateTime());
order.setId(IdWorker.getIdStr());
order.setOrderStatus(0);
order.setPayStatus(0);
order.setIsDelete(0);
//添加订单
Boolean bo = orderService.add(order);
return bo? RespResult.ok():RespResult.error(RespCode.SYSTEM_ERROR);
}
}
3.3.3.5 页面测试
order.html中去掉注释

购物车选择商品

结算页下单操作

订单数据:

订单明细:
