1.初始化项目

idea新建空项目

添加各个模块

gulimall-product商品服务

gulimall-ware仓储服务

gulimall-order订单服务

同理创建
gulimall-coupon优惠券服务
gulimall-member会员服务
同时添加springboot web依赖和springcloud-routing openfeign依赖

聚合模块

在根目录下添加pom文件,添加下列代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.leggasai.gulimall</groupId>
<artifactId>gulimall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimall</name>
<description>聚合服务</description>
<packaging>pom</packaging>
<modules>
<module>gulimall-ware</module>
<module>gulimall-member</module>
<module>gulimall-order</module>
<module>gulimall-product</module>
<module>gulimall-coupon</module>
</modules>
</project>

将pom文件加入maven,刷新maven

更改.gitignore文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
HELP.md  
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/

### VS Code ###
.vscode/


**/mvnw
**/mvnw.cmd

**/.mvn
**/target/

**/.gitignore

2.数据库设计

数据库分为 订单数据库、用户数据库、库存数据库、商品数据库、优惠数据库
gmall_数据库设计.pdm

订单数据库表

包含了订单信息,退货信息,订单项等
gulimall_oms.sql

用户数据库表

包含了用户信息,会员积分,用户收藏,收货地址等
gulimall_ums.sql

库存数据库表

包含库存工作单,采购单,库存表等
gulimall_wms.sql

商品数据库表

包含SPU表,SKU表,商品分类表,商品评价表,属性表等
gulimall_pms.sql

优惠数据库表

包含优惠券信息,积分表,秒杀信息等,商品优惠信息等
gulimall_sms.sql

3.快速开发代码生成

主要使用人人开源的renren-generator、renren-fast、renren-fast-vue

引入必要的依赖

创建公共模块

创建common模块,在其pom文件中添加一些公共的依赖,如mysql-connector-java,mybatis等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<?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>gulimall</artifactId>
<groupId>com.leggasai.gulimall</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>gulimall-common</artifactId>
<description>公共类</description>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<mybatis-plus.version>3.2.0</mybatis-plus.version>
<lombok.version>1.18.8</lombok.version>
<httpcore.version>4.4.12</httpcore.version>
<commons-lang.version>2.6</commons-lang.version>
<mysql-connector-java.version>8.0.17</mysql-connector-java.version>
<servlet-api.version>2.5</servlet-api.version>
</properties>
<dependencies>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>${httpcore.version}</version>
</dependency>

<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons-lang.version}</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>${servlet-api.version}</version>
<scope>provided</scope>
</dependency>

</dependencies>


</project>

其他模块在其pom文件中引入common模块

1
2
3
4
5
<dependency>  
<groupId>com.leggasai.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

配置Mybatis-Plus

官方链接:简介 | MyBatis-Plus (baomidou.com)

1.添加依赖

在commom模块的pom中添加mybatis-plus的代码

1
2
3
4
5
<dependency>  
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>

2.配置

配置数据源

在各个模块的application.yml中添加如下配置

1
2
3
4
5
6
spring:  
datasource:
username: root
password: root
url: jdbc:mysql://192.168.241.128:3306/gulimall_pms
driver-class-name: com.mysql.cj.jdbc.Driver

配置Mybatis-Plus

在各个模块的application.yml中添加如下配置

1
2
3
4
5
mybatis-plus:  
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto

4.分布式组件

  • Nacos:注册中心(服务发现/注册)
  • Nacos:配置中心(动态配置管理)
  • Ribbon:负载均衡
  • Feign:声明式HTTP客户端,RPC
  • Sentinel:服务容错(服务限流、服务降级、服务熔断)
  • Gateway:API网关
  • Sleuth:调用链监控
  • Seata:分布式事务解决方案

在common模块中引入Spring-cloud-Alibaba依赖
版本说明 · alibaba/spring-cloud-alibaba Wiki (github.com)

1
2
3
4
5
6
7
8
9
10
11
<dependencyManagement>  
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

dependencyManagement的用法:

  1. 对项目所依赖jar包进行版本管理的管理器。
  2. 使用pom.xml中的dependencyManagement元索能让所有在子模块中引用一个依赖而不用显式的列出版本号。也就是子模块不需要列出版本,子模块中的pom依赖会找到dependencyManagement所在jar包的版本,并引用这个jar对应的版本号。
  3. dependencyManagement只是声明依赖, 并不实现引入,因此子项目需要显示的声明需要用的依赖。
  4. 一般用于父工程,如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。

4.1 Nacos

Quick Start for Nacos Spring Cloud Projects

本地下载Nacos

下载链接:Nacos
本地启动:

1
2
#在bin/路径下运行
startup.cmd -m standalone

启动成功截图

访问地址:Nacos

服务发现

在子模块application.yml中添加如下代码

1
2
3
4
5
6
7
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: gulimall-member

在启动类上加入注解@EnableDiscoveryClient以开启服务发现

1
2
3
4
5
6
7
8
9
10
@MapperScan("com.leggasai.gulimall.member.dao")
@SpringBootApplication
@EnableDiscoveryClient
public class GulimallCouponApplication {

public static void main(String[] args) {
SpringApplication.run(GulimallCouponApplication.class, args);
}

}

远程调用

在pom模块中添加openFeign依赖

1
2
3
4
<dependency>  
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

在主类中开启,添加@EnableFeignClients(basePackages = “com.leggasai.gulimall.member.feign”),对应你存放远程调用服务的包路径

1
2
3
4
5
6
7
8
9
10
11
@MapperScan("com.leggasai.gulimall.member.dao")
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.leggasai.gulimall.member.feign")
public class GulimallMemberApplication {

public static void main(String[] args) {
SpringApplication.run(GulimallMemberApplication.class, args);
}

}

新建远程调用服务文件,路径对应上述basePackages,并编写接口
这里的接口要和被调用类中声明的一致,包括接口路径

1
2
3
4
5
6
7
8
/**  
* 声明远程调用
*/
@FeignClient("gulimall-coupon")
public interface CouponFeignService {
@RequestMapping("/coupon/coupon/test")
public R test();
}
1
2
3
4
5
6
7
8
//被调用类 couponController,前面有一个@RequestMapping("coupon/coupon")
//因此接口路径为/coupon/coupon/test
@RequestMapping("/test")
public R test(){
CouponEntity couponEntity = new CouponEntity();
couponEntity.setCouponName("满减200-100");
return R.ok().put("coupons", Arrays.asList(couponEntity));
}

使用远程调用,注入远程调用服务,调用其中的接口方法即可

1
2
3
4
5
6
7
8
9
10
@Autowired  
CouponFeignService couponFeignService;
@RequestMapping("/test")
public R test(){
R r = couponFeignService.test();
HashMap<String, Object> map = new HashMap<>();
map.put("member","jyc");
map.put("coupons",r.get("coupons"));
return R.ok(map);
}

配置中心

参考文档Nacos-config-example
在pom模块中添加依赖

1
2
3
4
5
<!--配置中心-->  
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

注意新版本需要引入spring-cloud-starter-bootstrap依赖来加载bootstrap.properties文件

1
2
3
4
5
<dependency>  
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>3.0.2</version>
</dependency>

新建bootstrap.properties文件,不能在application.yml中写,否则不能动态生效

1
2
spring.application.name=gulimall-coupon  
spring.cloud.nacos.config.server-addr=127.0.0.1:8848

打开Nacos配置中心,添加配置,Data ID必须为spring.application.name加properties

  • 命名空间:用来配置隔离
    • 默认使用public(保留空间),默认新增的所有配置都在public空间
    • 应用:
      • 配置开发,测试,生产环境的不同命名空间
      • 每一个微服务相互隔离,创建不同的命名空间
    • 需要使用spring.cloud.nacos.config.namespace=id,指定对应命名控件ID
  • 配置集:所有配置的集合
  • 配置集ID:Data ID
  • 配置分组:对配置进行分组
    • 默认所有的配置集都属于DEFAULT_GROUP
    • 需要使用spring.cloud.nacos.config.group指定对应的配置分组

本项目规则:

  • 每个微服务创建自己的命名空间,使用配置分组区分环境,dev、prop、test
  • 微服务任何配置信息,任何配置文件都可以放在配置中心中
  • 配置中心的配置会被优先使用

网关

网关是所有流量的入口,功能包括路由转发、权限校验、限流控制。本项目使用Spring Cloud Gateway作为网关。
Spring Cloud Gateway

  1. 新建gulimall-gateway模块

  2. 修改pom文件和根目录下的pom文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.6</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.leggasai.gulimall</groupId>
    <artifactId>gulimall-gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gulimall-gateway</name>
    <description>API Gateway</description>
    <properties>
    <java.version>8</java.version>
    <spring-cloud.version>2020.0.1</spring-cloud.version>
    </properties>
    <dependencies>
    <dependency>
    <groupId>com.leggasai.gulimall</groupId>
    <artifactId>gulimall-common</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    </dependency>

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>

    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>

    </project>

  3. 开启服务的注册和发现。先在Nacos配置中心处,新建一个命名空间gateway,用于存放和gateway网关有关的配置项。

    1
    2
    3
    spring.application.name=gulimall-gateway  
    spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    spring.cloud.nacos.config.namespace=6f924aa0-39f6-4a86-9be9-f13562a9af7f
  4. 排除数据库相关配置。在Application.java中添加如下

    1
    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

    否则运行会报错

  5. 添加路由规则,新建application.yml(此处为了本地调式方便,以后统一写在Nacos配置中心)

    1
    2
    3
    4
    5
    6
    7
    8
    spring:
    cloud:
    gateway:
    routes:
    - id: test_route
    uri: https://www.baidu.com
    predicates:
    - Query=url, baidu

5.商品服务

三级分类

递归查询

需要实现类似京东的商品分类树形结构,pms_category表中通过cat_idparent_cid建立层级关系。使用递归查询构建树形结构

逻辑代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//CategoryServiceImpl.java
@Override
public List<CategoryEntity> listWithTree() {
List<CategoryEntity> entities = this.baseMapper.selectList(null);
List<CategoryEntity> levelOneMenus = entities.stream().filter(categoryEntity ->
categoryEntity.getParentCid().equals(0L)
).map((item) -> {
item.setChildren(getChildren(item, entities));
return item;
}).sorted((t1,t2)->{
return (t1.getSort()==null?0:t1.getSort())-(t2.getSort()==null?0:t2.getSort());
}).collect(Collectors.toList());
return levelOneMenus;
}

private List<CategoryEntity> getChildren(CategoryEntity root,List<CategoryEntity> list){
List<CategoryEntity> children = list.stream().filter(categoryEntity ->
categoryEntity.getParentCid().equals(root.getCatId())
).map((item) -> {
item.setChildren(getChildren(item, list));
return item;
}).sorted((t1,t2)->{
return (t1.getSort()==null?0:t1.getSort())-(t2.getSort()==null?0:t2.getSort());
}).collect(Collectors.toList());
return children;
}

配置网关

先将后台管理的请求转发到网关
修改renren-fast-vue中static\config\index.js中的代码如下:

1
window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';

配置gateway的application.yml

拦截所有/api/**的请求,并进行路径重写,重写为/renren-fast/**
参考文档:Spring Cloud Gateway

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
cloud:
gateway:
routes:
- id: test_route
uri: https://www.baidu.com
predicates:
- Query=url, baidu

- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>/?.*), /renren-fast/$\{segment}

成功显示验证码

配置跨域

在gateway网关中配置跨域,添加config文件夹
Spring Cloud Gateway 解决跨域问题_springcloudgateway跨域配置-CSDN博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration  
public class CorsConfig {
@Bean
public CorsWebFilter corsWebFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
//1.配置跨域
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOriginPattern("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}

菜单增删改查

配置网关路由,注意顺序,更具体的路由必须在前,否则会被/api/**拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
- id: product_route  
uri: lb://gulimall-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/(?<segment>/?.*), /$\{segment}

- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>/?.*), /renren-fast/$\{segment}

在gulimall-product中开启服务发现

1
2
3
4
5
6
7
cloud:  
nacos:
discovery:
server-addr: 127.0.0.1:8848

application:
name: gulimall-product

删除

开启逻辑删除,配置Mybatis-plus

1
2
3
4
5
6
7
mybatis-plus:  
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0

如果逻辑删除字段的值需要和上述全局配置不同,可以在实体类上通过注解实现

1
2
3
4
/**  
* 是否显示[0-不显示(删除),1显示(删除)]
*/@TableLogic(value = "1",delval = "0")
private Integer showStatus;

问题

P16安装SASS

先找和本地node版本对应的node-sass的版本

1
node -version

修改前端package.json中node-sass版本至对应的版本

本人node为14.17.1,node-sass为4.14.1

单独安装node-sass

1
npm i node-sass@4.14.1 --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/

若执行成功

1
2
npm install
npm run dev

若失败,先清除node-sass缓存,再查找其他原因,可以降低node版本

1
2
npm rebuild node-sass
npm uninstall node-sass

P46验证码加载不出

首先renren-fast不要引入common依赖,而是根据renren-fast的springboot版本单独引入对应的spring-cloud-alibaba并进行依赖管理。然后引入spring-cloud-starter-alibaba-nacos-discoveryspring-cloud-loadbalancer(实际发现不引入也行?)
版本说明 · alibaba/spring-cloud-alibaba Wiki (github.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    <!--注册发现-->  
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

SKU和SPU概念

SPU:标准化产品单元(Standard Product Unit),是商品信息聚合的最小单位,是一组可复用标准化信息的集合,主要也是为了在交易端对一组同类型商品做页面的聚合展示,解决的是一品多型号多规格等等多属性的问题;
例如:iPhone X 可以确定一个产品即为一个SPU。

SKU:最小的库存单位(StockKeeping Unit),sku是库存存贮的最小单位,商品的进货、销售、售价、库存等最终都是打在sku身上的,最终的交易都决定在一个sku个体上;
例如:iPhone X 64G 银色 则是一个SKU。

可以理解为类和对象的关系,SPU是类/模板,SKU是对象/实例,有多种属性。一个SPU对应多个SKU。