## 第1章-环境搭建 **学习目标** - 了解项目背景 - 虚拟机环境准备 - 搭建微服务环境 - 完成商品微服务分类及属性管理 - 使用Swagger/Knife4j进行接口测试 - 域名访问本地服务 # 1. 课程简介 ## 1.1 为什么我们要讲电商? 因为就互联网平台来说,电商网站有很多典型的特征: - 访问量大 - 数据量大 - 有一定的业务复杂性 - 涉及支付考虑一定安全性{幂等性} ## 1.2 电商的主要模式 **B2B(Business to Business)** B2B ( Business to Business)是指进行电子商务交易的供需双方都是商家(或企业、公司),她(他)们使用了互联网的技术或各种商务网络平台,完成商务交易的过程。电子商务是现代 B2B marketing的一种具体主要的表现形式。 案例:阿里巴巴1688 **C2C(Consumer to Customer)** C2C即 Customer(Consumer) to Customer(Consumer),意思就是消费者个人间的电子商务行为。比如一个消费者有一台电脑,通过网络进行交易,把它出售给另外一个消费者,此种交易类型就称为C2C电子商务。 案例:淘宝、易趣、瓜子二手车、闲鱼 **B2C(Business-to-Customer)** B2C是Business-to-Customer的缩写,而其中文简称为“商对客”。“商对客”是电子商务的一种模式,也就是通常说的直接面向消费者销售产品和服务商业零售模式。这种形式的电子商务一般以网络零售业为主,主要借助于互联网开展在线销售活动。B2C即企业通过互联网为消费者提供一个新型的购物环境——网上商店,消费者通过网络在网上购物、网上支付等消费行为。 案例:唯品会、乐蜂网、京东 **B2B2C(Business to Business to Consumer)** B2B2C是一种电子商务类型的网络购物商业模式,B是BUSINESS的简称,C是CUSTOMER的简称,第一个B指的是商品或服务的供应商,第二个B指的是从事电子商务的企业,C则是表示消费者。 案例:京东商城、天猫商城 **C2B(Consumer to Business)** C2B(Consumer to Business,即消费者到企业),是互联网经济时代新的商业模式。这一模式改变了原有生产者(企业和机构)和消费者的关系,是一种消费者贡献价值(Create Value), 企业和机构消费价值(Consume Value)。 C2B模式和我们熟知的供需模式(DSM, Demand SupplyModel)恰恰相反,**真正的C2B 应该先有消费者需求产生而后有企业生产,即先有消费者提出需求,后有生产企业按需求组织生产**。通常情况为消费者根据自身需求定制产品和价格,或主动参与产品设计、生产和定价,产品、价格等彰显消费者的个性化需求,生产企业进行定制化生产。 案例:猪八戒 **O2O(Online To Offline)** O2O即Online To Offline(在线离线/线上到线下),是指将线下的商务机会与互联网结合,让互联网成为线下交易的平台,这个概念最早来源于美国。O2O的概念非常广泛,既可涉及到线上,又可涉及到线下,可以通称为O2O。主流商业管理课程均对O2O这种新型的商业模式有所介绍及关注。 案例:美团、饿了么 ## 1.3 我们能从这个项目中学到什么? ### 1.3.1 温故知新 巩固以前知识,学习技术点与技术点应用场景,**掌握电商业务流程** ### 1.3.2 核心技术 - **SpringBoot**:简化新Spring应用的初始搭建以及开发过程; - **SpringCloud**:基于Spring Boot实现的云原生应用开发工具,SpringCloud使用的技术:(Spring Cloud Gateway、Spring Cloud Alibaba Nacos、Spring Cloud Alibaba Sentinel、Spring Cloud Task和Spring Cloud Feign等) - **SpringBoot+SpringCloudAlibaba(Nacos,Sentinel)+Cloud OpenFeign** - MyBatis-Plus:持久层框架,也依赖mybatis - Redis:内存做缓存 - Redisson:基于redis的Java驻内存数据网格 - 框架;操作redis的框架 - RabbitMQ:消息中间件;大型分布式项目是标配;分布式事务最终一致性 - ElasticSearch+Kibana+Logstash: 全文检索服务器+可视化数据监控:检索 - ThreadPoolExecutor:线程池来实现异步操作,提高效率 - Thymeleaf: 页面模板技术;JSP、服务端渲染;${},前后分离 - Swagger2/Knife4J/YAPI:Api接口文档工具 - MinIO(私有化对象存储集群):分布式文件存储 类似于OSS(公有) - 支付宝支付:alipay.com - MySQL:关系型数据库 {shardingSphere-jdbc 进行分库,分表} - Lombok: 实体类的中get/set 生成的jar包 - Ngrok/natapp:内网穿透 - Docker:容器化技术; 生产环境Redis(运维人员);快速搭建环境Docker run - Git:代码管理工具;git使用,拉代码、提交、推送、合并、冲突解决 - Jenkins:持续集成工具(devops):部署 前端技术栈 - Vue.js:web 界面的渐进式框架; 对请求接口。 - Node.js: JavaScript 运行环境 - NPM:包管理器 ### 1.3.3 需要掌握的解决方案 分布式架构、缓存解决方案、分布式事务、后台管理(数据库的可视化界面,renren-fast、ruoyi)、文件管理系统 课前说明: 1、 建议内存16个G以上 2、 培养自己独立阅读代码的能力 3、 帮助大家分析 解构业务需求 4、 新的知识点,难点敲 5、 重复的功能 自己开发 ## 1.4 项目架构 ![](assets/day01/image-20221128163315857.png) 一个项目:如下选型 网络:公有云,私有云 存储:(结构化存储(关系型数据库(MySQL))、非结构化的存储(Redis,mongodb)) 消息:(消息队列: RabbitMQ、Kafka) 缓存:(Redis、Memcache) 检索:(ElasticSearch) 微服务治理:(配置中心(Nacos/Spring Cloud Config+Github)、注册中心(Nacos/Eureka)、流量保护(Sentinel/Hystrix),服务调用(Dubbo/OpenFeign)) 监控:(运维):Prometheus + Grafana 日志:(大数据+后端),提取有用信息进行数据分析、聚类计算、用户画像 ## 1.5 整体业务简介 ) | 首页 | 静态页面,包含了商品分类,搜索栏,商品广告位。 | | -------- | ---------------------------------------------- | | 全文搜索 | 通过搜索栏填入的关键字进行搜索,并列表展示 | | 分类查询 | 根据首页的商品类目进行查询 | | 商品详情 | 商品的详细信息展示 | | 购物车 | 将有购买意向的商品临时存放的地方 | | 单点登录 | 用户统一登录的管理 | | 结算 | 将购物车中勾选的商品初始化成要填写的订单 | | 下单 | 填好的订单提交 | | 订单服务 | 负责确认订单是否付款成功,并对接仓储物流系统。 | | 支付服务 | 下单后,用户点击支付,负责对接第三方支付系统。 | | 仓储物流 | 独立的管理系统,负责商品的库存。 | | 后台管理 | 主要维护类目、商品、库存单元、广告位等信息。 | | 秒杀 | 秒杀抢购完整方案 | 在线体验地址: - 前端门户:http://sph.atguigu.cn/ # 2. 虚拟机设置 ## 2.1 下载虚拟机 请使用百度云盘VIP进行下载 ![img](assets/day01/wps4.jpg) ## 2.2 配置虚拟机 第一步: ![img](assets/day01/wps5.jpg) 第二步:改NAT模式的子网IP:192.168.200.0 ![img](assets/day01/wps6.jpg) 第三步:确定 第四步:启动虚拟机 ![img](assets/day01/wps7.jpg) ## 2.3 登录虚拟机 IP:192.168.200.128 登录用户:root 登录密码:123456 ![](assets/image-20221212101755472.png) ## 2.4 查看虚拟机中安装的软件 查看所有安装的容器命令如下 - docker ps -a {查看docker中所有的容器} - docker ps {表示查看正在运行的容器} ![](assets/image-20221212101840681.png) Docker常用命令: 镜像相关命令 ```sh docker search 软件 #搜索镜像 docker pull 镜像名称 #下载镜像 docker images # 查询本地存在镜像 docker rmi 镜像名称/镜像ID #删除镜像 ``` 容器相关命令 ```sh #创建容器指令 docker run --name 容器名称 -p 宿主机端口:容器内部端口 -e 环境变量 -v 宿主机目录文件:/容器目录文件 镜像:版本 docker ps [-a] #查询本地容器列表 docker start/stop/restart 容器ID/容器名称 docker rm -f 容器ID/容器名称 #删除容器 ``` ## 2.5 使用SecureCRT或者FinalShell连接 ![img](assets/day01/wps10.jpg) ## 2.6 验证容器运行状态 目前在虚拟机中安装Mysql,Redis,Nacos,Sentinel,Elasticsearch,RabbitMQ... 都是开机自启动! | 名 | URL | 账号密码 | | :-----------: | :-------------------------------- | :----------------------: | | MySQL | 192.168.200.128:3306 | root/123456 | | Redis | 192.168.200.128:6379 | | | Elasticsearch | http://192.168.200.128:9200 | | | Kibana | http://192.168.200.128:5601 | | | Logstash | 收集日志的后台进程,无需访问 | | | Rabbitmq | http://192.168.200.128:15672 | admin/admin | | Zipkin | http://192.168.200.128:9411 | | | Sentinel | 192.168.200.128:8858 | sentinel/sentinel | | Nacos | http://192.168.200.128:8848/nacos | nacos/nacos | | MinIO | http://192.168.200.128:9001 | admin/admin123456 | | YAPI | http://192.168.200.128:3000 | admin@admin.com/ymfe.org | # 3. 电商微服务模块搭建**☆** 在搭建环境前准备好**数据库环境**,将配套资料中的数据库脚本执行 创建工作目录 D:\gmall 不能有中文,不能有空格 ## 3.0 YAPI接口服务 前后端分离开发模式下,前端跟后端工程师,需要有开发接口依据,以下信息必须包含在开发接口中 - 接口实现业务 - 请求方式 GET POST DELETE PUT - 请求路径 见名知意 - 请求参数格式以及内容 form-data/json url path - 响应结果 目前企业中开发都会采用在线接口平台,例如项目中使用YAPI,不仅仅看接口信息,还能够直接进行使用 - 前端工程师(可以访问mock地址模拟调试前端) - 后端工程师(可以直接通过YAPI访问后端服务进行测试) ## 3.1 搭建电商父工程(gmall-parent) ### 3.1.1 搭建gmall-parent 打开idea,选择File–>New–>Project,操作如下 ![img](assets/day01/wps1-1669366785857.jpg) 选择下一步 ![img](assets/day01/wps2-1669366785857.jpg) 配置: groupId:com.atguigu.gmall artifactId:gmall-parent 选择下一步 ![img](assets/day01/wps3.jpg) 完成 工程结构如下 ![img](assets/day01/wps4-1669366785857.jpg) 由于这是一个父工程,删除src目录 ![img](assets/day01/wps5-1669366785858.jpg) ### 3.1.2 配置pom.xml ```xml 4.0.0 com.atguigu.gmall gmall-parent 1.0 pom org.springframework.boot spring-boot-starter-parent 2.3.6.RELEASE 7.8.0 1.8 Hoxton.SR7 2.2.5.RELEASE 1.0 3.4.1 5.1.46 1.18.10 2.9.2 2.0.2 1.2.58 3.15.3 2.6.0 4.5.13 org.springframework.cloud spring-cloud-dependencies ${cloud.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies ${alibaba.version} pom import com.baomidou mybatis-plus-boot-starter ${mybatis-plus.version} mysql mysql-connector-java ${mysql.version} io.springfox springfox-swagger2 ${swagger.version} io.springfox springfox-swagger-ui ${swagger.version} com.github.xiaoymin knife4j-spring-boot-starter ${knife4j.version} org.projectlombok lombok ${lombok.version} org.redisson redisson ${redisson.version} org.apache.commons commons-pool2 ${pool2.version} org.apache.httpcomponents httpclient ${httpclient.version} com.alibaba fastjson ${fastjson.version} ``` ## 3.2 搭建gmall-common父模块 gmall-common:公共模块父节点 - common-util:工具类模块,所有模块都可以依赖于它 - service-util:service服务的工具包,包含service服务的公共配置类,所有service模块依赖于它 ### 3.2.1 搭建gmall-common 点击gmall-parent,选择New–>Module,操作如下 ![img](assets/day01/wps6-1669366785858.jpg) 选择下一步 ![](assets/day01/image-20221122215413747.png) 选择下一步 ![](assets/day01/image-20221122215445026.png) 完成,删除src目录,结构如下 ![](assets/image-20221212000840237.png) ### 3.2.2 修改配置pom.xml ```xml gmall-parent com.atguigu.gmall 1.0 4.0.0 gmall-common pom org.springframework.boot spring-boot-starter-web provided org.projectlombok lombok io.springfox springfox-swagger2 io.springfox springfox-swagger-ui com.github.xiaoymin knife4j-spring-boot-starter org.springframework.boot spring-boot-starter-validation com.alibaba fastjson 1.2.29 org.springframework.cloud spring-cloud-starter-openfeign provided com.baomidou mybatis-plus-annotation ${mybatis-plus.version} org.springframework.boot spring-boot-starter-test org.junit.vintage junit-vintage-engine ``` ## 3.3 搭建common-util模块 ### 3.3.1 搭建common-util 点击`gmall-common`,选择New–>Module,操作如下 ![img](assets/day01/wps10-1669366785858.jpg) 选择下一步 ![](assets/day01/image-20221122215943548.png) 选择下一步 ![](assets/day01/image-20221122220003626.png) 完成,结构如下 ![](assets/day01/image-20221122220626035.png) ### 3.3.2 修改配置pom.xml ```xml gmall-common com.atguigu.gmall 1.0 4.0.0 common-util jar common-util common-util org.apache.httpcomponents httpclient ``` ### 3.3.3 添加公共工具类 ![](assets/image-20221212001235111.png) | GmallException | 自定义全局异常 | | ----------------- | --------------------------- | | Result | API统一返回结果封装类 | | ResultCodeEnum | API统一返回结果状态信息 | | AuthContextHolder | 获取登录用户信息类 | | HttpClientUtil | http客户端类 | | MD5 | 通过MD5给字符串加密的工具类 | | IpUtil | 获取Ip地址的工具类 | | DateUitl | 日期比较工具类 | ## 3.4 搭建service-util模块 ### 3.4.1 搭建service-util 搭建过程同`common-util`,如图 ![](assets/day01/image-20221122221052896.png) ### 3.4.2 修改配置pom.xml ```xml gmall-common com.atguigu.gmall 1.0 4.0.0 service-util com.atguigu.gmall common-util 1.0 org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2 2.6.0 org.redisson redisson 3.15.3 com.baomidou mybatis-plus-boot-starter provided ``` ### 3.4.3 添加service-util工具类 ![](assets/image-20221212001602047.png) | MybatisPlusConfig | MybatisPlus配置类 | | ----------------- | ----------------- | | MybatisPlusConfig | MybatisPlus配置类 | | Swagger2Config | Swagger2配置类 | ## 3.5 搭建gmall-model模块 ### 3.5.1 搭建gmall-model 点击`gmall-parent` 父工程 选择module模块名称:gmall-model,搭建过程同common父模块 **注意**:搭建完成后,导入资料项目相关实体类,使用今日下发实体类 ![image-20221212002002659](assets/image-20221212002002659.png) ### 3.5.2 修改配置pom.xml ```xml gmall-parent com.atguigu.gmall 1.0 4.0.0 gmall-model org.projectlombok lombok com.baomidou mybatis-plus-boot-starter provided io.springfox springfox-swagger2 provided org.springframework.boot spring-boot-starter-data-elasticsearch provided org.springframework.data spring-data-mongodb com.alibaba fastjson ``` ## 3.6 搭建gmall-service父模块 gmall-service:service模块父节点 - service-product:商品服务模块 - service-xxxx:其他服务模块 ### 3.6.1 搭建gmall-service 选中`gmall-parent`父工程,新建子模块:gmall-service。搭建过程同common父模块 ![image-20221212002754023](assets/image-20221212002754023.png) ### 3.6.2 修改配置pom.xml ```xml gmall-parent com.atguigu.gmall 1.0 4.0.0 gmall-service pom com.atguigu.gmall service-util 1.0 com.atguigu.gmall gmall-model 1.0 org.springframework.boot spring-boot-starter-web com.baomidou mybatis-plus-boot-starter mysql mysql-connector-java com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-openfeign com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.springframework.boot spring-boot-devtools true ``` ## 3.7 product商品模块 ### 3.7.1 搭建service-product模块 1. 在`gmall-service`父工程下新增模块:service-product ![image-20221127230022723](assets/day01/image-20221127230022723.png) ![image-20221127230039733](assets/day01/image-20221127230039733.png) ### 3.7.2 启动类 创建启动类 包名:com.atguigu.gmall 类名:ProductApp ```java package com.atguigu.gmall; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * @author: atguigu * @create: 2022-11-27 23:04 */ @SpringBootApplication @EnableDiscoveryClient public class ProductApp { public static void main(String[] args) { SpringApplication.run(ProductApp.class, args); } } ``` ### 3.7.3 配置文件 1. 在resources下新建配置文件application.yml **说明**:后期统一上传nacos配置中心 ```properties server: port: 8206 spring: application: name: service-product datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/gmall_product?characterEncoding=utf-8&useSSL=false username: root password: root hikari: connection-test-query: SELECT 1 # 自动检测连接 connection-timeout: 60000 #数据库连接超时时间,默认30秒 idle-timeout: 500000 #空闲连接存活最大时间,默认600000(10分钟) max-lifetime: 540000 #此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟 maximum-pool-size: 12 #连接池最大连接数,默认是10 minimum-idle: 10 #最小空闲连接数量 pool-name: SPHHikariPool # 连接池名称 cloud: nacos: discovery: server-addr: 192.168.200.128:8848 mybatis-plus: configuration: # sql输出到控制台,方便开发调试 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl ``` # 4. 商品的基本知识 ## 4.1 基本信息—分类 一般情况可以分为两级或者三级。咱们的项目一共分为三级,即一级分类、二级分类、三级分类。 比如:家用电器是一级分类,电视是二级分类,那么超薄电视就是三级分类。 ![img](assets/day01/wps1-1669563691452.jpg) 数据库结构 ![img](assets/day01/wps2-1669563691453.jpg) ```sql SELECT base_category1.id,base_category1.name,base_category2.id,base_category2.name,base_category3.id,base_category3.name FROM base_category1 LEFT JOIN base_category2 ON base_category1.id = base_category2.category1_id LEFT JOIN base_category3 ON base_category2.id = base_category3.category2_id ``` ## 4.2 基本信息—平台属性 平台属性和平台属性值 ![img](assets/day01/wps3-1669563691453.jpg) 平台属性和平台属性值主要用于商品的检索,每个分类对应的属性都不同,分类包含一级分类、二级分类和三级分类,分类层级区分对应分类。 ![img](assets/day01/wps4-1669563691453.jpg) ## 4.3 基本信息—销售属性与销售属性值 销售属性,就是商品详情页右边,可以通过销售属性来定位一组spu下的哪款sku。可以让当前的商品详情页,跳转到自己的“兄弟”商品。 一般每种商品的销售属性不会太多,大约1-4种。整个平台的属性种类也不会太多,大概10种以内。比如:颜色、尺寸、版本、套装等等。 ![img](assets/day01/wps5-1669563691453.jpg) ![img](assets/day01/wps6-1669563691453.jpg) ## 4.4 基本信息—SPU与SKU **SKU=Stock Keeping Unit(库存量单位)**。即库存进出计量的基本单元,可以是以件,盒,托盘等为单位。SKU这是对于大型连锁超市DC(配送中心)物流管理的一个必要的方法。现在已经被引申为产品**统一编号**的简称,每种产品均对应有唯一的SKU号。 **SPU(Standard Product Unit)**:标准化产品单元。是商品信息聚合的最小单位,是一组**可复用、易检索**的标准化信息的集合,该集合描述了一个产品的特性。 首先通过检索搜索出来的商品列表中,每个商品都是一个sku。每个sku都有自己独立的库存数。也就是说每一个商品详情展示都是一个sku。 那spu又是干什么的呢? ![img](assets/day01/wps7-1669563691453.jpg) 如上图,一般的电商系统你点击进去以后,都能看到这个商品关联了其他好几个类似的商品,而且这些商品很多的信息都是共用的,比如商品图片,海报、销售属性等。 那么系统是靠什么把这些sku识别为一组的呢,那是这些sku都有一个公用的spu信息。而它们公共的信息,都放在spu信息下。所以,sku与spu的结构如下: ![img](assets/day01/wps8-1669563691453.jpg) 图中有两个图片信息表,其中spu_image表示整个spu相关下的所有图片信息,而sku_image表示这个spu下的某个sku使用的图片。sku_image中的图片是从spu_image中选取的。但是由于一个spu下的所有sku的海报都是一样,所以只存一份spu_poster就可以了。 # 5. 分类及属性查询**☆** **重点**:清楚分类、品牌的表关系,独立完成分类查询的后端代码,并返回正确结果 商城的核心自然是商品,而商品多了以后,肯定要进行分类,并且不同的商品会有不同的品牌信息 >分类 与 商品 是【一对多】关系 > >分类 与 品牌 是【多对多】关系,需要一张中间表来维护 > >品牌 与 商品 是【一对多】关系 简单理解: - 一个商品分类下有很多商品 (手机分类下有很多手机商品) - 一个商品分类下有很多品牌(手机分类下有苹果、华为、小米) - 一个品牌可以属于不同的分类 (华为品牌可以属于手机分类、也可以属于笔记本电脑分类) - 一个品牌下也会有很多商品 (华为品牌下可以P40、Meta40、P50 多种商品) > YAPI接口文档地址: > > - 获取一级分类列表:http://192.168.200.128:3000/project/11/interface/api/283 > - 获取二级分类列表:http://192.168.200.128:3000/project/11/interface/api/299 > - 获取三级分类列表:http://192.168.200.128:3000/project/11/interface/api/291 > - 根据分类Id 获取平台属性集合:http://192.168.200.128:3000/project/11/interface/api/267 ## 5.1 控制层 编写一个controller一般需要知道四个内容,这些内容来源于开发接口文档: - 请求方式:决定我们用GetMapping还是PostMapping或者其他 - 请求路径:决定映射路径 - 请求参数:决定方法的参数 - 返回值结果:决定方法的返回值 Controller代码: ```java package com.atguigu.gmall.product.controller; import com.atguigu.gmall.common.result.Result; import com.atguigu.gmall.product.model.BaseAttrInfo; import com.atguigu.gmall.product.model.BaseCategory1; import com.atguigu.gmall.product.model.BaseCategory2; import com.atguigu.gmall.product.model.BaseCategory3; import com.atguigu.gmall.product.service.BaseCategoryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * @author: atguigu * @create: 2022-12-12 00:50 */ @RequestMapping("admin/product") @RestController public class BaseCategoryController { @Autowired private BaseCategoryService baseCategoryService; /** * 查询所有的一级分类信息 * * @return */ @GetMapping("getCategory1") public Result> getCategory1() { List list = baseCategoryService.getCategory1(); return Result.ok(list); } /** * 根据一级分类Id 查询二级分类数据 * @param category1Id * @return */ @GetMapping("getCategory2/{category1Id}") public Result> getCategory2(@PathVariable("category1Id") Long category1Id) { List baseCategory2List = baseCategoryService.getCategory2(category1Id); return Result.ok(baseCategory2List); } /** * 根据二级分类Id 查询三级分类数据 * @param category2Id * @return */ @GetMapping("getCategory3/{category2Id}") public Result> getCategory3(@PathVariable("category2Id") Long category2Id) { List baseCategory3List = baseCategoryService.getCategory3(category2Id); return Result.ok(baseCategory3List); } /** * 根据分类Id 获取平台属性数据 * @param category1Id * @param category2Id * @param category3Id * @return */ @GetMapping("attrInfoList/{category1Id}/{category2Id}/{category3Id}") public Result> attrInfoList(@PathVariable("category1Id") Long category1Id, @PathVariable("category2Id") Long category2Id, @PathVariable("category3Id") Long category3Id) { List baseAttrInfoList = baseCategoryService.getAttrInfoList(category1Id, category2Id, category3Id); return Result.ok(baseAttrInfoList); } } ``` ## 5.2 业务层 ### 5.2.1 Service 包名:com.atguigu.gmall.product.service 接口名:BaseCategoryService ```java package com.atguigu.gmall.product.service; import com.atguigu.gmall.product.model.BaseAttrInfo; import com.atguigu.gmall.product.model.BaseCategory1; import com.atguigu.gmall.product.model.BaseCategory2; import com.atguigu.gmall.product.model.BaseCategory3; import java.util.List; public interface BaseCategoryService { /** * 查询所有的一级分类列表 * * @return */ List getCategory1(); /** * 根据一级分类Id 查询二级分类数据 * @param category1Id * @return */ List getCategory2(Long category1Id); /** * 根据二级分类Id 查询三级分类数据 * @param category2Id * @return */ List getCategory3(Long category2Id); /** * 根据分类Id 获取平台属性数据 * @param category1Id * @param category2Id * @param category3Id * @return */ List getAttrInfoList(Long category1Id, Long category2Id, Long category3Id); } ``` ### 5.2.2 ServiceImpl 包名:com.atguigu.gmall.product.service.impl 类名:BaseCategoryServiceImpl ```java package com.atguigu.gmall.product.service.impl; import com.atguigu.gmall.product.model.BaseAttrInfo; import com.atguigu.gmall.product.model.BaseCategory1; import com.atguigu.gmall.product.model.BaseCategory2; import com.atguigu.gmall.product.model.BaseCategory3; import com.atguigu.gmall.product.mapper.BaseAttrInfoMapper; import com.atguigu.gmall.product.mapper.BaseCategory1Mapper; import com.atguigu.gmall.product.mapper.BaseCategory2Mapper; import com.atguigu.gmall.product.mapper.BaseCategory3Mapper; import com.atguigu.gmall.product.service.BaseCategoryService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /** * @author: atguigu * @create: 2022-12-12 00:52 */ @Service public class BaseCategoryServiceImpl implements BaseCategoryService { @Autowired private BaseCategory1Mapper baseCategory1Mapper; @Autowired private BaseCategory2Mapper baseCategory2Mapper; @Autowired private BaseCategory3Mapper baseCategory3Mapper; @Autowired private BaseAttrInfoMapper baseAttrInfoMapper; /** * 查询所有的一级分类列表 * * @return */ @Override public List getCategory1() { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(BaseCategory1::getIsDeleted, false); return baseCategory1Mapper.selectList(queryWrapper); } /** * 根据一级分类Id 查询二级分类数据 * * @param category1Id * @return */ @Override public List getCategory2(Long category1Id) { // select * from baseCategory2 where Category1Id = ? LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(BaseCategory2::getCategory1Id, category1Id); queryWrapper.eq(BaseCategory2::getIsDeleted, false); return baseCategory2Mapper.selectList(queryWrapper); } /** * 根据二级分类Id 查询三级分类数据 * * @param category2Id * @return */ @Override public List getCategory3(Long category2Id) { // select * from baseCategory3 where Category2Id = ? LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(BaseCategory3::getCategory2Id, category2Id); queryWrapper.eq(BaseCategory3::getIsDeleted, false); return baseCategory3Mapper.selectList(queryWrapper); } /** * 根据分类Id 获取平台属性数据 * 接口说明: * 1,平台属性可以挂在一级分类、二级分类和三级分类 * 2,查询一级分类下面的平台属性,传:category1Id,0,0; 取出该分类的平台属性 * 3,查询二级分类下面的平台属性,传:category1Id,category2Id,0; * 取出对应一级分类下面的平台属性与二级分类对应的平台属性 * 4,查询三级分类下面的平台属性,传:category1Id,category2Id,category3Id; * 取出对应一级分类、二级分类与三级分类对应的平台属性 * * @param category1Id * @param category2Id * @param category3Id * @return */ @Override public List getAttrInfoList(Long category1Id, Long category2Id, Long category3Id) { // 调用mapper: return baseAttrInfoMapper.selectBaseAttrInfoList(category1Id, category2Id, category3Id); } } ``` ## 5.3 持久层 ### 5.3.1 Mapper 包名:com.atguigu.gmall.product.mapper 接口名:BaseCategory1Mapper BaseCategory2Mapper BaseCategory3Mapper BaseAttrInfoMapper BaseAttrValueMapper ```java package com.atguigu.gmall.product.mapper; import com.atguigu.gmall.product.model.BaseCategory1; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface BaseCategory1Mapper extends BaseMapper { } ``` ```java package com.atguigu.gmall.product.mapper; import com.atguigu.gmall.product.model.BaseCategory2; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface BaseCategory2Mapper extends BaseMapper { } ``` ```java package com.atguigu.gmall.product.mapper; import com.atguigu.gmall.product.model.BaseCategory3; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface BaseCategory3Mapper extends BaseMapper { } ``` ```java package com.atguigu.gmall.product.mapper; import com.atguigu.gmall.product.model.BaseAttrInfo; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import java.util.List; public interface BaseAttrInfoMapper extends BaseMapper { /** * 根据分类Id 查询平台属性集合对象 | 编写xml 文件 * @param category1Id * @param category2Id * @param category3Id * @return */ List selectBaseAttrInfoList(Long category1Id, Long category2Id, Long category3Id); } ``` ```java package com.atguigu.gmall.product.mapper; import com.atguigu.gmall.product.model.BaseAttrValue; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface BaseAttrValueMapper extends BaseMapper { } ``` ### 5.3.2 MapperXml 在resources目录添加mapper文件夹,添加 BaseAttrInfoMapper.xml文件 ```xml ``` # 6. 接口测试工具 ## 6.1 Swagger介绍 (1)简介 Swagger 是一个规范完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务()。 它的主要作用是: 1. 使得前后端分离开发更加方便,有利于团队协作 2. 接口的文档在线自动生成,降低后端开发人员编写接口文档的负担 3. 功能测试 Spring已经将Swagger纳入自身的标准,建立了Spring-swagger项目,现在叫Springfox。通过在项目中引入Springfox ,即可非常简单快捷的使用Swagger。 (2)SpringBoot集成Swagger - 引入依赖 - 在`service-util`工程的config包中添加一个配置类 ```java package com.atguigu.gmall.common.config; import com.google.common.base.Predicates; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.ParameterBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.schema.ModelRef; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.service.Parameter; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.ArrayList; import java.util.List; /** * Swagger2配置信息 */ @Configuration @EnableSwagger2 public class Swagger2Config { @Bean public Docket webApiConfig(){ //添加head参数start List pars = new ArrayList<>(); ParameterBuilder tokenPar = new ParameterBuilder(); tokenPar.name("userId") .description("用户ID") .defaultValue("1") .modelRef(new ModelRef("string")) .parameterType("header") .required(false) .build(); pars.add(tokenPar.build()); ParameterBuilder tmpPar = new ParameterBuilder(); tmpPar.name("userTempId") .description("临时用户ID") .defaultValue("1") .modelRef(new ModelRef("string")) .parameterType("header") .required(false) .build(); pars.add(tmpPar.build()); //添加head参数end return new Docket(DocumentationType.SWAGGER_2) .groupName("webApi") .apiInfo(webApiInfo()) .select() //过滤掉admin路径下的所有页面 .paths(Predicates.and(PathSelectors.regex("/api/.*"))) //过滤掉所有error或error.*页面 //.paths(Predicates.not(PathSelectors.regex("/error.*"))) .build() .globalOperationParameters(pars); } @Bean public Docket adminApiConfig(){ return new Docket(DocumentationType.SWAGGER_2) .groupName("adminApi") .apiInfo(adminApiInfo()) .select() //只显示admin路径下的页面 .paths(Predicates.and(PathSelectors.regex("/admin/.*"))) .build(); } private ApiInfo webApiInfo(){ return new ApiInfoBuilder() .title("网站-API文档") .description("本文档描述了网站微服务接口定义") .version("1.0") .contact(new Contact("Helen", "http://atguigu.com", "55317332@qq.com")) .build(); } private ApiInfo adminApiInfo(){ return new ApiInfoBuilder() .title("后台管理系统-API文档") .description("本文档描述了后台管理系统微服务接口定义") .version("1.0") .contact(new Contact("Helen", "http://atguigu.com", "55317332@qq.com")) .build(); } } ``` 在`service-product`模块中启动类会自动扫描到该配置类 (3)Swagger常用注解 在Java类中添加Swagger的注解即可生成Swagger接口文档,常用Swagger注解如下: **@Api**:修饰整个类,描述Controller的作用 tags 指定名称 **@ApiOperation**:描述一个类的一个方法,或者说一个接口 value 指定名称 notes 添加备注 @ApiParam:单个参数的描述信息 @ApiModel:用对象来接收参数 **@ApiModelProperty**:用对象接收参数时,描述对象的一个字段 value 指定字段名称 @ApiResponse:HTTP响应其中1个描述 @ApiResponses:HTTP响应整体描述 @ApiIgnore:使用该注解忽略这个API @ApiError :发生错误返回的信息 **@ApiImplicitParam**:一个请求参数 @ApiImplicitParams:多个请求参数的描述信息 ## 6.2 knife4j (1)简介 knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名knife4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍! gitee地址:https://gitee.com/xiaoym/knife4j 官方文档:https://doc.xiaominfo.com/ 效果演示:http://knife4j.xiaominfo.com/doc.html (2)核心功能 该UI增强包主要包括两大核心功能:文档说明 和 在线调试 - 文档说明:根据Swagger的规范说明,详细列出接口文档的说明,包括接口地址、类型、请求示例、请求参数、响应示例、响应参数、响应码等信息,使用swagger-bootstrap-ui能根据该文档说明,对该接口的使用情况一目了然。 - 在线调试:提供在线接口联调的强大功能,自动解析当前接口参数,同时包含表单验证,调用参数可返回接口响应内容、headers、Curl请求命令实例、响应时间、响应状态码等信息,帮助开发者在线调试,而不必通过其他测试工具测试接口是否正确,简介、强大。 - 个性化配置:通过个性化ui配置项,可自定义UI的相关显示信息 - 离线文档:根据标准规范,生成的在线markdown离线文档,开发者可以进行拷贝生成markdown接口文档,通过其他第三方markdown转换工具转换成html或pdf,这样也可以放弃swagger2markdown组件 - 接口排序:自1.8.5后,ui支持了接口排序功能,例如一个注册功能主要包含了多个步骤,可以根据swagger-bootstrap-ui提供的接口排序规则实现接口的排序,step化接口操作,方便其他开发者进行接口对接 (3)快速集成 - 在`gmall-common`模块中的`pom.xml`文件中引入`knife4j`的依赖(已导入),如下: ```xml com.github.xiaoymin knife4j-spring-boot-starter ``` - 创建Knife4J配置文件 在`service-util`模块中新建包package com.atguigu.gmall.common.config,在此包中新建配置类,代码如下: ```java package com.atguigu.gmall.common.config; import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j; import com.google.common.base.Predicates; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.ParameterBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.schema.ModelRef; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.service.Parameter; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.ArrayList; import java.util.List; /** * Swagger2配置信息 */ @Configuration @EnableSwagger2 @EnableKnife4j @Import(BeanValidatorPluginsConfiguration.class) public class Knife4jConfig { @Bean public Docket webApiConfig(){ //添加head参数start List pars = new ArrayList<>(); ParameterBuilder tokenPar = new ParameterBuilder(); tokenPar.name("userId") .description("用户ID") .defaultValue("1") .modelRef(new ModelRef("string")) .parameterType("header") .required(false) .build(); pars.add(tokenPar.build()); ParameterBuilder tmpPar = new ParameterBuilder(); tmpPar.name("userTempId") .description("临时用户ID") .defaultValue("1") .modelRef(new ModelRef("string")) .parameterType("header") .required(false) .build(); pars.add(tmpPar.build()); //添加head参数end return new Docket(DocumentationType.SWAGGER_2) .groupName("webApi") .apiInfo(webApiInfo()) .select() //过滤掉admin路径下的所有页面 .paths(Predicates.and(PathSelectors.regex("/api/.*"))) //过滤掉所有error或error.*页面 //.paths(Predicates.not(PathSelectors.regex("/error.*"))) .build() .globalOperationParameters(pars); } @Bean public Docket adminApiConfig(){ return new Docket(DocumentationType.SWAGGER_2) .groupName("adminApi") .apiInfo(adminApiInfo()) .select() //只显示admin路径下的页面 .paths(Predicates.and(PathSelectors.regex("/admin/.*"))) .build(); } private ApiInfo webApiInfo(){ return new ApiInfoBuilder() .title("网站-API文档") .description("本文档描述了网站微服务接口定义") .version("1.0") .contact(new Contact("Helen", "http://atguigu.com", "55317332@qq.com")) .build(); } private ApiInfo adminApiInfo(){ return new ApiInfoBuilder() .title("后台管理系统-API文档") .description("本文档描述了后台管理系统微服务接口定义") .version("1.0") .contact(new Contact("Helen", "http://atguigu.com", "55317332@qq.com")) .build(); } } ``` 以上有两个注解需要特别说明,如下表: | 注解 | 说明 | | ----------------- | ------------------------------------------------------------ | | `@EnableSwagger2` | 该注解是Springfox-swagger框架提供的使用Swagger注解,该注解必须加 | | `@EnableKnife4j` | 该注解是`knife4j`提供的增强注解,Ui提供了例如动态参数、参数过滤、接口排序等增强功能,如果你想使用这些增强功能就必须加该注解,否则可以不用加 | - **tips:Knife4j中以及包含了Swagger所以要将Swagger的配置类注释掉,否则会有冲突** ![image-20221129000413774](assets/day01/image-20221129000413774.png) - 在浏览器输入地址:`http://host:port/doc.html` ![image-20221129000308206](assets/day01/image-20221129000308206.png) # 7 使用域名访问本地项目 ## 7.1 统一环境 我们现在访问页面使用的是: > http://localhost:port 有什么问题呢? 实际开发中,会有不同的环境: - 开发环境:自己的电脑 - 测试环境:提供给测试人员使用的环境 - 预发布环境:数据是和生成环境的数据一致,运行最新的项目代码进去测试 - 生产环境:项目最终发布上线的环境 如果不同环境使用不同的ip去访问,可能会出现一些问题。为了保证所有环境的一致,我们会在各种环境下都使用域名来访问。 我们将使用以下域名: >主域名是:www.gmall.com - 门户系统 > >检索系统域名:list.gmall.com > >商品系统域名:item.gmall.com > >购物车系统域名:cart.gmall.com > >登录系统域名: passport.gmal.com > >订单系统域名:order.gmall.com > >支付系统域名:payment.gmall.com > >活动系统域名:activity.gmall.com > >网关域名:api.gmall.com 最终,我们希望这些域名指向的还是我们本机的某个端口。 那么,当我们在浏览器输入一个域名时,浏览器是如何找到对应服务的ip和端口的呢? ## 7.2 域名解析 一个域名一定会被解析为一个或多个ip。这一般会包含两步: - 本地域名解析 浏览器会首先在本机的hosts文件中查找域名映射的IP地址,如果查找到就返回IP ,没找到则进行域名服务器解析,一般本地解析都会失败,因为默认这个文件是空的。 - Windows下的hosts文件地址: > C:/Windows/System32/drivers/etc/hosts - Linux下的hosts文件所在路径: > /etc/hosts 示例如下: ``` # gmall 127.0.0.1 www.gmall.com item.gmall.com order.gmall.com payment.gmall.com 127.0.0.1 activity.gmall.com passport.gmall.com cart.gmall.com list.gmall.com api.gmall.com ``` - 域名服务器解析 本地解析失败,才会进行域名服务器解析,域名服务器就是网络中的一台计算机,里面记录了所有注册备案的域名和ip映射关系,一般只要域名是正确的,并且备案通过,一定能找到。 **注意:手动修改hosts文件不会立即生效,需要刷新本地DNS才会生效** 在命令行中执行命令刷新: ``` ipconfig/flushdns ``` ![image-20221223102851075](assets/image-20221223102851075.png)