跳至主要內容

云商城-购物车&订单

soulballad实践项目SpringCloudAlibaba 云商城SpringCloudAlibaba约 6516 字大约 22 分钟

第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)对象。字段值可以包含其他文档,数组及文档数组。

1604802898038

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" } ] });

效果如下:

1604807492359

权限说明:

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 购物车功能

1604966967796

2.1 购物车介绍

免登陆购物车:用户不登录也能使用购物车,以前京东使用的就是这种方案,现在已经改为身份验证购物车模式,因为这种模式数据更安全。

​ 优点:用户不登录也能使用购物车,给用户带来了方便

​ 缺点:数据混乱,数据丢失概率大

身份验证购物车:用户必须登录才能使用购物车,天猫一直在使用这种方案。

​ 优点:数据安全,不易丢失。

​ 缺点:用户使用不方便,必须有账号、必须登录。

购物车特点:

​ 1、使用购物车的群体大

​ 2、查询购物车的频率高、操作购物车的频率 高

​ 3、数据不存在交易操作,安全级别不用太高

​ 4、存储时间久

​ 5、购物车以用户为单位进行存储

存储技术分析:

​ 1、免登陆购物车,数据可存储在客户端,例如Cookie、LocalStorage、WebSQL,但都存在跨域问题。

​ 2、身份校验购物车,数据可存储在非关系型数据可,例如MongoDB,tair,不建议使用Redis,因为购物车量大。如果设计永久保存购物车数据,可以使用MongoDB或者tair,他们都可以进行大规模扩容。

2.2 购物车流程分析

1604969388042

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

​ 购物车最终效果如下:

1604969760732

2.2 SpringBoot操作MongoDB

Spring Data MongoDBSpring 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属性如下:

ComponentDescription
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-servicecom.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-apicom.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测试,效果如下:

1605055256197

购物车数据如下:

1605055313904

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测试效果如下:

1605055573994

2.4 购物车对接

参考资料中的前台\frant\cart.html中js脚本中的注释去掉即可看到效果。

1605063542025

效果如下:

1605063663370

3 订单功能

3.1 功能分析

1605064469999

​ 从上面可以看出,我们需要查询用户的收件地址信息,而用户收件地址信息来源于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 指定购物车查询

1605067526782

​ 需要根据指定购物车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);
}

效果如下:

1605068558285

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.Ordercom.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-apicom.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中去掉注释

1605155618134

购物车选择商品

1605155470373

结算页下单操作

1605155506684

订单数据:

1605155535150

订单明细:

1605155570468

上次编辑于:
贡献者: soulballad