## 第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)