一.项目简介

1.项目背景

电商模式 谷粒商城使用的B2C模式,销售自营的商品给客户

市面上5种常见的电商模式B2B、B2C、C2B、C2C、O2O

1、B2B模式:
business to business,是指商家与商家建立的商业关系。如阿里巴巴。

2、B2C模式:
business to consumer,商对客模式。即通常说的商业零售,供应商直接把商品卖给用户。如:苏宁易购、京东、天猫、小米商城。

3、C2B模式:
customer to business,即消费者对企业。先有消费者需求而后有企业生产。例如众筹类的商城。

4、C2C模式:
customer to consumer,客户之间直接把东西放上网上去卖。如:淘宝、闲鱼。

5、O2O模式:
online to offline,线上线下。线上快速支付,线下优质服务。如:饿了么、美团、京东到家。

2.项目架构图

项目的架构图

微服务划分图

image-20230518015831413

3.项目技术和特色

  • 前后端分离开发,开发基于VUE的后台管理系统
  • SpringCloud全新解决方案
  • 应用监控、限流、网关、熔断降级等分布式方案 全方位涉及
  • 包含分布式事务、分布式锁等技术难点
  • 分析高并发场景的编码方式、线程池、异步编排等使用
  • 压力测试与性能优化
  • 各种集群技术的区别已经使用
  • CI/CD的使用

4.项目的前置要求

  • 熟悉SpringBoot以及常见的整合方案
  • 了解SpringCloud
  • 熟悉Maven git
  • 熟悉linux、redis、docker基本操作
  • 了解html、css、js、vue
  • 熟练使用idea开发项目

二.分布式基础概念

1.微服务

微服务架构风格,就像是一个单独的应用程序开发为一套小服务,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API。这些服务围绕业务能力来构建,并通过完全自动化部署机制来独立部署。这些服务使用不同的编程语言编写,以及不同的数据存储技术,并保持最低限度的集中式管理。

简而言之:拒绝大型单体应用,基于业务边界进行服务微化拆分,各个应用独立部署运行。

2.集群|分布式|节点

集群是一个物理形态 ,分布式是个工作方式

分布式: 将不同的业务分布在不同的地方

集群: 集群指的是将几台服务器集中在一起,实现同一业务

分布式中的每一个节点,都可以做集群。而集群不一定是分布式的。

节点: 集群中的一个服务器

3.远程调用

在分布式系统中,各个服务可能处于不同主机,但是服务之间不可避免的需要相互调用,我们称之为远程调用。

SpringCloud中使用HTTP+JSON的方式完成远程调用

image-20230518010847966

4.负载均衡

image-20230518011030343
分布式系统中,A服务需要调用B服务,B服务在多台机器上都存在,A调用任意一个服务均可完成功能。为了使每一个服务器都不要太忙或者太闲,我们可以使用负载均衡的调用每一个服务器,提升网站的健壮性。

常见的负载均衡算法:

轮询: 为第一个请求选择健康池中的第一台后端服务器,然后按顺序往后依次选择,直到最后一个,然后循环。

最小连接: 有限选择连接数量少,也就是压力最小的后端服务器,在会话较长的情况下可以考虑采取这种方式。

随机法:通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。

源地址哈希法:源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。

加权轮询法:不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。

加权随机法:与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。

5.服务注册|发现|注册中心

image-20230518012211042

6.配置中心

每一个服务最终会有大量的配置,并且每个服务都可能部署在多台机器上。我们经常需要变更配置,我们可以让每个服务在配置中心获取自己的配置。

image-20230518012538349

7.服务熔断和服务降级

在微服务的架构中,微服务之间通过网络进行通信,存在相互依赖,当其中的一个服务不可用时,有可能会造成雪崩效应。要防止这样的情况,必须要有容错机制来保护服务。

服务熔断: 设置服务的超时,当被调用的服务经常失败到达某个阈值,我们可以开启短路保护机制,后来的请求不再去调用这个服务。本地直接返回默认的数据。

服务降级: 在运维期间,当系统处于高峰期,系统资源紧张,我们可以让非核心的业务降级运行。降级:某些业务不处理,或者简单处理[抛异常、返回NULL、调用mock数据、调用Fallback处理逻辑]。

image-20230518013254404

image-20230518013624395

8.API网关

在微服务的架构中,API Gateway作为整体架构的重要组件,他抽象了微服务中都需要的公共功能,同时提供了客户端的负载均衡,服务自动熔断,灰度发布、统一认证、限流流控、日志统计等丰富功能,帮助我们解决很多API管理难题。
image-20230518014732955

三.环境搭建

1.安装虚拟机

需要使用虚拟机安装相关的软件和部署集群

VMWare虚拟机安装Linux教程 | The Blog (qingling.icu)

将Linxu的IP地址固定下来

Linux设置静态IP | The Blog (qingling.icu)

2.Docker安装与配置

包含docker安装、开启开机自启动、配置镜像加速服务

Docker容器化技术 | The Blog (qingling.icu)image-20230518141429712

image-20230518145456236

3.Docker安装mysql

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
#下载mysql的镜像文件 这里以下载mysql5.7为例
docker pull mysql:5.7

#查看是否下载成功
docker images

#使用MySQL的镜像启动一个容器
docker run -p 3306:3306 --name mysql \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7
#参数说明
# -p 3306:3306:将容器的 3306 端口映射到主机的 3306 端口
#-v /mydata/mysql/conf:/etc/mysql:将配置文件夹挂载到主机
#-v /mydata/mysql/log:/var/log/mysql:将日志文件夹挂载到主机
#-v /mydata/mysql/data:/var/lib/mysql/:将配置文件夹挂载到主机
#-e MYSQL_ROOT_PASSWORD=root:初始化 root 用户的密码

#查看是否运行成功 这时使用Navicat就可以连接上了 连接不上 关闭防火墙
docker ps #查看正在运行的容器

#进入容器的内部
docker exec -it mysql /bin/bash

#查看容器内部的目录结构 我们可以看到容器的内部是一个小小的linux系统
ls / #注意中间有一个空格

#查看与mysql相关的目录
whereis mysql

#退出容器
exit;

#在容器外部查看mysql挂载在本地的三个文件
#容器内部文件的变化会同步在这三个文件中 conf data log
#外部修改 也会同步到内部
cd /mydata/mysql
ls

#创建一个mysql的配置文件设置字符编码 将下面的内容粘贴到配置文件中
#1.创建配置文件
vi /mydata/mysql/conf/my.cnf
#2.将以下的内容粘贴到配置文件配置文件内容
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
init_connect='SET collation_connection = utf8_unicode_ci'
init_connect='SET NAMES utf8'
character-set-server=utf8
collation-server=utf8_unicode_ci
skip-character-set-client-handshake
skip-name-resolve
#3.重启MySQl的服务 让配置文件生效 这个配置文件也会同步到容器内部
docker restart mysql

#设置开机自启动
docker update mysql --restart=always

4.Docker安装redis

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
#下载redis的镜像文件 直接下载最新的
docker pull redis

#创建redis挂载在宿主机的配置文件和目录
mkdir -p /mydata/redis/conf #创建目录
touch /mydata/redis/conf/redis.conf #创建配置文件

#创建并启动容器 这是以配置文件的方式启动 \前面有一个空格
docker run -p 6379:6379 --name redis \
-v /mydata/redis/data:/data \
-v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf

#检查容器的运行情况
docker ps

#测试客户端的使用
docker exec -it redis redis-cli

#可以在redis客户端中使用如下命令
127.0.0.1:6379> set name tom #存值
OK
127.0.0.1:6379> get name #取值
"tom"

#redis中的数据不能持久化 关机重启之后数据会丢失
#通过以下的配置 实现redis的持久化操作 AOF的持久化
vi /mydata/redis/conf/redis.conf #修改之前创建的配置文件
#添加如下配置
appendonly yes

#重启redis 让配置生效
docker restart redis

#设置开机自启动
docker update redis --restart=always

5.统一开发环境

其中Java使用的是java8及以上

Maven 配置阿里云的镜像 profiles

开发环境的搭建 | The Blog (qingling.icu)

IDEA中修改使用自己安装的Maven

IDEA插件:Lombok|MybatisX

VSCode插件:

image-20230518160444927

image-20230518155308167

6.配置Git

官网下载:https://git-scm.com/downloads

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#以下的操作在下载安装完毕之后进行
#1.鼠标在桌面右键 选择Git Bash Here 打开控制台

#2.配置用户名和邮箱
git config --global user.name "用户名" #随意
git config --global user.email "邮箱" #自己的邮箱

#3.配置SSH免密连接
#生成密钥
ssh-keygen -t rsa -C "在码云上注册的邮箱地址" #连续三次回车
#查看密钥并复制公钥的内容
cat ~/.ssh/id_rsa.pub

#4.将密钥的复制到码云的SSH公钥中
#4.1添加公钥 公钥名随意 公钥内容就是上面复制的内容

#5.测试
ssh -T git@gitee.com

常用命令

image-20230518161145808

7.从Gitee上初始化一个项目

我们是先从码云上初始化一个项目 然后拉取到本地

创建一个仓库

image-20230518163703370

复制刚才创建仓库的地址

image-20230518163838162

将工程导入到IDEA中

image-20230518164005690

导入成功

image-20230518164217773

8.创建微服务模块

image-20230518171355588

9.数据库设计

image-20230518171546236

image-20230518173203605

10.后台管理系统的搭建

10.1.后端的搭建

后台管理系统使用的是人人开源提供的一个脚手架:

仓库的地址信息:

仓库主页:https://gitee.com/renrenio

后端renren-fast:https://gitee.com/renrenio/renren-fast.git

前端renren-fast-vue:https://gitee.com/renrenio/renren-fast-vue.git

将其作为一个模块集成到gulimall中

image-20230518221222845

同时创建好这个脚手架需要的数据库

image-20230518221433314

修改配置文件中数据库账号密码ip的设置,启动项目看有没有报错

10.2 前端的搭建

安装node.js 和 npm包管理工具 (node.js自带npm包管理工具)

1
2
3
4
5
6
7
#检查是否安装成功
#出现版本号就是安装成功了
node -v
npm -v

#npm配置淘宝的镜像 下载依赖的速度更快
npm config set registry https://registry.npmmirror.com

将项目导入到vscode中,并安装依赖

1
2
#安装依赖的命令
npm install

image-20230518223021928

下载完成之后,运行前端的项目

1
2
#运行命令
npm run dev

运行成功过之后访问 http://localhost:8001

成功的页面显示如下

image-20230518223525151

**使用账号:admin密码:admin 登录 **

可以登录成功并显示以下的页面说明前后端的联调通过

image-20230518223921179

11.快速开发-逆向工程的搭建

1.代码生成器快速使用案例

人人开源代码生成器的地址:https://gitee.com/renrenio/renren-generator.git

集成代码生成器

image-20230518230016421

修改代码生成器的配置文件

1.修改yml配置文件中数据库的连接信息

2.修改properties中与代码生成相关的配置信息

配置举例 根据不同的模块 不同的数据库 配置不同

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
#代码生成器配置信息

mainPath=com.atguigu
#包名
package=com.atguigu.gulimall
moduleName=product
#作者
author=JasonGong
#Email
email=JasonGong@gmail.com
#表前缀(类名不会包含表前缀)
tablePrefix=pms_


#以下的配置信息一般不动
#类型转换配置信息
tinyint=Integer
smallint=Integer
mediumint=Integer
int=Integer
integer=Integer
bigint=Long
float=Float
double=Double
decimal=BigDecimal
bit=Boolean

char=String
varchar=String
tinytext=String
text=String
mediumtext=String
longtext=String


date=Date
datetime=Date
timestamp=Date

NUMBER=Integer
INT=Integer
INTEGER=Integer
BINARY_INTEGER=Integer
LONG=String
FLOAT=Float
BINARY_FLOAT=Float
DOUBLE=Double
BINARY_DOUBLE=Double
DECIMAL=BigDecimal
CHAR=String
VARCHAR=String
VARCHAR2=String
NVARCHAR=String
NVARCHAR2=String
CLOB=String
BLOB=String
DATE=Date
DATETIME=Date
TIMESTAMP=Date
TIMESTAMP(6)=Date

int8=Long
int4=Integer
int2=Integer
numeric=BigDecimal

nvarchar=String

3.运行项目

通过http://localhost:81 访问

image-20230518234030568

4.使用生成代码的功能生成代码

image-20230518234311272

解压之后的文件如下

image-20230518234941961

压缩包中的文件如下所示

image-20230518234922087

由于项目中没有使用shiro安全框架,我们注释掉安全框架的注解生成模板

image-20230519002146272

配置每个模块的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://192.168.111.100:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml #定义mapper.xml文件的位置
global-config:
db-config:
id-type: auto #主键的生成规则

测试生成的代码

image-20230519005131468

2.代码生成器的使用步骤

1.修改代码生成器的配置文件generator.properties

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
#代码生成器配置信息

mainPath=com.atguigu
#包名
package=com.atguigu.gulimall
moduleName=ware #1.修改模块名
#作者
author=JasonGong
#Email
email=JasonGong@gmail.com
#表前缀(类名不会包含表前缀)
tablePrefix=wms_ #2.数据库表的前缀


#以下的配置信息一般不动
#类型转换配置信息
tinyint=Integer
smallint=Integer
mediumint=Integer
int=Integer
integer=Integer
bigint=Long
float=Float
double=Double
decimal=BigDecimal
bit=Boolean

char=String
varchar=String
tinytext=String
text=String
mediumtext=String
longtext=String


date=Date
datetime=Date
timestamp=Date

NUMBER=Integer
INT=Integer
INTEGER=Integer
BINARY_INTEGER=Integer
LONG=String
FLOAT=Float
BINARY_FLOAT=Float
DOUBLE=Double
BINARY_DOUBLE=Double
DECIMAL=BigDecimal
CHAR=String
VARCHAR=String
VARCHAR2=String
NVARCHAR=String
NVARCHAR2=String
CLOB=String
BLOB=String
DATE=Date
DATETIME=Date
TIMESTAMP=Date
TIMESTAMP(6)=Date

int8=Long
int4=Integer
int2=Integer
numeric=BigDecimal

nvarchar=String

2修改要生成增删改查代码的数据库的连接配置application.yml

只用配置数据库的连接信息即可

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
server:
port: 81

# mysql
spring:
main:
allow-circular-references: true
datasource:
type: com.alibaba.druid.pool.DruidDataSource
#MySQL配置
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.111.100:3306/gulimall_wms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
#oracle配置
# driverClassName: oracle.jdbc.OracleDriver
# url: jdbc:oracle:thin:@47.100.206.162:1521:xe
# username: renren
# password: 123456
#SQLServer配置
# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
# url: jdbc:sqlserver://192.168.10.10:1433;DatabaseName=renren_fast
# username: sa
# password: 123456
#PostgreSQL配置
# driverClassName: org.postgresql.Driver
# url: jdbc:postgresql://192.168.10.10:5432/renren_fast
# username: postgres
# password: 123456



jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
web:
resources:
static-locations: classpath:/static/,classpath:/views/

#mongodb:
# host: localhost
# port: 27017
# auth: false #是否使用密码验证
# username: tincery
# password: renren
# source: 123456
# database: test

mybatis-plus:
mapperLocations: classpath:mapper/**/*.xml


pagehelper:
reasonable: true
supportMethodsArguments: true
params: count=countSql


#指定数据库,可选值有【mysql、oracle、sqlserver、postgresql、mongodb】
renren:
database: mysql

3.启动服务生成代码,将生成的代码解压并粘贴到对应的模块中去

将main这个目录替换原来模块中的main目录即可,sql文件暂时不用管

4.创建模块的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://192.168.111.100:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml #定义mapper.xml文件的位置
global-config:
db-config:
id-type: auto #主键的生成规则

5.启动测试生成的代码

在启动的时候,我们要处理好代码依赖的包,这些包都存在与生成代码的模块中,我们在生成代码的模块中复制过来即可,然后手动的引入相应的包,处理好报错信息。

3.后台搭建完成之后的项目树

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
C:.
│ .gitignore
│ LICENSE
│ pom.xml

├─.idea
│ │ .gitignore
│ │ compiler.xml
│ │ encodings.xml
│ │ jarRepositories.xml
│ │ misc.xml
│ │ uiDesigner.xml
│ │ vcs.xml
│ │ workspace.xml
│ │
│ └─codeStyles
│ codeStyleConfig.xml
│ Project.xml

├─gulimall-common
│ │ pom.xml
│ │
│ ├─src
│ │ ├─main
│ │ │ ├─java
│ │ │ │ └─com
│ │ │ │ └─atguigu
│ │ │ │ └─common
│ │ │ │ ├─exception
│ │ │ │ │ RRException.java
│ │ │ │ │
│ │ │ │ ├─utils
│ │ │ │ │ Constant.java
│ │ │ │ │ PageUtils.java
│ │ │ │ │ Query.java
│ │ │ │ │ R.java
│ │ │ │ │
│ │ │ │ └─xss
│ │ │ │ HTMLFilter.java
│ │ │ │ SQLFilter.java
│ │ │ │
│ │ │ └─resources
│ │ └─test
│ │ └─java
│ └─target
│ ├─classes
│ │ └─com
│ │ └─atguigu
│ │ └─common
│ │ ├─exception
│ │ │ RRException.class
│ │ │
│ │ ├─utils
│ │ │ Constant$MenuType.class
│ │ │ Constant$ScheduleStatus.class
│ │ │ Constant.class
│ │ │ PageUtils.class
│ │ │ Query.class
│ │ │ R.class
│ │ │
│ │ └─xss
│ │ HTMLFilter.class
│ │ SQLFilter.class
│ │
│ └─generated-sources
│ └─annotations
├─gulimall-coupon
│ │ .gitignore
│ │ gulimall-coupon.iml
│ │ HELP.md
│ │ mvnw
│ │ mvnw.cmd
│ │ pom.xml
│ │
│ ├─.mvn
│ │ └─wrapper
│ │ maven-wrapper.jar
│ │ maven-wrapper.properties
│ │
│ ├─src
│ │ ├─main
│ │ │ ├─java
│ │ │ │ └─com
│ │ │ │ └─atguigu
│ │ │ │ └─gulimall
│ │ │ │ └─coupon
│ │ │ │ │ GulimallCouponApplication.java
│ │ │ │ │
│ │ │ │ ├─controller
│ │ │ │ │ CouponController.java
│ │ │ │ │ CouponHistoryController.java
│ │ │ │ │ CouponSpuCategoryRelationController.java
│ │ │ │ │ CouponSpuRelationController.java
│ │ │ │ │ HomeAdvController.java
│ │ │ │ │ HomeSubjectController.java
│ │ │ │ │ HomeSubjectSpuController.java
│ │ │ │ │ MemberPriceController.java
│ │ │ │ │ SeckillPromotionController.java
│ │ │ │ │ SeckillSessionController.java
│ │ │ │ │ SeckillSkuNoticeController.java
│ │ │ │ │ SeckillSkuRelationController.java
│ │ │ │ │ SkuFullReductionController.java
│ │ │ │ │ SkuLadderController.java
│ │ │ │ │ SpuBoundsController.java
│ │ │ │ │
│ │ │ │ ├─dao
│ │ │ │ │ CouponDao.java
│ │ │ │ │ CouponHistoryDao.java
│ │ │ │ │ CouponSpuCategoryRelationDao.java
│ │ │ │ │ CouponSpuRelationDao.java
│ │ │ │ │ HomeAdvDao.java
│ │ │ │ │ HomeSubjectDao.java
│ │ │ │ │ HomeSubjectSpuDao.java
│ │ │ │ │ MemberPriceDao.java
│ │ │ │ │ SeckillPromotionDao.java
│ │ │ │ │ SeckillSessionDao.java
│ │ │ │ │ SeckillSkuNoticeDao.java
│ │ │ │ │ SeckillSkuRelationDao.java
│ │ │ │ │ SkuFullReductionDao.java
│ │ │ │ │ SkuLadderDao.java
│ │ │ │ │ SpuBoundsDao.java
│ │ │ │ │
│ │ │ │ ├─entity
│ │ │ │ │ CouponEntity.java
│ │ │ │ │ CouponHistoryEntity.java
│ │ │ │ │ CouponSpuCategoryRelationEntity.java
│ │ │ │ │ CouponSpuRelationEntity.java
│ │ │ │ │ HomeAdvEntity.java
│ │ │ │ │ HomeSubjectEntity.java
│ │ │ │ │ HomeSubjectSpuEntity.java
│ │ │ │ │ MemberPriceEntity.java
│ │ │ │ │ SeckillPromotionEntity.java
│ │ │ │ │ SeckillSessionEntity.java
│ │ │ │ │ SeckillSkuNoticeEntity.java
│ │ │ │ │ SeckillSkuRelationEntity.java
│ │ │ │ │ SkuFullReductionEntity.java
│ │ │ │ │ SkuLadderEntity.java
│ │ │ │ │ SpuBoundsEntity.java
│ │ │ │ │
│ │ │ │ └─service
│ │ │ │ │ CouponHistoryService.java
│ │ │ │ │ CouponService.java
│ │ │ │ │ CouponSpuCategoryRelationService.java
│ │ │ │ │ CouponSpuRelationService.java
│ │ │ │ │ HomeAdvService.java
│ │ │ │ │ HomeSubjectService.java
│ │ │ │ │ HomeSubjectSpuService.java
│ │ │ │ │ MemberPriceService.java
│ │ │ │ │ SeckillPromotionService.java
│ │ │ │ │ SeckillSessionService.java
│ │ │ │ │ SeckillSkuNoticeService.java
│ │ │ │ │ SeckillSkuRelationService.java
│ │ │ │ │ SkuFullReductionService.java
│ │ │ │ │ SkuLadderService.java
│ │ │ │ │ SpuBoundsService.java
│ │ │ │ │
│ │ │ │ └─impl
│ │ │ │ CouponHistoryServiceImpl.java
│ │ │ │ CouponServiceImpl.java
│ │ │ │ CouponSpuCategoryRelationServiceImpl.java
│ │ │ │ CouponSpuRelationServiceImpl.java
│ │ │ │ HomeAdvServiceImpl.java
│ │ │ │ HomeSubjectServiceImpl.java
│ │ │ │ HomeSubjectSpuServiceImpl.java
│ │ │ │ MemberPriceServiceImpl.java
│ │ │ │ SeckillPromotionServiceImpl.java
│ │ │ │ SeckillSessionServiceImpl.java
│ │ │ │ SeckillSkuNoticeServiceImpl.java
│ │ │ │ SeckillSkuRelationServiceImpl.java
│ │ │ │ SkuFullReductionServiceImpl.java
│ │ │ │ SkuLadderServiceImpl.java
│ │ │ │ SpuBoundsServiceImpl.java
│ │ │ │
│ │ │ └─resources
│ │ │ │ application.properties
│ │ │ │ application.yml
│ │ │ │
│ │ │ ├─mapper
│ │ │ │ └─coupon
│ │ │ │ CouponDao.xml
│ │ │ │ CouponHistoryDao.xml
│ │ │ │ CouponSpuCategoryRelationDao.xml
│ │ │ │ CouponSpuRelationDao.xml
│ │ │ │ HomeAdvDao.xml
│ │ │ │ HomeSubjectDao.xml
│ │ │ │ HomeSubjectSpuDao.xml
│ │ │ │ MemberPriceDao.xml
│ │ │ │ SeckillPromotionDao.xml
│ │ │ │ SeckillSessionDao.xml
│ │ │ │ SeckillSkuNoticeDao.xml
│ │ │ │ SeckillSkuRelationDao.xml
│ │ │ │ SkuFullReductionDao.xml
│ │ │ │ SkuLadderDao.xml
│ │ │ │ SpuBoundsDao.xml
│ │ │ │
│ │ │ ├─src
│ │ │ │ └─views
│ │ │ │ └─modules
│ │ │ │ └─coupon
│ │ │ │ coupon-add-or-update.vue
│ │ │ │ coupon.vue
│ │ │ │ couponhistory-add-or-update.vue
│ │ │ │ couponhistory.vue
│ │ │ │ couponspucategoryrelation-add-or-update.vue
│ │ │ │ couponspucategoryrelation.vue
│ │ │ │ couponspurelation-add-or-update.vue
│ │ │ │ couponspurelation.vue
│ │ │ │ homeadv-add-or-update.vue
│ │ │ │ homeadv.vue
│ │ │ │ homesubject-add-or-update.vue
│ │ │ │ homesubject.vue
│ │ │ │ homesubjectspu-add-or-update.vue
│ │ │ │ homesubjectspu.vue
│ │ │ │ memberprice-add-or-update.vue
│ │ │ │ memberprice.vue
│ │ │ │ seckillpromotion-add-or-update.vue
│ │ │ │ seckillpromotion.vue
│ │ │ │ seckillsession-add-or-update.vue
│ │ │ │ seckillsession.vue
│ │ │ │ seckillskunotice-add-or-update.vue
│ │ │ │ seckillskunotice.vue
│ │ │ │ seckillskurelation-add-or-update.vue
│ │ │ │ seckillskurelation.vue
│ │ │ │ skufullreduction-add-or-update.vue
│ │ │ │ skufullreduction.vue
│ │ │ │ skuladder-add-or-update.vue
│ │ │ │ skuladder.vue
│ │ │ │ spubounds-add-or-update.vue
│ │ │ │ spubounds.vue
│ │ │ │
│ │ │ ├─static
│ │ │ └─templates
│ │ └─test
│ │ └─java
│ │ └─com
│ │ └─atguigu
│ │ └─gulimall
│ │ └─coupon
│ │ GulimallCouponApplicationTests.java
│ │
│ └─target
│ ├─classes
│ │ │ application.properties
│ │ │ application.yml
│ │ │
│ │ ├─com
│ │ │ └─atguigu
│ │ │ └─gulimall
│ │ │ └─coupon
│ │ │ │ GulimallCouponApplication.class
│ │ │ │
│ │ │ ├─controller
│ │ │ │ CouponController.class
│ │ │ │ CouponHistoryController.class
│ │ │ │ CouponSpuCategoryRelationController.class
│ │ │ │ CouponSpuRelationController.class
│ │ │ │ HomeAdvController.class
│ │ │ │ HomeSubjectController.class
│ │ │ │ HomeSubjectSpuController.class
│ │ │ │ MemberPriceController.class
│ │ │ │ SeckillPromotionController.class
│ │ │ │ SeckillSessionController.class
│ │ │ │ SeckillSkuNoticeController.class
│ │ │ │ SeckillSkuRelationController.class
│ │ │ │ SkuFullReductionController.class
│ │ │ │ SkuLadderController.class
│ │ │ │ SpuBoundsController.class
│ │ │ │
│ │ │ ├─dao
│ │ │ │ CouponDao.class
│ │ │ │ CouponHistoryDao.class
│ │ │ │ CouponSpuCategoryRelationDao.class
│ │ │ │ CouponSpuRelationDao.class
│ │ │ │ HomeAdvDao.class
│ │ │ │ HomeSubjectDao.class
│ │ │ │ HomeSubjectSpuDao.class
│ │ │ │ MemberPriceDao.class
│ │ │ │ SeckillPromotionDao.class
│ │ │ │ SeckillSessionDao.class
│ │ │ │ SeckillSkuNoticeDao.class
│ │ │ │ SeckillSkuRelationDao.class
│ │ │ │ SkuFullReductionDao.class
│ │ │ │ SkuLadderDao.class
│ │ │ │ SpuBoundsDao.class
│ │ │ │
│ │ │ ├─entity
│ │ │ │ CouponEntity.class
│ │ │ │ CouponHistoryEntity.class
│ │ │ │ CouponSpuCategoryRelationEntity.class
│ │ │ │ CouponSpuRelationEntity.class
│ │ │ │ HomeAdvEntity.class
│ │ │ │ HomeSubjectEntity.class
│ │ │ │ HomeSubjectSpuEntity.class
│ │ │ │ MemberPriceEntity.class
│ │ │ │ SeckillPromotionEntity.class
│ │ │ │ SeckillSessionEntity.class
│ │ │ │ SeckillSkuNoticeEntity.class
│ │ │ │ SeckillSkuRelationEntity.class
│ │ │ │ SkuFullReductionEntity.class
│ │ │ │ SkuLadderEntity.class
│ │ │ │ SpuBoundsEntity.class
│ │ │ │
│ │ │ └─service
│ │ │ │ CouponHistoryService.class
│ │ │ │ CouponService.class
│ │ │ │ CouponSpuCategoryRelationService.class
│ │ │ │ CouponSpuRelationService.class
│ │ │ │ HomeAdvService.class
│ │ │ │ HomeSubjectService.class
│ │ │ │ HomeSubjectSpuService.class
│ │ │ │ MemberPriceService.class
│ │ │ │ SeckillPromotionService.class
│ │ │ │ SeckillSessionService.class
│ │ │ │ SeckillSkuNoticeService.class
│ │ │ │ SeckillSkuRelationService.class
│ │ │ │ SkuFullReductionService.class
│ │ │ │ SkuLadderService.class
│ │ │ │ SpuBoundsService.class
│ │ │ │
│ │ │ └─impl
│ │ │ CouponHistoryServiceImpl.class
│ │ │ CouponServiceImpl.class
│ │ │ CouponSpuCategoryRelationServiceImpl.class
│ │ │ CouponSpuRelationServiceImpl.class
│ │ │ HomeAdvServiceImpl.class
│ │ │ HomeSubjectServiceImpl.class
│ │ │ HomeSubjectSpuServiceImpl.class
│ │ │ MemberPriceServiceImpl.class
│ │ │ SeckillPromotionServiceImpl.class
│ │ │ SeckillSessionServiceImpl.class
│ │ │ SeckillSkuNoticeServiceImpl.class
│ │ │ SeckillSkuRelationServiceImpl.class
│ │ │ SkuFullReductionServiceImpl.class
│ │ │ SkuLadderServiceImpl.class
│ │ │ SpuBoundsServiceImpl.class
│ │ │
│ │ ├─mapper
│ │ │ └─coupon
│ │ │ CouponDao.xml
│ │ │ CouponHistoryDao.xml
│ │ │ CouponSpuCategoryRelationDao.xml
│ │ │ CouponSpuRelationDao.xml
│ │ │ HomeAdvDao.xml
│ │ │ HomeSubjectDao.xml
│ │ │ HomeSubjectSpuDao.xml
│ │ │ MemberPriceDao.xml
│ │ │ SeckillPromotionDao.xml
│ │ │ SeckillSessionDao.xml
│ │ │ SeckillSkuNoticeDao.xml
│ │ │ SeckillSkuRelationDao.xml
│ │ │ SkuFullReductionDao.xml
│ │ │ SkuLadderDao.xml
│ │ │ SpuBoundsDao.xml
│ │ │
│ │ └─src
│ │ └─views
│ │ └─modules
│ │ └─coupon
│ │ coupon-add-or-update.vue
│ │ coupon.vue
│ │ couponhistory-add-or-update.vue
│ │ couponhistory.vue
│ │ couponspucategoryrelation-add-or-update.vue
│ │ couponspucategoryrelation.vue
│ │ couponspurelation-add-or-update.vue
│ │ couponspurelation.vue
│ │ homeadv-add-or-update.vue
│ │ homeadv.vue
│ │ homesubject-add-or-update.vue
│ │ homesubject.vue
│ │ homesubjectspu-add-or-update.vue
│ │ homesubjectspu.vue
│ │ memberprice-add-or-update.vue
│ │ memberprice.vue
│ │ seckillpromotion-add-or-update.vue
│ │ seckillpromotion.vue
│ │ seckillsession-add-or-update.vue
│ │ seckillsession.vue
│ │ seckillskunotice-add-or-update.vue
│ │ seckillskunotice.vue
│ │ seckillskurelation-add-or-update.vue
│ │ seckillskurelation.vue
│ │ skufullreduction-add-or-update.vue
│ │ skufullreduction.vue
│ │ skuladder-add-or-update.vue
│ │ skuladder.vue
│ │ spubounds-add-or-update.vue
│ │ spubounds.vue
│ │
│ ├─generated-sources
│ │ └─annotations
│ └─maven-status
│ └─maven-compiler-plugin
│ └─compile
│ └─default-compile
│ createdFiles.lst
│ inputFiles.lst

├─gulimall-member
│ │ .gitignore
│ │ gulimall-member.iml
│ │ HELP.md
│ │ mvnw
│ │ mvnw.cmd
│ │ pom.xml
│ │
│ ├─.mvn
│ │ └─wrapper
│ │ maven-wrapper.jar
│ │ maven-wrapper.properties
│ │
│ ├─src
│ │ ├─main
│ │ │ ├─java
│ │ │ │ └─com
│ │ │ │ └─atguigu
│ │ │ │ └─gulimall
│ │ │ │ └─member
│ │ │ │ │ GulimallMemberApplication.java
│ │ │ │ │
│ │ │ │ ├─controller
│ │ │ │ │ GrowthChangeHistoryController.java
│ │ │ │ │ IntegrationChangeHistoryController.java
│ │ │ │ │ MemberCollectSpuController.java
│ │ │ │ │ MemberCollectSubjectController.java
│ │ │ │ │ MemberController.java
│ │ │ │ │ MemberLevelController.java
│ │ │ │ │ MemberLoginLogController.java
│ │ │ │ │ MemberReceiveAddressController.java
│ │ │ │ │ MemberStatisticsInfoController.java
│ │ │ │ │
│ │ │ │ ├─dao
│ │ │ │ │ GrowthChangeHistoryDao.java
│ │ │ │ │ IntegrationChangeHistoryDao.java
│ │ │ │ │ MemberCollectSpuDao.java
│ │ │ │ │ MemberCollectSubjectDao.java
│ │ │ │ │ MemberDao.java
│ │ │ │ │ MemberLevelDao.java
│ │ │ │ │ MemberLoginLogDao.java
│ │ │ │ │ MemberReceiveAddressDao.java
│ │ │ │ │ MemberStatisticsInfoDao.java
│ │ │ │ │
│ │ │ │ ├─entity
│ │ │ │ │ GrowthChangeHistoryEntity.java
│ │ │ │ │ IntegrationChangeHistoryEntity.java
│ │ │ │ │ MemberCollectSpuEntity.java
│ │ │ │ │ MemberCollectSubjectEntity.java
│ │ │ │ │ MemberEntity.java
│ │ │ │ │ MemberLevelEntity.java
│ │ │ │ │ MemberLoginLogEntity.java
│ │ │ │ │ MemberReceiveAddressEntity.java
│ │ │ │ │ MemberStatisticsInfoEntity.java
│ │ │ │ │
│ │ │ │ └─service
│ │ │ │ │ GrowthChangeHistoryService.java
│ │ │ │ │ IntegrationChangeHistoryService.java
│ │ │ │ │ MemberCollectSpuService.java
│ │ │ │ │ MemberCollectSubjectService.java
│ │ │ │ │ MemberLevelService.java
│ │ │ │ │ MemberLoginLogService.java
│ │ │ │ │ MemberReceiveAddressService.java
│ │ │ │ │ MemberService.java
│ │ │ │ │ MemberStatisticsInfoService.java
│ │ │ │ │
│ │ │ │ └─impl
│ │ │ │ GrowthChangeHistoryServiceImpl.java
│ │ │ │ IntegrationChangeHistoryServiceImpl.java
│ │ │ │ MemberCollectSpuServiceImpl.java
│ │ │ │ MemberCollectSubjectServiceImpl.java
│ │ │ │ MemberLevelServiceImpl.java
│ │ │ │ MemberLoginLogServiceImpl.java
│ │ │ │ MemberReceiveAddressServiceImpl.java
│ │ │ │ MemberServiceImpl.java
│ │ │ │ MemberStatisticsInfoServiceImpl.java
│ │ │ │
│ │ │ └─resources
│ │ │ │ application.properties
│ │ │ │ application.yml
│ │ │ │
│ │ │ ├─mapper
│ │ │ │ └─member
│ │ │ │ GrowthChangeHistoryDao.xml
│ │ │ │ IntegrationChangeHistoryDao.xml
│ │ │ │ MemberCollectSpuDao.xml
│ │ │ │ MemberCollectSubjectDao.xml
│ │ │ │ MemberDao.xml
│ │ │ │ MemberLevelDao.xml
│ │ │ │ MemberLoginLogDao.xml
│ │ │ │ MemberReceiveAddressDao.xml
│ │ │ │ MemberStatisticsInfoDao.xml
│ │ │ │
│ │ │ ├─src
│ │ │ │ └─views
│ │ │ │ └─modules
│ │ │ │ └─member
│ │ │ │ growthchangehistory-add-or-update.vue
│ │ │ │ growthchangehistory.vue
│ │ │ │ integrationchangehistory-add-or-update.vue
│ │ │ │ integrationchangehistory.vue
│ │ │ │ member-add-or-update.vue
│ │ │ │ member.vue
│ │ │ │ membercollectspu-add-or-update.vue
│ │ │ │ membercollectspu.vue
│ │ │ │ membercollectsubject-add-or-update.vue
│ │ │ │ membercollectsubject.vue
│ │ │ │ memberlevel-add-or-update.vue
│ │ │ │ memberlevel.vue
│ │ │ │ memberloginlog-add-or-update.vue
│ │ │ │ memberloginlog.vue
│ │ │ │ memberreceiveaddress-add-or-update.vue
│ │ │ │ memberreceiveaddress.vue
│ │ │ │ memberstatisticsinfo-add-or-update.vue
│ │ │ │ memberstatisticsinfo.vue
│ │ │ │
│ │ │ ├─static
│ │ │ └─templates
│ │ └─test
│ │ └─java
│ │ └─com
│ │ └─atguigu
│ │ └─gulimall
│ │ └─member
│ │ GulimallMemberApplicationTests.java
│ │
│ └─target
│ ├─classes
│ │ │ application.properties
│ │ │ application.yml
│ │ │
│ │ ├─com
│ │ │ └─atguigu
│ │ │ └─gulimall
│ │ │ └─member
│ │ │ │ GulimallMemberApplication.class
│ │ │ │
│ │ │ ├─controller
│ │ │ │ GrowthChangeHistoryController.class
│ │ │ │ IntegrationChangeHistoryController.class
│ │ │ │ MemberCollectSpuController.class
│ │ │ │ MemberCollectSubjectController.class
│ │ │ │ MemberController.class
│ │ │ │ MemberLevelController.class
│ │ │ │ MemberLoginLogController.class
│ │ │ │ MemberReceiveAddressController.class
│ │ │ │ MemberStatisticsInfoController.class
│ │ │ │
│ │ │ ├─dao
│ │ │ │ GrowthChangeHistoryDao.class
│ │ │ │ IntegrationChangeHistoryDao.class
│ │ │ │ MemberCollectSpuDao.class
│ │ │ │ MemberCollectSubjectDao.class
│ │ │ │ MemberDao.class
│ │ │ │ MemberLevelDao.class
│ │ │ │ MemberLoginLogDao.class
│ │ │ │ MemberReceiveAddressDao.class
│ │ │ │ MemberStatisticsInfoDao.class
│ │ │ │
│ │ │ ├─entity
│ │ │ │ GrowthChangeHistoryEntity.class
│ │ │ │ IntegrationChangeHistoryEntity.class
│ │ │ │ MemberCollectSpuEntity.class
│ │ │ │ MemberCollectSubjectEntity.class
│ │ │ │ MemberEntity.class
│ │ │ │ MemberLevelEntity.class
│ │ │ │ MemberLoginLogEntity.class
│ │ │ │ MemberReceiveAddressEntity.class
│ │ │ │ MemberStatisticsInfoEntity.class
│ │ │ │
│ │ │ └─service
│ │ │ │ GrowthChangeHistoryService.class
│ │ │ │ IntegrationChangeHistoryService.class
│ │ │ │ MemberCollectSpuService.class
│ │ │ │ MemberCollectSubjectService.class
│ │ │ │ MemberLevelService.class
│ │ │ │ MemberLoginLogService.class
│ │ │ │ MemberReceiveAddressService.class
│ │ │ │ MemberService.class
│ │ │ │ MemberStatisticsInfoService.class
│ │ │ │
│ │ │ └─impl
│ │ │ GrowthChangeHistoryServiceImpl.class
│ │ │ IntegrationChangeHistoryServiceImpl.class
│ │ │ MemberCollectSpuServiceImpl.class
│ │ │ MemberCollectSubjectServiceImpl.class
│ │ │ MemberLevelServiceImpl.class
│ │ │ MemberLoginLogServiceImpl.class
│ │ │ MemberReceiveAddressServiceImpl.class
│ │ │ MemberServiceImpl.class
│ │ │ MemberStatisticsInfoServiceImpl.class
│ │ │
│ │ ├─mapper
│ │ │ └─member
│ │ │ GrowthChangeHistoryDao.xml
│ │ │ IntegrationChangeHistoryDao.xml
│ │ │ MemberCollectSpuDao.xml
│ │ │ MemberCollectSubjectDao.xml
│ │ │ MemberDao.xml
│ │ │ MemberLevelDao.xml
│ │ │ MemberLoginLogDao.xml
│ │ │ MemberReceiveAddressDao.xml
│ │ │ MemberStatisticsInfoDao.xml
│ │ │
│ │ └─src
│ │ └─views
│ │ └─modules
│ │ └─member
│ │ growthchangehistory-add-or-update.vue
│ │ growthchangehistory.vue
│ │ integrationchangehistory-add-or-update.vue
│ │ integrationchangehistory.vue
│ │ member-add-or-update.vue
│ │ member.vue
│ │ membercollectspu-add-or-update.vue
│ │ membercollectspu.vue
│ │ membercollectsubject-add-or-update.vue
│ │ membercollectsubject.vue
│ │ memberlevel-add-or-update.vue
│ │ memberlevel.vue
│ │ memberloginlog-add-or-update.vue
│ │ memberloginlog.vue
│ │ memberreceiveaddress-add-or-update.vue
│ │ memberreceiveaddress.vue
│ │ memberstatisticsinfo-add-or-update.vue
│ │ memberstatisticsinfo.vue
│ │
│ ├─generated-sources
│ │ └─annotations
│ └─maven-status
│ └─maven-compiler-plugin
│ └─compile
│ └─default-compile
│ createdFiles.lst
│ inputFiles.lst

├─gulimall-order
│ │ .gitignore
│ │ gulimall-order.iml
│ │ HELP.md
│ │ mvnw
│ │ mvnw.cmd
│ │ pom.xml
│ │
│ ├─.mvn
│ │ └─wrapper
│ │ maven-wrapper.jar
│ │ maven-wrapper.properties
│ │
│ ├─src
│ │ ├─main
│ │ │ ├─java
│ │ │ │ └─com
│ │ │ │ └─atguigu
│ │ │ │ └─gulimall
│ │ │ │ └─order
│ │ │ │ │ GulimallOrderApplication.java
│ │ │ │ │
│ │ │ │ ├─controller
│ │ │ │ │ OrderController.java
│ │ │ │ │ OrderItemController.java
│ │ │ │ │ OrderOperateHistoryController.java
│ │ │ │ │ OrderReturnApplyController.java
│ │ │ │ │ OrderReturnReasonController.java
│ │ │ │ │ OrderSettingController.java
│ │ │ │ │ PaymentInfoController.java
│ │ │ │ │ RefundInfoController.java
│ │ │ │ │
│ │ │ │ ├─dao
│ │ │ │ │ OrderDao.java
│ │ │ │ │ OrderItemDao.java
│ │ │ │ │ OrderOperateHistoryDao.java
│ │ │ │ │ OrderReturnApplyDao.java
│ │ │ │ │ OrderReturnReasonDao.java
│ │ │ │ │ OrderSettingDao.java
│ │ │ │ │ PaymentInfoDao.java
│ │ │ │ │ RefundInfoDao.java
│ │ │ │ │
│ │ │ │ ├─entity
│ │ │ │ │ OrderEntity.java
│ │ │ │ │ OrderItemEntity.java
│ │ │ │ │ OrderOperateHistoryEntity.java
│ │ │ │ │ OrderReturnApplyEntity.java
│ │ │ │ │ OrderReturnReasonEntity.java
│ │ │ │ │ OrderSettingEntity.java
│ │ │ │ │ PaymentInfoEntity.java
│ │ │ │ │ RefundInfoEntity.java
│ │ │ │ │
│ │ │ │ └─service
│ │ │ │ │ OrderItemService.java
│ │ │ │ │ OrderOperateHistoryService.java
│ │ │ │ │ OrderReturnApplyService.java
│ │ │ │ │ OrderReturnReasonService.java
│ │ │ │ │ OrderService.java
│ │ │ │ │ OrderSettingService.java
│ │ │ │ │ PaymentInfoService.java
│ │ │ │ │ RefundInfoService.java
│ │ │ │ │
│ │ │ │ └─impl
│ │ │ │ OrderItemServiceImpl.java
│ │ │ │ OrderOperateHistoryServiceImpl.java
│ │ │ │ OrderReturnApplyServiceImpl.java
│ │ │ │ OrderReturnReasonServiceImpl.java
│ │ │ │ OrderServiceImpl.java
│ │ │ │ OrderSettingServiceImpl.java
│ │ │ │ PaymentInfoServiceImpl.java
│ │ │ │ RefundInfoServiceImpl.java
│ │ │ │
│ │ │ └─resources
│ │ │ │ application.properties
│ │ │ │ application.yml
│ │ │ │
│ │ │ ├─mapper
│ │ │ │ └─order
│ │ │ │ OrderDao.xml
│ │ │ │ OrderItemDao.xml
│ │ │ │ OrderOperateHistoryDao.xml
│ │ │ │ OrderReturnApplyDao.xml
│ │ │ │ OrderReturnReasonDao.xml
│ │ │ │ OrderSettingDao.xml
│ │ │ │ PaymentInfoDao.xml
│ │ │ │ RefundInfoDao.xml
│ │ │ │
│ │ │ ├─src
│ │ │ │ └─views
│ │ │ │ └─modules
│ │ │ │ └─order
│ │ │ │ order-add-or-update.vue
│ │ │ │ order.vue
│ │ │ │ orderitem-add-or-update.vue
│ │ │ │ orderitem.vue
│ │ │ │ orderoperatehistory-add-or-update.vue
│ │ │ │ orderoperatehistory.vue
│ │ │ │ orderreturnapply-add-or-update.vue
│ │ │ │ orderreturnapply.vue
│ │ │ │ orderreturnreason-add-or-update.vue
│ │ │ │ orderreturnreason.vue
│ │ │ │ ordersetting-add-or-update.vue
│ │ │ │ ordersetting.vue
│ │ │ │ paymentinfo-add-or-update.vue
│ │ │ │ paymentinfo.vue
│ │ │ │ refundinfo-add-or-update.vue
│ │ │ │ refundinfo.vue
│ │ │ │
│ │ │ ├─static
│ │ │ └─templates
│ │ └─test
│ │ └─java
│ │ └─com
│ │ └─atguigu
│ │ └─gulimall
│ │ └─order
│ │ GulimallOrderApplicationTests.java
│ │
│ └─target
│ ├─classes
│ │ │ application.properties
│ │ │ application.yml
│ │ │
│ │ ├─com
│ │ │ └─atguigu
│ │ │ └─gulimall
│ │ │ ├─gulimallorder
│ │ │ │ GulimallOrderApplication.class
│ │ │ │
│ │ │ └─order
│ │ │ │ GulimallOrderApplication.class
│ │ │ │
│ │ │ ├─controller
│ │ │ │ OrderController.class
│ │ │ │ OrderItemController.class
│ │ │ │ OrderOperateHistoryController.class
│ │ │ │ OrderReturnApplyController.class
│ │ │ │ OrderReturnReasonController.class
│ │ │ │ OrderSettingController.class
│ │ │ │ PaymentInfoController.class
│ │ │ │ RefundInfoController.class
│ │ │ │
│ │ │ ├─dao
│ │ │ │ OrderDao.class
│ │ │ │ OrderItemDao.class
│ │ │ │ OrderOperateHistoryDao.class
│ │ │ │ OrderReturnApplyDao.class
│ │ │ │ OrderReturnReasonDao.class
│ │ │ │ OrderSettingDao.class
│ │ │ │ PaymentInfoDao.class
│ │ │ │ RefundInfoDao.class
│ │ │ │
│ │ │ ├─entity
│ │ │ │ OrderEntity.class
│ │ │ │ OrderItemEntity.class
│ │ │ │ OrderOperateHistoryEntity.class
│ │ │ │ OrderReturnApplyEntity.class
│ │ │ │ OrderReturnReasonEntity.class
│ │ │ │ OrderSettingEntity.class
│ │ │ │ PaymentInfoEntity.class
│ │ │ │ RefundInfoEntity.class
│ │ │ │
│ │ │ └─service
│ │ │ │ OrderItemService.class
│ │ │ │ OrderOperateHistoryService.class
│ │ │ │ OrderReturnApplyService.class
│ │ │ │ OrderReturnReasonService.class
│ │ │ │ OrderService.class
│ │ │ │ OrderSettingService.class
│ │ │ │ PaymentInfoService.class
│ │ │ │ RefundInfoService.class
│ │ │ │
│ │ │ └─impl
│ │ │ OrderItemServiceImpl.class
│ │ │ OrderOperateHistoryServiceImpl.class
│ │ │ OrderReturnApplyServiceImpl.class
│ │ │ OrderReturnReasonServiceImpl.class
│ │ │ OrderServiceImpl.class
│ │ │ OrderSettingServiceImpl.class
│ │ │ PaymentInfoServiceImpl.class
│ │ │ RefundInfoServiceImpl.class
│ │ │
│ │ ├─mapper
│ │ │ └─order
│ │ │ OrderDao.xml
│ │ │ OrderItemDao.xml
│ │ │ OrderOperateHistoryDao.xml
│ │ │ OrderReturnApplyDao.xml
│ │ │ OrderReturnReasonDao.xml
│ │ │ OrderSettingDao.xml
│ │ │ PaymentInfoDao.xml
│ │ │ RefundInfoDao.xml
│ │ │
│ │ └─src
│ │ └─views
│ │ └─modules
│ │ └─order
│ │ order-add-or-update.vue
│ │ order.vue
│ │ orderitem-add-or-update.vue
│ │ orderitem.vue
│ │ orderoperatehistory-add-or-update.vue
│ │ orderoperatehistory.vue
│ │ orderreturnapply-add-or-update.vue
│ │ orderreturnapply.vue
│ │ orderreturnreason-add-or-update.vue
│ │ orderreturnreason.vue
│ │ ordersetting-add-or-update.vue
│ │ ordersetting.vue
│ │ paymentinfo-add-or-update.vue
│ │ paymentinfo.vue
│ │ refundinfo-add-or-update.vue
│ │ refundinfo.vue
│ │
│ ├─generated-sources
│ │ └─annotations
│ └─maven-status
│ └─maven-compiler-plugin
│ └─compile
│ └─default-compile
│ createdFiles.lst
│ inputFiles.lst

├─gulimall-product
│ │ .gitignore
│ │ gulimall-product.iml
│ │ HELP.md
│ │ mvnw
│ │ mvnw.cmd
│ │ pom.xml
│ │
│ ├─.mvn
│ │ └─wrapper
│ │ maven-wrapper.jar
│ │ maven-wrapper.properties
│ │
│ ├─src
│ │ ├─main
│ │ │ ├─java
│ │ │ │ └─com
│ │ │ │ └─atguigu
│ │ │ │ └─gulimall
│ │ │ │ └─product
│ │ │ │ │ GulimallProductApplication.java
│ │ │ │ │
│ │ │ │ ├─controller
│ │ │ │ │ AttrAttrgroupRelationController.java
│ │ │ │ │ AttrController.java
│ │ │ │ │ AttrGroupController.java
│ │ │ │ │ BrandController.java
│ │ │ │ │ CategoryBrandRelationController.java
│ │ │ │ │ CategoryController.java
│ │ │ │ │ CommentReplayController.java
│ │ │ │ │ ProductAttrValueController.java
│ │ │ │ │ SkuImagesController.java
│ │ │ │ │ SkuInfoController.java
│ │ │ │ │ SkuSaleAttrValueController.java
│ │ │ │ │ SpuCommentController.java
│ │ │ │ │ SpuImagesController.java
│ │ │ │ │ SpuInfoController.java
│ │ │ │ │ SpuInfoDescController.java
│ │ │ │ │
│ │ │ │ ├─dao
│ │ │ │ │ AttrAttrgroupRelationDao.java
│ │ │ │ │ AttrDao.java
│ │ │ │ │ AttrGroupDao.java
│ │ │ │ │ BrandDao.java
│ │ │ │ │ CategoryBrandRelationDao.java
│ │ │ │ │ CategoryDao.java
│ │ │ │ │ CommentReplayDao.java
│ │ │ │ │ ProductAttrValueDao.java
│ │ │ │ │ SkuImagesDao.java
│ │ │ │ │ SkuInfoDao.java
│ │ │ │ │ SkuSaleAttrValueDao.java
│ │ │ │ │ SpuCommentDao.java
│ │ │ │ │ SpuImagesDao.java
│ │ │ │ │ SpuInfoDao.java
│ │ │ │ │ SpuInfoDescDao.java
│ │ │ │ │
│ │ │ │ ├─entity
│ │ │ │ │ AttrAttrgroupRelationEntity.java
│ │ │ │ │ AttrEntity.java
│ │ │ │ │ AttrGroupEntity.java
│ │ │ │ │ BrandEntity.java
│ │ │ │ │ CategoryBrandRelationEntity.java
│ │ │ │ │ CategoryEntity.java
│ │ │ │ │ CommentReplayEntity.java
│ │ │ │ │ ProductAttrValueEntity.java
│ │ │ │ │ SkuImagesEntity.java
│ │ │ │ │ SkuInfoEntity.java
│ │ │ │ │ SkuSaleAttrValueEntity.java
│ │ │ │ │ SpuCommentEntity.java
│ │ │ │ │ SpuImagesEntity.java
│ │ │ │ │ SpuInfoDescEntity.java
│ │ │ │ │ SpuInfoEntity.java
│ │ │ │ │
│ │ │ │ └─service
│ │ │ │ │ AttrAttrgroupRelationService.java
│ │ │ │ │ AttrGroupService.java
│ │ │ │ │ AttrService.java
│ │ │ │ │ BrandService.java
│ │ │ │ │ CategoryBrandRelationService.java
│ │ │ │ │ CategoryService.java
│ │ │ │ │ CommentReplayService.java
│ │ │ │ │ ProductAttrValueService.java
│ │ │ │ │ SkuImagesService.java
│ │ │ │ │ SkuInfoService.java
│ │ │ │ │ SkuSaleAttrValueService.java
│ │ │ │ │ SpuCommentService.java
│ │ │ │ │ SpuImagesService.java
│ │ │ │ │ SpuInfoDescService.java
│ │ │ │ │ SpuInfoService.java
│ │ │ │ │
│ │ │ │ └─impl
│ │ │ │ AttrAttrgroupRelationServiceImpl.java
│ │ │ │ AttrGroupServiceImpl.java
│ │ │ │ AttrServiceImpl.java
│ │ │ │ BrandServiceImpl.java
│ │ │ │ CategoryBrandRelationServiceImpl.java
│ │ │ │ CategoryServiceImpl.java
│ │ │ │ CommentReplayServiceImpl.java
│ │ │ │ ProductAttrValueServiceImpl.java
│ │ │ │ SkuImagesServiceImpl.java
│ │ │ │ SkuInfoServiceImpl.java
│ │ │ │ SkuSaleAttrValueServiceImpl.java
│ │ │ │ SpuCommentServiceImpl.java
│ │ │ │ SpuImagesServiceImpl.java
│ │ │ │ SpuInfoDescServiceImpl.java
│ │ │ │ SpuInfoServiceImpl.java
│ │ │ │
│ │ │ └─resources
│ │ │ │ application.properties
│ │ │ │ application.yml
│ │ │ │
│ │ │ ├─mapper
│ │ │ │ └─product
│ │ │ │ AttrAttrgroupRelationDao.xml
│ │ │ │ AttrDao.xml
│ │ │ │ AttrGroupDao.xml
│ │ │ │ BrandDao.xml
│ │ │ │ CategoryBrandRelationDao.xml
│ │ │ │ CategoryDao.xml
│ │ │ │ CommentReplayDao.xml
│ │ │ │ ProductAttrValueDao.xml
│ │ │ │ SkuImagesDao.xml
│ │ │ │ SkuInfoDao.xml
│ │ │ │ SkuSaleAttrValueDao.xml
│ │ │ │ SpuCommentDao.xml
│ │ │ │ SpuImagesDao.xml
│ │ │ │ SpuInfoDao.xml
│ │ │ │ SpuInfoDescDao.xml
│ │ │ │
│ │ │ ├─src
│ │ │ │ └─views
│ │ │ │ └─modules
│ │ │ │ └─product
│ │ │ │ attr-add-or-update.vue
│ │ │ │ attr.vue
│ │ │ │ attrattrgrouprelation-add-or-update.vue
│ │ │ │ attrattrgrouprelation.vue
│ │ │ │ attrgroup-add-or-update.vue
│ │ │ │ attrgroup.vue
│ │ │ │ brand-add-or-update.vue
│ │ │ │ brand.vue
│ │ │ │ category-add-or-update.vue
│ │ │ │ category.vue
│ │ │ │ categorybrandrelation-add-or-update.vue
│ │ │ │ categorybrandrelation.vue
│ │ │ │ commentreplay-add-or-update.vue
│ │ │ │ commentreplay.vue
│ │ │ │ productattrvalue-add-or-update.vue
│ │ │ │ productattrvalue.vue
│ │ │ │ skuimages-add-or-update.vue
│ │ │ │ skuimages.vue
│ │ │ │ skuinfo-add-or-update.vue
│ │ │ │ skuinfo.vue
│ │ │ │ skusaleattrvalue-add-or-update.vue
│ │ │ │ skusaleattrvalue.vue
│ │ │ │ spucomment-add-or-update.vue
│ │ │ │ spucomment.vue
│ │ │ │ spuimages-add-or-update.vue
│ │ │ │ spuimages.vue
│ │ │ │ spuinfo-add-or-update.vue
│ │ │ │ spuinfo.vue
│ │ │ │ spuinfodesc-add-or-update.vue
│ │ │ │ spuinfodesc.vue
│ │ │ │
│ │ │ ├─static
│ │ │ └─templates
│ │ └─test
│ │ └─java
│ │ └─com
│ │ └─atguigu
│ │ └─gulimall
│ │ └─product
│ │ GulimallProductApplicationTests.java
│ │
│ └─target
│ ├─classes
│ │ │ application.properties
│ │ │ application.yml
│ │ │
│ │ ├─com
│ │ │ └─atguigu
│ │ │ └─gulimall
│ │ │ └─product
│ │ │ │ GulimallProductApplication.class
│ │ │ │
│ │ │ ├─controller
│ │ │ │ AttrAttrgroupRelationController.class
│ │ │ │ AttrController.class
│ │ │ │ AttrGroupController.class
│ │ │ │ BrandController.class
│ │ │ │ CategoryBrandRelationController.class
│ │ │ │ CategoryController.class
│ │ │ │ CommentReplayController.class
│ │ │ │ ProductAttrValueController.class
│ │ │ │ SkuImagesController.class
│ │ │ │ SkuInfoController.class
│ │ │ │ SkuSaleAttrValueController.class
│ │ │ │ SpuCommentController.class
│ │ │ │ SpuImagesController.class
│ │ │ │ SpuInfoController.class
│ │ │ │ SpuInfoDescController.class
│ │ │ │
│ │ │ ├─dao
│ │ │ │ AttrAttrgroupRelationDao.class
│ │ │ │ AttrDao.class
│ │ │ │ AttrGroupDao.class
│ │ │ │ BrandDao.class
│ │ │ │ CategoryBrandRelationDao.class
│ │ │ │ CategoryDao.class
│ │ │ │ CommentReplayDao.class
│ │ │ │ ProductAttrValueDao.class
│ │ │ │ SkuImagesDao.class
│ │ │ │ SkuInfoDao.class
│ │ │ │ SkuSaleAttrValueDao.class
│ │ │ │ SpuCommentDao.class
│ │ │ │ SpuImagesDao.class
│ │ │ │ SpuInfoDao.class
│ │ │ │ SpuInfoDescDao.class
│ │ │ │
│ │ │ ├─entity
│ │ │ │ AttrAttrgroupRelationEntity.class
│ │ │ │ AttrEntity.class
│ │ │ │ AttrGroupEntity.class
│ │ │ │ BrandEntity.class
│ │ │ │ CategoryBrandRelationEntity.class
│ │ │ │ CategoryEntity.class
│ │ │ │ CommentReplayEntity.class
│ │ │ │ ProductAttrValueEntity.class
│ │ │ │ SkuImagesEntity.class
│ │ │ │ SkuInfoEntity.class
│ │ │ │ SkuSaleAttrValueEntity.class
│ │ │ │ SpuCommentEntity.class
│ │ │ │ SpuImagesEntity.class
│ │ │ │ SpuInfoDescEntity.class
│ │ │ │ SpuInfoEntity.class
│ │ │ │
│ │ │ └─service
│ │ │ │ AttrAttrgroupRelationService.class
│ │ │ │ AttrGroupService.class
│ │ │ │ AttrService.class
│ │ │ │ BrandService.class
│ │ │ │ CategoryBrandRelationService.class
│ │ │ │ CategoryService.class
│ │ │ │ CommentReplayService.class
│ │ │ │ ProductAttrValueService.class
│ │ │ │ SkuImagesService.class
│ │ │ │ SkuInfoService.class
│ │ │ │ SkuSaleAttrValueService.class
│ │ │ │ SpuCommentService.class
│ │ │ │ SpuImagesService.class
│ │ │ │ SpuInfoDescService.class
│ │ │ │ SpuInfoService.class
│ │ │ │
│ │ │ └─impl
│ │ │ AttrAttrgroupRelationServiceImpl.class
│ │ │ AttrGroupServiceImpl.class
│ │ │ AttrServiceImpl.class
│ │ │ BrandServiceImpl.class
│ │ │ CategoryBrandRelationServiceImpl.class
│ │ │ CategoryServiceImpl.class
│ │ │ CommentReplayServiceImpl.class
│ │ │ ProductAttrValueServiceImpl.class
│ │ │ SkuImagesServiceImpl.class
│ │ │ SkuInfoServiceImpl.class
│ │ │ SkuSaleAttrValueServiceImpl.class
│ │ │ SpuCommentServiceImpl.class
│ │ │ SpuImagesServiceImpl.class
│ │ │ SpuInfoDescServiceImpl.class
│ │ │ SpuInfoServiceImpl.class
│ │ │
│ │ ├─mapper
│ │ │ └─product
│ │ │ AttrAttrgroupRelationDao.xml
│ │ │ AttrDao.xml
│ │ │ AttrGroupDao.xml
│ │ │ BrandDao.xml
│ │ │ CategoryBrandRelationDao.xml
│ │ │ CategoryDao.xml
│ │ │ CommentReplayDao.xml
│ │ │ ProductAttrValueDao.xml
│ │ │ SkuImagesDao.xml
│ │ │ SkuInfoDao.xml
│ │ │ SkuSaleAttrValueDao.xml
│ │ │ SpuCommentDao.xml
│ │ │ SpuImagesDao.xml
│ │ │ SpuInfoDao.xml
│ │ │ SpuInfoDescDao.xml
│ │ │
│ │ └─src
│ │ └─views
│ │ └─modules
│ │ └─product
│ │ attr-add-or-update.vue
│ │ attr.vue
│ │ attrattrgrouprelation-add-or-update.vue
│ │ attrattrgrouprelation.vue
│ │ attrgroup-add-or-update.vue
│ │ attrgroup.vue
│ │ brand-add-or-update.vue
│ │ brand.vue
│ │ category-add-or-update.vue
│ │ category.vue
│ │ categorybrandrelation-add-or-update.vue
│ │ categorybrandrelation.vue
│ │ commentreplay-add-or-update.vue
│ │ commentreplay.vue
│ │ productattrvalue-add-or-update.vue
│ │ productattrvalue.vue
│ │ skuimages-add-or-update.vue
│ │ skuimages.vue
│ │ skuinfo-add-or-update.vue
│ │ skuinfo.vue
│ │ skusaleattrvalue-add-or-update.vue
│ │ skusaleattrvalue.vue
│ │ spucomment-add-or-update.vue
│ │ spucomment.vue
│ │ spuimages-add-or-update.vue
│ │ spuimages.vue
│ │ spuinfo-add-or-update.vue
│ │ spuinfo.vue
│ │ spuinfodesc-add-or-update.vue
│ │ spuinfodesc.vue
│ │
│ ├─generated-sources
│ │ └─annotations
│ ├─generated-test-sources
│ │ └─test-annotations
│ ├─maven-status
│ │ └─maven-compiler-plugin
│ │ └─compile
│ │ └─default-compile
│ │ createdFiles.lst
│ │ inputFiles.lst
│ │
│ └─test-classes
│ └─com
│ └─atguigu
│ └─gulimall
│ └─product
│ GulimallProductApplicationTests.class

├─gulimall-ware
│ │ .gitignore
│ │ gulimall-ware.iml
│ │ HELP.md
│ │ mvnw
│ │ mvnw.cmd
│ │ pom.xml
│ │
│ ├─.mvn
│ │ └─wrapper
│ │ maven-wrapper.jar
│ │ maven-wrapper.properties
│ │
│ ├─src
│ │ ├─main
│ │ │ ├─java
│ │ │ │ └─com
│ │ │ │ └─atguigu
│ │ │ │ └─gulimall
│ │ │ │ └─ware
│ │ │ │ │ GulimallWareApplication.java
│ │ │ │ │
│ │ │ │ ├─controller
│ │ │ │ │ WareInfoController.java
│ │ │ │ │ WareOrderTaskController.java
│ │ │ │ │ WareOrderTaskDetailController.java
│ │ │ │ │ WareSkuController.java
│ │ │ │ │
│ │ │ │ ├─dao
│ │ │ │ │ WareInfoDao.java
│ │ │ │ │ WareOrderTaskDao.java
│ │ │ │ │ WareOrderTaskDetailDao.java
│ │ │ │ │ WareSkuDao.java
│ │ │ │ │
│ │ │ │ ├─entity
│ │ │ │ │ WareInfoEntity.java
│ │ │ │ │ WareOrderTaskDetailEntity.java
│ │ │ │ │ WareOrderTaskEntity.java
│ │ │ │ │ WareSkuEntity.java
│ │ │ │ │
│ │ │ │ └─service
│ │ │ │ │ WareInfoService.java
│ │ │ │ │ WareOrderTaskDetailService.java
│ │ │ │ │ WareOrderTaskService.java
│ │ │ │ │ WareSkuService.java
│ │ │ │ │
│ │ │ │ └─impl
│ │ │ │ WareInfoServiceImpl.java
│ │ │ │ WareOrderTaskDetailServiceImpl.java
│ │ │ │ WareOrderTaskServiceImpl.java
│ │ │ │ WareSkuServiceImpl.java
│ │ │ │
│ │ │ └─resources
│ │ │ │ application.yml
│ │ │ │
│ │ │ ├─mapper
│ │ │ │ └─ware
│ │ │ │ WareInfoDao.xml
│ │ │ │ WareOrderTaskDao.xml
│ │ │ │ WareOrderTaskDetailDao.xml
│ │ │ │ WareSkuDao.xml
│ │ │ │
│ │ │ └─src
│ │ │ └─views
│ │ │ └─modules
│ │ │ └─ware
│ │ │ wareinfo-add-or-update.vue
│ │ │ wareinfo.vue
│ │ │ wareordertask-add-or-update.vue
│ │ │ wareordertask.vue
│ │ │ wareordertaskdetail-add-or-update.vue
│ │ │ wareordertaskdetail.vue
│ │ │ waresku-add-or-update.vue
│ │ │ waresku.vue
│ │ │
│ │ └─test
│ │ └─java
│ │ └─com
│ │ └─atguigu
│ │ └─gulimall
│ │ └─gulimallware
│ │ GulimallWareApplicationTests.java
│ │
│ └─target
│ ├─classes
│ │ │ application.yml
│ │ │
│ │ ├─com
│ │ │ └─atguigu
│ │ │ └─gulimall
│ │ │ ├─gulimallware
│ │ │ │ GulimallWareApplication.class
│ │ │ │
│ │ │ └─ware
│ │ │ │ GulimallWareApplication.class
│ │ │ │
│ │ │ ├─controller
│ │ │ │ WareInfoController.class
│ │ │ │ WareOrderTaskController.class
│ │ │ │ WareOrderTaskDetailController.class
│ │ │ │ WareSkuController.class
│ │ │ │
│ │ │ ├─dao
│ │ │ │ WareInfoDao.class
│ │ │ │ WareOrderTaskDao.class
│ │ │ │ WareOrderTaskDetailDao.class
│ │ │ │ WareSkuDao.class
│ │ │ │
│ │ │ ├─entity
│ │ │ │ WareInfoEntity.class
│ │ │ │ WareOrderTaskDetailEntity.class
│ │ │ │ WareOrderTaskEntity.class
│ │ │ │ WareSkuEntity.class
│ │ │ │
│ │ │ └─service
│ │ │ │ WareInfoService.class
│ │ │ │ WareOrderTaskDetailService.class
│ │ │ │ WareOrderTaskService.class
│ │ │ │ WareSkuService.class
│ │ │ │
│ │ │ └─impl
│ │ │ WareInfoServiceImpl.class
│ │ │ WareOrderTaskDetailServiceImpl.class
│ │ │ WareOrderTaskServiceImpl.class
│ │ │ WareSkuServiceImpl.class
│ │ │
│ │ ├─mapper
│ │ │ └─ware
│ │ │ WareInfoDao.xml
│ │ │ WareOrderTaskDao.xml
│ │ │ WareOrderTaskDetailDao.xml
│ │ │ WareSkuDao.xml
│ │ │
│ │ └─src
│ │ └─views
│ │ └─modules
│ │ └─ware
│ │ wareinfo-add-or-update.vue
│ │ wareinfo.vue
│ │ wareordertask-add-or-update.vue
│ │ wareordertask.vue
│ │ wareordertaskdetail-add-or-update.vue
│ │ wareordertaskdetail.vue
│ │ waresku-add-or-update.vue
│ │ waresku.vue
│ │
│ ├─generated-sources
│ │ └─annotations
│ ├─generated-test-sources
│ │ └─test-annotations
│ ├─maven-status
│ │ └─maven-compiler-plugin
│ │ └─compile
│ │ └─default-compile
│ │ createdFiles.lst
│ │ inputFiles.lst
│ │
│ └─test-classes
│ └─com
│ └─atguigu
│ └─gulimall
│ └─gulimallware
│ GulimallWareApplicationTests.class

├─renren-fast
│ │ .gitignore
│ │ docker-compose.yml
│ │ Dockerfile
│ │ LICENSE
│ │ pom.xml
│ │ README.md
│ │ renren-fast.iml
│ │
│ ├─db
│ │ mysql.sql
│ │ oracle.sql
│ │ postgresql.sql
│ │ sqlserver.sql
│ │
│ ├─src
│ │ ├─main
│ │ │ ├─java
│ │ │ │ └─io
│ │ │ │ └─renren
│ │ │ │ │ RenrenApplication.java
│ │ │ │ │
│ │ │ │ ├─common
│ │ │ │ │ ├─annotation
│ │ │ │ │ │ SysLog.java
│ │ │ │ │ │
│ │ │ │ │ ├─aspect
│ │ │ │ │ │ RedisAspect.java
│ │ │ │ │ │ SysLogAspect.java
│ │ │ │ │ │
│ │ │ │ │ ├─exception
│ │ │ │ │ │ RRException.java
│ │ │ │ │ │ RRExceptionHandler.java
│ │ │ │ │ │
│ │ │ │ │ ├─utils
│ │ │ │ │ │ ConfigConstant.java
│ │ │ │ │ │ Constant.java
│ │ │ │ │ │ DateUtils.java
│ │ │ │ │ │ HttpContextUtils.java
│ │ │ │ │ │ IPUtils.java
│ │ │ │ │ │ MapUtils.java
│ │ │ │ │ │ PageUtils.java
│ │ │ │ │ │ Query.java
│ │ │ │ │ │ R.java
│ │ │ │ │ │ RedisKeys.java
│ │ │ │ │ │ RedisUtils.java
│ │ │ │ │ │ ShiroUtils.java
│ │ │ │ │ │ SpringContextUtils.java
│ │ │ │ │ │
│ │ │ │ │ ├─validator
│ │ │ │ │ │ │ Assert.java
│ │ │ │ │ │ │ ValidatorUtils.java
│ │ │ │ │ │ │
│ │ │ │ │ │ └─group
│ │ │ │ │ │ AddGroup.java
│ │ │ │ │ │ AliyunGroup.java
│ │ │ │ │ │ Group.java
│ │ │ │ │ │ QcloudGroup.java
│ │ │ │ │ │ QiniuGroup.java
│ │ │ │ │ │ UpdateGroup.java
│ │ │ │ │ │
│ │ │ │ │ └─xss
│ │ │ │ │ HTMLFilter.java
│ │ │ │ │ SQLFilter.java
│ │ │ │ │ XssFilter.java
│ │ │ │ │ XssHttpServletRequestWrapper.java
│ │ │ │ │
│ │ │ │ ├─config
│ │ │ │ │ CorsConfig.java
│ │ │ │ │ FilterConfig.java
│ │ │ │ │ KaptchaConfig.java
│ │ │ │ │ MybatisPlusConfig.java
│ │ │ │ │ RedisConfig.java
│ │ │ │ │ ShiroConfig.java
│ │ │ │ │ SwaggerConfig.java
│ │ │ │ │
│ │ │ │ ├─datasource
│ │ │ │ │ ├─annotation
│ │ │ │ │ │ DataSource.java
│ │ │ │ │ │
│ │ │ │ │ ├─aspect
│ │ │ │ │ │ DataSourceAspect.java
│ │ │ │ │ │
│ │ │ │ │ ├─config
│ │ │ │ │ │ DynamicContextHolder.java
│ │ │ │ │ │ DynamicDataSource.java
│ │ │ │ │ │ DynamicDataSourceConfig.java
│ │ │ │ │ │ DynamicDataSourceFactory.java
│ │ │ │ │ │
│ │ │ │ │ └─properties
│ │ │ │ │ DataSourceProperties.java
│ │ │ │ │ DynamicDataSourceProperties.java
│ │ │ │ │
│ │ │ │ └─modules
│ │ │ │ ├─app
│ │ │ │ │ ├─annotation
│ │ │ │ │ │ Login.java
│ │ │ │ │ │ LoginUser.java
│ │ │ │ │ │
│ │ │ │ │ ├─config
│ │ │ │ │ │ WebMvcConfig.java
│ │ │ │ │ │
│ │ │ │ │ ├─controller
│ │ │ │ │ │ AppLoginController.java
│ │ │ │ │ │ AppRegisterController.java
│ │ │ │ │ │ AppTestController.java
│ │ │ │ │ │
│ │ │ │ │ ├─dao
│ │ │ │ │ │ UserDao.java
│ │ │ │ │ │
│ │ │ │ │ ├─entity
│ │ │ │ │ │ UserEntity.java
│ │ │ │ │ │
│ │ │ │ │ ├─form
│ │ │ │ │ │ LoginForm.java
│ │ │ │ │ │ RegisterForm.java
│ │ │ │ │ │
│ │ │ │ │ ├─interceptor
│ │ │ │ │ │ AuthorizationInterceptor.java
│ │ │ │ │ │
│ │ │ │ │ ├─resolver
│ │ │ │ │ │ LoginUserHandlerMethodArgumentResolver.java
│ │ │ │ │ │
│ │ │ │ │ ├─service
│ │ │ │ │ │ │ UserService.java
│ │ │ │ │ │ │
│ │ │ │ │ │ └─impl
│ │ │ │ │ │ UserServiceImpl.java
│ │ │ │ │ │
│ │ │ │ │ └─utils
│ │ │ │ │ JwtUtils.java
│ │ │ │ │
│ │ │ │ ├─job
│ │ │ │ │ ├─config
│ │ │ │ │ │ ScheduleConfig.java
│ │ │ │ │ │
│ │ │ │ │ ├─controller
│ │ │ │ │ │ ScheduleJobController.java
│ │ │ │ │ │ ScheduleJobLogController.java
│ │ │ │ │ │
│ │ │ │ │ ├─dao
│ │ │ │ │ │ ScheduleJobDao.java
│ │ │ │ │ │ ScheduleJobLogDao.java
│ │ │ │ │ │
│ │ │ │ │ ├─entity
│ │ │ │ │ │ ScheduleJobEntity.java
│ │ │ │ │ │ ScheduleJobLogEntity.java
│ │ │ │ │ │
│ │ │ │ │ ├─service
│ │ │ │ │ │ │ ScheduleJobLogService.java
│ │ │ │ │ │ │ ScheduleJobService.java
│ │ │ │ │ │ │
│ │ │ │ │ │ └─impl
│ │ │ │ │ │ ScheduleJobLogServiceImpl.java
│ │ │ │ │ │ ScheduleJobServiceImpl.java
│ │ │ │ │ │
│ │ │ │ │ ├─task
│ │ │ │ │ │ ITask.java
│ │ │ │ │ │ TestTask.java
│ │ │ │ │ │
│ │ │ │ │ └─utils
│ │ │ │ │ ScheduleJob.java
│ │ │ │ │ ScheduleUtils.java
│ │ │ │ │
│ │ │ │ ├─oss
│ │ │ │ │ ├─cloud
│ │ │ │ │ │ AliyunCloudStorageService.java
│ │ │ │ │ │ CloudStorageConfig.java
│ │ │ │ │ │ CloudStorageService.java
│ │ │ │ │ │ OSSFactory.java
│ │ │ │ │ │ QcloudCloudStorageService.java
│ │ │ │ │ │ QiniuCloudStorageService.java
│ │ │ │ │ │
│ │ │ │ │ ├─controller
│ │ │ │ │ │ SysOssController.java
│ │ │ │ │ │
│ │ │ │ │ ├─dao
│ │ │ │ │ │ SysOssDao.java
│ │ │ │ │ │
│ │ │ │ │ ├─entity
│ │ │ │ │ │ SysOssEntity.java
│ │ │ │ │ │
│ │ │ │ │ └─service
│ │ │ │ │ │ SysOssService.java
│ │ │ │ │ │
│ │ │ │ │ └─impl
│ │ │ │ │ SysOssServiceImpl.java
│ │ │ │ │
│ │ │ │ └─sys
│ │ │ │ ├─controller
│ │ │ │ │ AbstractController.java
│ │ │ │ │ SysConfigController.java
│ │ │ │ │ SysLogController.java
│ │ │ │ │ SysLoginController.java
│ │ │ │ │ SysMenuController.java
│ │ │ │ │ SysRoleController.java
│ │ │ │ │ SysUserController.java
│ │ │ │ │
│ │ │ │ ├─dao
│ │ │ │ │ SysCaptchaDao.java
│ │ │ │ │ SysConfigDao.java
│ │ │ │ │ SysLogDao.java
│ │ │ │ │ SysMenuDao.java
│ │ │ │ │ SysRoleDao.java
│ │ │ │ │ SysRoleMenuDao.java
│ │ │ │ │ SysUserDao.java
│ │ │ │ │ SysUserRoleDao.java
│ │ │ │ │ SysUserTokenDao.java
│ │ │ │ │
│ │ │ │ ├─entity
│ │ │ │ │ SysCaptchaEntity.java
│ │ │ │ │ SysConfigEntity.java
│ │ │ │ │ SysLogEntity.java
│ │ │ │ │ SysMenuEntity.java
│ │ │ │ │ SysRoleEntity.java
│ │ │ │ │ SysRoleMenuEntity.java
│ │ │ │ │ SysUserEntity.java
│ │ │ │ │ SysUserRoleEntity.java
│ │ │ │ │ SysUserTokenEntity.java
│ │ │ │ │
│ │ │ │ ├─form
│ │ │ │ │ PasswordForm.java
│ │ │ │ │ SysLoginForm.java
│ │ │ │ │
│ │ │ │ ├─oauth2
│ │ │ │ │ OAuth2Filter.java
│ │ │ │ │ OAuth2Realm.java
│ │ │ │ │ OAuth2Token.java
│ │ │ │ │ TokenGenerator.java
│ │ │ │ │
│ │ │ │ ├─redis
│ │ │ │ │ SysConfigRedis.java
│ │ │ │ │
│ │ │ │ └─service
│ │ │ │ │ ShiroService.java
│ │ │ │ │ SysCaptchaService.java
│ │ │ │ │ SysConfigService.java
│ │ │ │ │ SysLogService.java
│ │ │ │ │ SysMenuService.java
│ │ │ │ │ SysRoleMenuService.java
│ │ │ │ │ SysRoleService.java
│ │ │ │ │ SysUserRoleService.java
│ │ │ │ │ SysUserService.java
│ │ │ │ │ SysUserTokenService.java
│ │ │ │ │
│ │ │ │ └─impl
│ │ │ │ ShiroServiceImpl.java
│ │ │ │ SysCaptchaServiceImpl.java
│ │ │ │ SysConfigServiceImpl.java
│ │ │ │ SysLogServiceImpl.java
│ │ │ │ SysMenuServiceImpl.java
│ │ │ │ SysRoleMenuServiceImpl.java
│ │ │ │ SysRoleServiceImpl.java
│ │ │ │ SysUserRoleServiceImpl.java
│ │ │ │ SysUserServiceImpl.java
│ │ │ │ SysUserTokenServiceImpl.java
│ │ │ │
│ │ │ └─resources
│ │ │ │ application-dev.yml
│ │ │ │ application-prod.yml
│ │ │ │ application-test.yml
│ │ │ │ application.yml
│ │ │ │ banner.txt
│ │ │ │ logback-spring.xml
│ │ │ │
│ │ │ ├─mapper
│ │ │ │ ├─app
│ │ │ │ │ UserDao.xml
│ │ │ │ │
│ │ │ │ ├─job
│ │ │ │ │ ScheduleJobDao.xml
│ │ │ │ │ ScheduleJobLogDao.xml
│ │ │ │ │
│ │ │ │ ├─oss
│ │ │ │ │ SysOssDao.xml
│ │ │ │ │
│ │ │ │ └─sys
│ │ │ │ SysConfigDao.xml
│ │ │ │ SysLogDao.xml
│ │ │ │ SysMenuDao.xml
│ │ │ │ SysRoleDao.xml
│ │ │ │ SysRoleMenuDao.xml
│ │ │ │ SysUserDao.xml
│ │ │ │ SysUserRoleDao.xml
│ │ │ │ SysUserTokenDao.xml
│ │ │ │
│ │ │ └─static
│ │ │ │ favicon.ico
│ │ │ │
│ │ │ └─swagger
│ │ │ │ favicon-16x16.png
│ │ │ │ favicon-32x32.png
│ │ │ │ index.html
│ │ │ │ index.yaml
│ │ │ │ o2c.html
│ │ │ │ oauth2-redirect.html
│ │ │ │ swagger-ui-bundle.js
│ │ │ │ swagger-ui-bundle.js.map
│ │ │ │ swagger-ui-standalone-preset.js
│ │ │ │ swagger-ui-standalone-preset.js.map
│ │ │ │ swagger-ui.css
│ │ │ │ swagger-ui.css.map
│ │ │ │ swagger-ui.js
│ │ │ │ swagger-ui.js.map
│ │ │ │ swagger-ui.min.js
│ │ │ │
│ │ │ ├─css
│ │ │ │ print.css
│ │ │ │ reset.css
│ │ │ │ screen.css
│ │ │ │ style.css
│ │ │ │ typography.css
│ │ │ │
│ │ │ ├─fonts
│ │ │ │ DroidSans-Bold.ttf
│ │ │ │ DroidSans.ttf
│ │ │ │
│ │ │ ├─images
│ │ │ │ collapse.gif
│ │ │ │ expand.gif
│ │ │ │ explorer_icons.png
│ │ │ │ favicon-16x16.png
│ │ │ │ favicon-32x32.png
│ │ │ │ favicon.ico
│ │ │ │ logo_small.png
│ │ │ │ pet_store_api.png
│ │ │ │ throbber.gif
│ │ │ │ wordnik_api.png
│ │ │ │
│ │ │ ├─lang
│ │ │ │ en.js
│ │ │ │ translator.js
│ │ │ │ zh-cn.js
│ │ │ │
│ │ │ └─lib
│ │ │ backbone-min.js
│ │ │ es5-shim.js
│ │ │ handlebars-4.0.5.js
│ │ │ highlight.9.1.0.pack.js
│ │ │ highlight.9.1.0.pack_extended.js
│ │ │ jquery-1.8.0.min.js
│ │ │ jquery.ba-bbq.min.js
│ │ │ jquery.slideto.min.js
│ │ │ jquery.wiggle.min.js
│ │ │ js-yaml.min.js
│ │ │ jsoneditor.min.js
│ │ │ lodash.min.js
│ │ │ marked.js
│ │ │ object-assign-pollyfill.js
│ │ │ sanitize-html.min.js
│ │ │ swagger-oauth.js
│ │ │
│ │ └─test
│ │ └─java
│ │ └─io
│ │ └─renren
│ │ │ DynamicDataSourceTest.java
│ │ │ JwtTest.java
│ │ │ RedisTest.java
│ │ │
│ │ └─service
│ │ DynamicDataSourceTestService.java
│ │
│ └─target
│ ├─classes
│ │ │ application-dev.yml
│ │ │ application-prod.yml
│ │ │ application-test.yml
│ │ │ application.yml
│ │ │ banner.txt
│ │ │ logback-spring.xml
│ │ │
│ │ ├─io
│ │ │ └─renren
│ │ │ │ RenrenApplication.class
│ │ │ │
│ │ │ ├─common
│ │ │ │ ├─annotation
│ │ │ │ │ SysLog.class
│ │ │ │ │
│ │ │ │ ├─aspect
│ │ │ │ │ RedisAspect.class
│ │ │ │ │ SysLogAspect.class
│ │ │ │ │
│ │ │ │ ├─exception
│ │ │ │ │ RRException.class
│ │ │ │ │ RRExceptionHandler.class
│ │ │ │ │
│ │ │ │ ├─utils
│ │ │ │ │ ConfigConstant.class
│ │ │ │ │ Constant$CloudService.class
│ │ │ │ │ Constant$MenuType.class
│ │ │ │ │ Constant$ScheduleStatus.class
│ │ │ │ │ Constant.class
│ │ │ │ │ DateUtils.class
│ │ │ │ │ HttpContextUtils.class
│ │ │ │ │ IPUtils.class
│ │ │ │ │ MapUtils.class
│ │ │ │ │ PageUtils.class
│ │ │ │ │ Query.class
│ │ │ │ │ R.class
│ │ │ │ │ RedisKeys.class
│ │ │ │ │ RedisUtils.class
│ │ │ │ │ ShiroUtils.class
│ │ │ │ │ SpringContextUtils.class
│ │ │ │ │
│ │ │ │ ├─validator
│ │ │ │ │ │ Assert.class
│ │ │ │ │ │ ValidatorUtils.class
│ │ │ │ │ │
│ │ │ │ │ └─group
│ │ │ │ │ AddGroup.class
│ │ │ │ │ AliyunGroup.class
│ │ │ │ │ Group.class
│ │ │ │ │ QcloudGroup.class
│ │ │ │ │ QiniuGroup.class
│ │ │ │ │ UpdateGroup.class
│ │ │ │ │
│ │ │ │ └─xss
│ │ │ │ HTMLFilter.class
│ │ │ │ SQLFilter.class
│ │ │ │ XssFilter.class
│ │ │ │ XssHttpServletRequestWrapper$1.class
│ │ │ │ XssHttpServletRequestWrapper.class
│ │ │ │
│ │ │ ├─config
│ │ │ │ CorsConfig.class
│ │ │ │ FilterConfig.class
│ │ │ │ KaptchaConfig.class
│ │ │ │ MybatisPlusConfig.class
│ │ │ │ RedisConfig.class
│ │ │ │ ShiroConfig.class
│ │ │ │ SwaggerConfig.class
│ │ │ │
│ │ │ ├─datasource
│ │ │ │ ├─annotation
│ │ │ │ │ DataSource.class
│ │ │ │ │
│ │ │ │ ├─aspect
│ │ │ │ │ DataSourceAspect.class
│ │ │ │ │
│ │ │ │ ├─config
│ │ │ │ │ DynamicContextHolder$1.class
│ │ │ │ │ DynamicContextHolder.class
│ │ │ │ │ DynamicDataSource.class
│ │ │ │ │ DynamicDataSourceConfig.class
│ │ │ │ │ DynamicDataSourceFactory.class
│ │ │ │ │
│ │ │ │ └─properties
│ │ │ │ DataSourceProperties.class
│ │ │ │ DynamicDataSourceProperties.class
│ │ │ │
│ │ │ └─modules
│ │ │ ├─app
│ │ │ │ ├─annotation
│ │ │ │ │ Login.class
│ │ │ │ │ LoginUser.class
│ │ │ │ │
│ │ │ │ ├─config
│ │ │ │ │ WebMvcConfig.class
│ │ │ │ │
│ │ │ │ ├─controller
│ │ │ │ │ AppLoginController.class
│ │ │ │ │ AppRegisterController.class
│ │ │ │ │ AppTestController.class
│ │ │ │ │
│ │ │ │ ├─dao
│ │ │ │ │ UserDao.class
│ │ │ │ │
│ │ │ │ ├─entity
│ │ │ │ │ UserEntity.class
│ │ │ │ │
│ │ │ │ ├─form
│ │ │ │ │ LoginForm.class
│ │ │ │ │ RegisterForm.class
│ │ │ │ │
│ │ │ │ ├─interceptor
│ │ │ │ │ AuthorizationInterceptor.class
│ │ │ │ │
│ │ │ │ ├─resolver
│ │ │ │ │ LoginUserHandlerMethodArgumentResolver.class
│ │ │ │ │
│ │ │ │ ├─service
│ │ │ │ │ │ UserService.class
│ │ │ │ │ │
│ │ │ │ │ └─impl
│ │ │ │ │ UserServiceImpl.class
│ │ │ │ │
│ │ │ │ └─utils
│ │ │ │ JwtUtils.class
│ │ │ │
│ │ │ ├─job
│ │ │ │ ├─config
│ │ │ │ │ ScheduleConfig.class
│ │ │ │ │
│ │ │ │ ├─controller
│ │ │ │ │ ScheduleJobController.class
│ │ │ │ │ ScheduleJobLogController.class
│ │ │ │ │
│ │ │ │ ├─dao
│ │ │ │ │ ScheduleJobDao.class
│ │ │ │ │ ScheduleJobLogDao.class
│ │ │ │ │
│ │ │ │ ├─entity
│ │ │ │ │ ScheduleJobEntity.class
│ │ │ │ │ ScheduleJobLogEntity.class
│ │ │ │ │
│ │ │ │ ├─service
│ │ │ │ │ │ ScheduleJobLogService.class
│ │ │ │ │ │ ScheduleJobService.class
│ │ │ │ │ │
│ │ │ │ │ └─impl
│ │ │ │ │ ScheduleJobLogServiceImpl.class
│ │ │ │ │ ScheduleJobServiceImpl.class
│ │ │ │ │
│ │ │ │ ├─task
│ │ │ │ │ ITask.class
│ │ │ │ │ TestTask.class
│ │ │ │ │
│ │ │ │ └─utils
│ │ │ │ ScheduleJob.class
│ │ │ │ ScheduleUtils.class
│ │ │ │
│ │ │ ├─oss
│ │ │ │ ├─cloud
│ │ │ │ │ AliyunCloudStorageService.class
│ │ │ │ │ CloudStorageConfig.class
│ │ │ │ │ CloudStorageService.class
│ │ │ │ │ OSSFactory.class
│ │ │ │ │ QcloudCloudStorageService.class
│ │ │ │ │ QiniuCloudStorageService.class
│ │ │ │ │
│ │ │ │ ├─controller
│ │ │ │ │ SysOssController.class
│ │ │ │ │
│ │ │ │ ├─dao
│ │ │ │ │ SysOssDao.class
│ │ │ │ │
│ │ │ │ ├─entity
│ │ │ │ │ SysOssEntity.class
│ │ │ │ │
│ │ │ │ └─service
│ │ │ │ │ SysOssService.class
│ │ │ │ │
│ │ │ │ └─impl
│ │ │ │ SysOssServiceImpl.class
│ │ │ │
│ │ │ └─sys
│ │ │ ├─controller
│ │ │ │ AbstractController.class
│ │ │ │ SysConfigController.class
│ │ │ │ SysLogController.class
│ │ │ │ SysLoginController.class
│ │ │ │ SysMenuController.class
│ │ │ │ SysRoleController.class
│ │ │ │ SysUserController.class
│ │ │ │
│ │ │ ├─dao
│ │ │ │ SysCaptchaDao.class
│ │ │ │ SysConfigDao.class
│ │ │ │ SysLogDao.class
│ │ │ │ SysMenuDao.class
│ │ │ │ SysRoleDao.class
│ │ │ │ SysRoleMenuDao.class
│ │ │ │ SysUserDao.class
│ │ │ │ SysUserRoleDao.class
│ │ │ │ SysUserTokenDao.class
│ │ │ │
│ │ │ ├─entity
│ │ │ │ SysCaptchaEntity.class
│ │ │ │ SysConfigEntity.class
│ │ │ │ SysLogEntity.class
│ │ │ │ SysMenuEntity.class
│ │ │ │ SysRoleEntity.class
│ │ │ │ SysRoleMenuEntity.class
│ │ │ │ SysUserEntity.class
│ │ │ │ SysUserRoleEntity.class
│ │ │ │ SysUserTokenEntity.class
│ │ │ │
│ │ │ ├─form
│ │ │ │ PasswordForm.class
│ │ │ │ SysLoginForm.class
│ │ │ │
│ │ │ ├─oauth2
│ │ │ │ OAuth2Filter.class
│ │ │ │ OAuth2Realm.class
│ │ │ │ OAuth2Token.class
│ │ │ │ TokenGenerator.class
│ │ │ │
│ │ │ ├─redis
│ │ │ │ SysConfigRedis.class
│ │ │ │
│ │ │ └─service
│ │ │ │ ShiroService.class
│ │ │ │ SysCaptchaService.class
│ │ │ │ SysConfigService.class
│ │ │ │ SysLogService.class
│ │ │ │ SysMenuService.class
│ │ │ │ SysRoleMenuService.class
│ │ │ │ SysRoleService.class
│ │ │ │ SysUserRoleService.class
│ │ │ │ SysUserService.class
│ │ │ │ SysUserTokenService.class
│ │ │ │
│ │ │ └─impl
│ │ │ ShiroServiceImpl.class
│ │ │ SysCaptchaServiceImpl.class
│ │ │ SysConfigServiceImpl.class
│ │ │ SysLogServiceImpl.class
│ │ │ SysMenuServiceImpl.class
│ │ │ SysRoleMenuServiceImpl.class
│ │ │ SysRoleServiceImpl.class
│ │ │ SysUserRoleServiceImpl.class
│ │ │ SysUserServiceImpl.class
│ │ │ SysUserTokenServiceImpl.class
│ │ │
│ │ ├─mapper
│ │ │ ├─app
│ │ │ │ UserDao.xml
│ │ │ │
│ │ │ ├─job
│ │ │ │ ScheduleJobDao.xml
│ │ │ │ ScheduleJobLogDao.xml
│ │ │ │
│ │ │ ├─oss
│ │ │ │ SysOssDao.xml
│ │ │ │
│ │ │ └─sys
│ │ │ SysConfigDao.xml
│ │ │ SysLogDao.xml
│ │ │ SysMenuDao.xml
│ │ │ SysRoleDao.xml
│ │ │ SysRoleMenuDao.xml
│ │ │ SysUserDao.xml
│ │ │ SysUserRoleDao.xml
│ │ │ SysUserTokenDao.xml
│ │ │
│ │ ├─META-INF
│ │ │ spring-configuration-metadata.json
│ │ │
│ │ └─static
│ │ │ favicon.ico
│ │ │
│ │ └─swagger
│ │ │ favicon-16x16.png
│ │ │ favicon-32x32.png
│ │ │ index.html
│ │ │ index.yaml
│ │ │ o2c.html
│ │ │ oauth2-redirect.html
│ │ │ swagger-ui-bundle.js
│ │ │ swagger-ui-bundle.js.map
│ │ │ swagger-ui-standalone-preset.js
│ │ │ swagger-ui-standalone-preset.js.map
│ │ │ swagger-ui.css
│ │ │ swagger-ui.css.map
│ │ │ swagger-ui.js
│ │ │ swagger-ui.js.map
│ │ │ swagger-ui.min.js
│ │ │
│ │ ├─css
│ │ │ print.css
│ │ │ reset.css
│ │ │ screen.css
│ │ │ style.css
│ │ │ typography.css
│ │ │
│ │ ├─fonts
│ │ │ DroidSans-Bold.ttf
│ │ │ DroidSans.ttf
│ │ │
│ │ ├─images
│ │ │ collapse.gif
│ │ │ expand.gif
│ │ │ explorer_icons.png
│ │ │ favicon-16x16.png
│ │ │ favicon-32x32.png
│ │ │ favicon.ico
│ │ │ logo_small.png
│ │ │ pet_store_api.png
│ │ │ throbber.gif
│ │ │ wordnik_api.png
│ │ │
│ │ ├─lang
│ │ │ en.js
│ │ │ translator.js
│ │ │ zh-cn.js
│ │ │
│ │ └─lib
│ │ backbone-min.js
│ │ es5-shim.js
│ │ handlebars-4.0.5.js
│ │ highlight.9.1.0.pack.js
│ │ highlight.9.1.0.pack_extended.js
│ │ jquery-1.8.0.min.js
│ │ jquery.ba-bbq.min.js
│ │ jquery.slideto.min.js
│ │ jquery.wiggle.min.js
│ │ js-yaml.min.js
│ │ jsoneditor.min.js
│ │ lodash.min.js
│ │ marked.js
│ │ object-assign-pollyfill.js
│ │ sanitize-html.min.js
│ │ swagger-oauth.js
│ │
│ └─generated-sources
│ └─annotations
└─renren-generator
│ .gitignore
│ LICENSE
│ pom.xml
│ README.md
│ renren-generator.iml

├─src
│ ├─main
│ │ ├─java
│ │ │ └─io
│ │ │ └─renren
│ │ │ │ RenrenApplicationGenerator.java
│ │ │ │
│ │ │ ├─adaptor
│ │ │ │ MongoTableInfoAdaptor.java
│ │ │ │
│ │ │ ├─config
│ │ │ │ DbConfig.java
│ │ │ │ MongoCondition.java
│ │ │ │ MongoConfig.java
│ │ │ │ MongoManager.java
│ │ │ │ MongoNullCondition.java
│ │ │ │
│ │ │ ├─controller
│ │ │ │ SysGeneratorController.java
│ │ │ │
│ │ │ ├─dao
│ │ │ │ GeneratorDao.java
│ │ │ │ MongoDBGeneratorDao.java
│ │ │ │ MySQLGeneratorDao.java
│ │ │ │ OracleGeneratorDao.java
│ │ │ │ PostgreSQLGeneratorDao.java
│ │ │ │ SQLServerGeneratorDao.java
│ │ │ │
│ │ │ ├─entity
│ │ │ │ │ ColumnEntity.java
│ │ │ │ │ TableEntity.java
│ │ │ │ │
│ │ │ │ └─mongo
│ │ │ │ MongoDefinition.java
│ │ │ │ MongoGeneratorEntity.java
│ │ │ │ Type.java
│ │ │ │
│ │ │ ├─factory
│ │ │ │ MongoDBCollectionFactory.java
│ │ │ │
│ │ │ ├─service
│ │ │ │ SysGeneratorService.java
│ │ │ │
│ │ │ └─utils
│ │ │ Constant.java
│ │ │ DateUtils.java
│ │ │ GenUtils.java
│ │ │ MongoScanner.java
│ │ │ PageUtils.java
│ │ │ Query.java
│ │ │ R.java
│ │ │ RRException.java
│ │ │ RRExceptionHandler.java
│ │ │
│ │ └─resources
│ │ │ application.yml
│ │ │ generator.properties
│ │ │
│ │ ├─mapper
│ │ │ MySQLGeneratorDao.xml
│ │ │ OracleGeneratorDao.xml
│ │ │ PostgreSQLGeneratorDao.xml
│ │ │ SQLServerGeneratorDao.xml
│ │ │
│ │ ├─static
│ │ │ │ favicon.ico
│ │ │ │
│ │ │ ├─css
│ │ │ │ AdminLTE.min.css
│ │ │ │ all-skins.min.css
│ │ │ │ bootstrap.min.css
│ │ │ │ font-awesome.min.css
│ │ │ │ main.css
│ │ │ │
│ │ │ ├─fonts
│ │ │ │ fontawesome-webfont.eot
│ │ │ │ fontawesome-webfont.svg
│ │ │ │ fontawesome-webfont.ttf
│ │ │ │ fontawesome-webfont.woff
│ │ │ │ fontawesome-webfont.woff2
│ │ │ │ FontAwesome.otf
│ │ │ │ glyphicons-halflings-regular.eot
│ │ │ │ glyphicons-halflings-regular.svg
│ │ │ │ glyphicons-halflings-regular.ttf
│ │ │ │ glyphicons-halflings-regular.woff
│ │ │ │ glyphicons-halflings-regular.woff2
│ │ │ │
│ │ │ ├─js
│ │ │ │ common.js
│ │ │ │ generator.js
│ │ │ │ index.js
│ │ │ │
│ │ │ ├─libs
│ │ │ │ app.js
│ │ │ │ app.min.js
│ │ │ │ bootstrap.min.js
│ │ │ │ fastclick.min.js
│ │ │ │ jquery.min.js
│ │ │ │ jquery.slimscroll.min.js
│ │ │ │ router.js
│ │ │ │ vue.min.js
│ │ │ │
│ │ │ └─plugins
│ │ │ ├─jqgrid
│ │ │ │ grid.locale-cn.js
│ │ │ │ jquery.jqGrid.min.js
│ │ │ │ ui.jqgrid-bootstrap-ui.css
│ │ │ │ ui.jqgrid-bootstrap.css
│ │ │ │ ui.jqgrid.css
│ │ │ │
│ │ │ └─layer
│ │ │ │ layer.js
│ │ │ │
│ │ │ ├─mobile
│ │ │ │ │ layer.js
│ │ │ │ │
│ │ │ │ └─need
│ │ │ │ layer.css
│ │ │ │
│ │ │ └─skin
│ │ │ ├─default
│ │ │ │ icon-ext.png
│ │ │ │ icon.png
│ │ │ │ layer.css
│ │ │ │ loading-0.gif
│ │ │ │ loading-1.gif
│ │ │ │ loading-2.gif
│ │ │ │
│ │ │ └─moon
│ │ │ default.png
│ │ │ style.css
│ │ │
│ │ ├─template
│ │ │ add-or-update.vue.vm
│ │ │ Controller.java.vm
│ │ │ Dao.java.vm
│ │ │ Dao.xml.vm
│ │ │ Entity.java.vm
│ │ │ index.vue.vm
│ │ │ menu.sql.vm
│ │ │ MongoChildrenEntity.java.vm
│ │ │ MongoEntity.java.vm
│ │ │ Service.java.vm
│ │ │ ServiceImpl.java.vm
│ │ │
│ │ └─views
│ │ generator.html
│ │ index.html
│ │ main.html
│ │
│ └─test
│ └─java
│ └─io
│ └─renren
│ RenrenApplicationTests.java

└─target
├─classes
│ │ application.yml
│ │ generator.properties
│ │
│ ├─io
│ │ └─renren
│ │ │ RenrenApplicationGenerator.class
│ │ │
│ │ ├─adaptor
│ │ │ MongoTableInfoAdaptor.class
│ │ │
│ │ ├─config
│ │ │ DbConfig.class
│ │ │ MongoCondition.class
│ │ │ MongoConfig.class
│ │ │ MongoManager.class
│ │ │ MongoNullCondition.class
│ │ │
│ │ ├─controller
│ │ │ SysGeneratorController.class
│ │ │
│ │ ├─dao
│ │ │ GeneratorDao.class
│ │ │ MongoDBGeneratorDao.class
│ │ │ MySQLGeneratorDao.class
│ │ │ OracleGeneratorDao.class
│ │ │ PostgreSQLGeneratorDao.class
│ │ │ SQLServerGeneratorDao.class
│ │ │
│ │ ├─entity
│ │ │ │ ColumnEntity.class
│ │ │ │ TableEntity.class
│ │ │ │
│ │ │ └─mongo
│ │ │ MongoDefinition.class
│ │ │ MongoGeneratorEntity.class
│ │ │ Type.class
│ │ │
│ │ ├─factory
│ │ │ MongoDBCollectionFactory.class
│ │ │
│ │ ├─service
│ │ │ SysGeneratorService.class
│ │ │
│ │ └─utils
│ │ Constant.class
│ │ DateUtils.class
│ │ GenUtils.class
│ │ MongoScanner$ForkJoinGetProcessName.class
│ │ MongoScanner$ForkJoinProcessType.class
│ │ MongoScanner.class
│ │ PageUtils.class
│ │ Query.class
│ │ R.class
│ │ RRException.class
│ │ RRExceptionHandler.class
│ │
│ ├─mapper
│ │ MySQLGeneratorDao.xml
│ │ OracleGeneratorDao.xml
│ │ PostgreSQLGeneratorDao.xml
│ │ SQLServerGeneratorDao.xml
│ │
│ ├─static
│ │ │ favicon.ico
│ │ │
│ │ ├─css
│ │ │ AdminLTE.min.css
│ │ │ all-skins.min.css
│ │ │ bootstrap.min.css
│ │ │ font-awesome.min.css
│ │ │ main.css
│ │ │
│ │ ├─fonts
│ │ │ fontawesome-webfont.eot
│ │ │ fontawesome-webfont.svg
│ │ │ fontawesome-webfont.ttf
│ │ │ fontawesome-webfont.woff
│ │ │ fontawesome-webfont.woff2
│ │ │ FontAwesome.otf
│ │ │ glyphicons-halflings-regular.eot
│ │ │ glyphicons-halflings-regular.svg
│ │ │ glyphicons-halflings-regular.ttf
│ │ │ glyphicons-halflings-regular.woff
│ │ │ glyphicons-halflings-regular.woff2
│ │ │
│ │ ├─js
│ │ │ common.js
│ │ │ generator.js
│ │ │ index.js
│ │ │
│ │ ├─libs
│ │ │ app.js
│ │ │ app.min.js
│ │ │ bootstrap.min.js
│ │ │ fastclick.min.js
│ │ │ jquery.min.js
│ │ │ jquery.slimscroll.min.js
│ │ │ router.js
│ │ │ vue.min.js
│ │ │
│ │ └─plugins
│ │ ├─jqgrid
│ │ │ grid.locale-cn.js
│ │ │ jquery.jqGrid.min.js
│ │ │ ui.jqgrid-bootstrap-ui.css
│ │ │ ui.jqgrid-bootstrap.css
│ │ │ ui.jqgrid.css
│ │ │
│ │ └─layer
│ │ │ layer.js
│ │ │
│ │ ├─mobile
│ │ │ │ layer.js
│ │ │ │
│ │ │ └─need
│ │ │ layer.css
│ │ │
│ │ └─skin
│ │ ├─default
│ │ │ icon-ext.png
│ │ │ icon.png
│ │ │ layer.css
│ │ │ loading-0.gif
│ │ │ loading-1.gif
│ │ │ loading-2.gif
│ │ │
│ │ └─moon
│ │ default.png
│ │ style.css
│ │
│ ├─template
│ │ add-or-update.vue.vm
│ │ Controller.java.vm
│ │ Dao.java.vm
│ │ Dao.xml.vm
│ │ Entity.java.vm
│ │ index.vue.vm
│ │ menu.sql.vm
│ │ MongoChildrenEntity.java.vm
│ │ MongoEntity.java.vm
│ │ Service.java.vm
│ │ ServiceImpl.java.vm
│ │
│ └─views
│ generator.html
│ index.html
│ main.html

└─generated-sources
└─annotations

四.项目前置知识

1.SpringCloud Alibaba

1.1简介

SpringCloud Alibaba致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务必须的组件,方便开发者通过SpringCloud编程模型轻松使用这些组件来开发分布式的应用服务。

依托SpringCloud Alibaba,你只需要添加一些注解和少量的配置,就可以将SpringCloud应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。

github的地址:https://github.com/alibaba/spring-cloud-alibaba

1.2为什么使用SpringCloud Alibaba?

SpringCloud的不足:

  • 部分组件停止维护,给开发带来不便
  • 环境搭建复杂,没有完善的可视化界面,需要大量的二次开发和定制
  • 配置复杂,难以上手,部分配置差别难以区分和合理应用

SpringCloud Alibaba的优势:

阿里使用过的组件经历了考验,性能强悍,设计合理,开源成套的搭配和可视化的界面给开发带来极大的便利,搭建简单,学习成本低。

1.3 SpringCloud Alibaba最终的技术搭配方案

SpringCloud Alibaba - Nacos :注册中心(服务发现/注册)

SpringCloud Alibaba - Nacos :配置中心(动态的配置管理)

SpringCloud - Ribbon: 负载均衡

SpringCloud - Feign: 声明式HTTP客户端(远程调用服务/服务调用)

SpringCloud Alibaba - Sentinel:服务容错(限流、降级、熔断)

SpringCloud - Gateway: API网关(webflux编程模式)

SpringCloud - Sleuth:调用链监控

SpringCloud Alibaba - Seata:原Fescar,即分布式事务解决方案

image-20230519161707857

1.4 项目中使用SpringCloud Alibaba

在common模块中添加如下的依赖

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>2.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

1.5 SpringCloud Alibaba的组件

1.Nacos注册中心

image-20230519162859782

简介

Nacos 是阿里巴巴推出来的一个新开源项目,这是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施

Nacos 支持如下核心特性

1)服务发现: 支持 DNS 与 RPC 服务发现,也提供原生 SDK 、OpenAPI 等多种服务注册方式和 DNS、HTTP 与 API 等多种服务发现方式。
2)服务健康监测: Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。
3)动态配置服务: Nacos 提供配置统一管理功能,能够帮助我们将配置以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。
4)动态 DNS 服务: Nacos 支持动态 DNS 服务权重路由,能够让我们很容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单 DNS 解析服务。
5)服务及其元数据管理: Nacos 支持从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。

使用nacos

使用之前需要下载nacos并启动nacos的服务

image-20230519164012950

在common工程中添加依赖

1
2
3
4
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

配置中心中配置Nacos的地址

1
2
3
4
5
6
spring:
application:
name: gulimall-coupon #指定服务的名字,指定了名字才能注册到注册中心中
cloud:
nacos:
server-addr: 127.0.0.1:8848 #指定注册中心的地址信息

在每个启动类上使用注解开启服务的注册与发现功能

1
2
//开启服务的注册与发现功能
@EnableDiscoveryClient

查看注册服务列表

1
通过访问: http://localhost:8848/nacos

image-20230519165606784

2.Feign声明式远程调用

简介

Feign是一个声明式的HTTP客户端,它的目的是让远程调用变得更简单。Feign提供了HTTP请求的模板,通过**编写简单的接口和插入注解**,就可以定义好HTTP请求的参数、格式、地址等信息。

Feign整合了Ribbon(负载均衡)和Hystrix(服务熔断),可以让我们不再需要显式的使用这两个组件。

使用

引入依赖,每个模块都需要引入这个依赖

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

编写代码测试远程调用,这里我们以用户模块调用优惠卷模块来获取用户的拥有的优惠卷为例来测试远程调用。

会员模块提供获取用户名下优惠卷的方法

1
2
3
4
5
6
7
8
9
  /**
* 获取用户明名下的优惠卷
*/
@RequestMapping("member/list")
public R membercoupons(){
CouponEntity couponEntity = new CouponEntity();
couponEntity.setCouponName("满一百减五十");
return R.ok().put("coupons",Arrays.asList(couponEntity));
}

用户模块远程调用优惠卷模块

远程调用别的服务端步骤

  1. 引入open-feign的依赖

  2. 调用端编写一个接口,告诉SpringCloud这个接口需要调用远程服务,

    声明接口的每一个方法都是调用哪个远程服务的那个请求

  3. 开启远程调用的功能,在调用端的启动类上添加**@EnableFeignClients**注解

在用户模块创建一个Feign包(维护的时候见名知义),声明调用的接口,在启动类上添加注解

1
2
3
//注解
//basePackages声明的远程调用的接口所在的包
@EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.atguigu.gulimall.member.feign;

import com.atguigu.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

/**
* @author GongChangjiang
* @version 1.0
* @Date 2023/5/19
* @Description 远程调用优惠卷服务
*/
@FeignClient("gulimall-coupon")//告诉springCloud这是一个远程客户端,并指定要调用服务的服务名
public interface CouponFeignService {


/**
* 这里需要写全路径
* 远程调用优惠卷模块中获取优惠卷信息的方法
*/
@RequestMapping("/coupon/coupon/member/list")
public R membercoupons();
}

测试远程调用

1
2
3
4
5
6
7
8
9
10
11
12
13
@Autowired
private CouponFeignService couponFeignService;

/**
* 测试远程调用优惠卷模块的服务
*/
@RequestMapping("/coupons")
public R test(){
MemberEntity memberEntity = new MemberEntity();
memberEntity.setNickname("张三");
R membercoupons = couponFeignService.membercoupons();
return R.ok().put("member", memberEntity).put("coupons", membercoupons.get("coupons"));
}

测试的时候出现的小问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalanc
出现这个问题是没有loadbalanc,但是nacos中ribbon会造成loadbalanc包失效,在common的pom中的依赖改成如下的依赖-->
<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.2.RELEASE</version>
</dependency>

处理完上面的报错之后,浏览器访问 http://localhost:8000/member/member/coupons ,显示如下的内容,说明远程调用成功

image-20230519210917423

3.Nacos配置中心

简介

简介见上面的nacos注册中心,这里的Nacos配置中心和上面的nacos注册中心是同一个东西

使用

1.在common中引入nacos配置中心的依赖,和上面的注册中心的依赖不一样

1
2
3
4
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

2.给需要配置中心管理的模块下创建配置文件bootstrap.properties,这个文件会优先于application.properties读取

1
2
3
4
5
#服务名
spring.application.name=gulimall-coupon
#Nacos配置中心的地址的地址
#注意看这里多了一个config,和模块中nacos的地址还是有区别的
spring.cloud.nacos.config.server-addr=127.0.0.1:8848

3.测试

3.1.在application.properties配置文件中添加如下的配置

1
2
coupon.user.name=zhansan
coupon.user.age=18

3.2 编写接口并访问测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

/**
* 获取配置文件中的配置信息
*/
@Value("${user.name}")
private String name;
@Value("${user.age}")
private Integer age;

/**
* 测试优先获取哪个配置文件的值
*/
@RequestMapping("/test")
public R test(){
return R.ok().put("name",name).put("age",age);
}

测试的结果如下:

image-20230519213945320

3.3 需求:应用不重启,实时的修改配置文件中的值

3.3.1 在Nacos配置中心中创建一个名为 应用名.properties(例如:gulimall-coupon.properties) 的配置文件

进入nacos管理的可视化页面,点击配置列表,点击 + ,新建一个配置 ,同时将配置文件中name值改为lihua

image-20230519215420623

发布之后,就可以在配置列表中看到这个配置了

image-20230519215232782

重启项目测试,配置中心的配置是否生效

没有生效的话,添加这个依赖,springBoot2.4以上的版本需要

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

生效的话,这时刷新页面,可以看到name已经由zhangsan变成lihua了

image-20230519220357933

3.3.2 想让配置文件实时生效的话,这时我们还要添加以下的注解,然后重启应用,让注解生效

注意: @RefreshScope 这里的注解添加在Controller上,不是添加在启动类上

1
2
//给需要配置中心统一配置的模块的Controller上上添加@RefreshScope的注解
@RefreshScope

最终测试

这时我们在配置中心中修改配置并发布,这些配置就可以实时的生效了

我们在配置中心修改name和age的值,并发布

image-20230519220922701

这时刷新请求接口的页面,我们可以看到页面中的数据发生了实时的变化

image-20230519223403139

总结

实现nacos配置中心统一配置的流程

  1. 引入依赖

    1
    2
    3
    4
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
  2. 创建一个bootstrap.properties的配置文件,添加如下的内容:

    1
    2
    3
    4
    5
    #服务名
    spring.application.name=gulimall-coupon
    #Nacos配置中心的地址
    #注意看这里多了一个config,和模块中nacos的地址还是有区别的
    spring.cloud.nacos.config.server-addr=127.0.0.1:8848
  3. 需要给配置中心中新建一个数据集(Data Id)为 应用名.properties(例如:gulimall-coupon.properties)的配置。默认规则: 应用名.properties

  4. 给 应用名.properties 添加任何配置 这些配置都可以在配置中心配置生效

  5. 在Controller上添加**@RefreshScope**注解就可以实时的动态刷新配置,配置中心更新了配置并发布了之后,配置实时的生效。

注意

如果配置中心和当前应用的配置文件都配置了相同的项,优先使用配置中心的配置

细节

命名空间: 配置隔离

默认:public(保留空间)

1.可以分别设置开发、测试、生产环境的命名空间,不同的环境下使用不同的命名空间中的配置

通过命令空间实现环境隔离

bootstrap.properties设置使用哪个命名空间,可以在配置文件中添加如下的配置

image-20230519225637848

1
2
3
4
# 切换名称空间
# 可以选择对应的命名空间 # 写上对应环境的命名空间ID
# 使用唯一的命名空间ID
spring.cloud.nacos.config.namespace=8a84e478-92d0-4581-a316-b0274fc76eb8

2.可以为每一个微服务可以创建自己的命名空间,让每个服务的配置放在每个服务的命名空间下

配置集:所有的配置的集合

配置集ID: 类似文件名

Data Id: 就是配置集ID

配置分组:

默认所有的配置集都属于 : DEFAULT_GROUP

例如在生产环境的命名空间下,淘宝的配置分组有以下几个: 双十一,688,平时

1
2
#指定配置分组
spring.cloud.nacos.config.group=1111

Tips:同一个命名空间下可能会有不同的配置分组

每个微服务创建自己的命令空间,使用配置分组区分环境 dev、test、prod

从配置中心中读取多个配置集(配置文件):

可以在bootstrap.properties添加以下的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#第一个配置文件
#配置文件名
spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
#配置文件的对应的配置分组
spring.cloud.nacos.config.ext-config[0].group=dev
#是否动态的刷新
spring.cloud.nacos.config.ext-config[0].refresh=true

#第二个配置文件
spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
spring.cloud.nacos.config.ext-config[1].group=dev
spring.cloud.nacos.config.ext-config[1].refresh=true

#第三个配置文件
spring.cloud.nacos.config.ext-config[2].data-id=others.yml
spring.cloud.nacos.config.ext-config[2].group=dev
spring.cloud.nacos.config.ext-config[2].refresh=true

总结:

1.微服务的任何配置信息,任何配置文件都可以放在配置中心中

2.加载配置中心的哪些配置文件,只需要在bootstrap.properties说明加载哪些配置文件即可

3.如果配置中心和当前应用的配置文件都配置了相同的项,优先使用配置中心的配置

4.Gateway网关

image-20230519233741029

简介

网关最为流量的入口,常用的功能包括**路由转发**、**权限校验**、**限流控制**等。SpringCloud gateway是springcloud官方提供的第二代网关框架,取代了Zull网关。

网关的功能

  • 针对所有请求进行登陆统一鉴权(登录态)限流缓存日志(用户打点)
  • 可以根据不同的请求路径pattern,来进行请求的鉴权、转发、和拒绝。
  • 协议转化。针对后段多种不同的协议,在网关层统一处理后以HTTP对外提供服务。
  • 提供统一的错误码
  • 请求转发,并且可以基于网关实现内网与外网的隔离

使用

  1. 创建一个springboot initializr的工程 ,勾选上Gateway的依赖

  2. 在网关模块的pom.xml文件中添加上对common工程的依赖

  3. 在网关的启动类上添加上**@EnableDiscoveryClient**的注解,把网关注册到注册中心

  4. 配置nacos的地址和服务名信息

    1
    2
    3
    4
    #配置网关的地址
    spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    #服务名
    spring.application.name=gulimall-gateway
  5. 创建bootstrap.properties配置文件,将网关模块的配置通过交给nacos配置中心管理

    image-20230520205015056

    1
    2
    3
    4
    5
    spring.application.name=gulimall-gateway
    #配置配置中心的地址
    spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    #名称空间
    spring.cloud.nacos.config.namespace=4a8e80ba-3d3c-40ef-897f-d400797c4676
  6. 启动测试

    启动之后出现的问题及解决方案:

    a.单元测试@Test注解爆红,是我们更换了springboot的版本的原因,删除爆红的地方,重新导包即可

    b.数据库相关的报错,是因为网关中没有使用数据库,就没有配置数据库的配置,我们通过@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})解决。

  7. 转发测试

    测试案例:我们在浏览器的地址栏输入localhost:88?url=baidu,就给我们转发到百度,输入localhost:88?url=qq就给我们转发到腾讯qq

    实现:创建一个yml的配置文件,在配置文件中添加如下的配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    spring:
    cloud:
    gateway:
    routes:
    # 路由的唯一id
    - id: baidu_route
    uri: https://www.baidu.com
    # 断言,什么时候去上面那个url
    predicates:
    # 下面的这句话的意思是如果请求中存在请求参数url=baidu,我们就去 https://www.baidu.com这个url
    - Query=url,baidu
    - id: qq_route
    uri: https://www.qq.com
    predicates:
    # 下面的这句话的意思是如果请求中存在请求参数url=qq,我们就去 https://www.qq.com这个url
    - Query=url,qq
    image-20230520212001874

2.前端开发的基础知识

前端基础知识 | The Blog (qingling.icu)

前后端技术类比

image-20230520212351174

五.基础篇程序设计

提示:所有的API路径一定要和老师的一样,不然在后面会吃亏!!!

1.商品服务

1.1 三级分类

1.1.1 查询-递归树形结构数据获取

controller

1
2
3
4
5
6
7
8
/**
* 查询所有的分类以及子分类,以树形结构组装起来
*/
@RequestMapping("/list/tree")
public R list(){
List<CategoryEntity> entities = categoryService.listWithTree();
return R.ok().put("data", entities);
}

service

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.atguigu.gulimall.product.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

@Autowired
private CategoryDao categoryDao;

@Override
public PageUtils queryPage(Map<String, Object> params) {
IPage<CategoryEntity> page = this.page(
new Query<CategoryEntity>().getPage(params),
new QueryWrapper<CategoryEntity>()
);

return new PageUtils(page);
}

/**
* 查询所有分类,以树形结构显示
*/
@Override
public List<CategoryEntity> listWithTree() {
//查出所有的分类
List<CategoryEntity> entities = categoryDao.selectList(null);
//组装成父子的树形结构
//找到所有的一级分类
List<CategoryEntity> level1List = entities.stream().filter(categoryEntity ->
//父级分类的id等于0的就是一级分类
categoryEntity.getParentCid() == 0
).map((menu) -> {
//为当前分类的子分类赋值
menu.setChildren(getChildren(menu, entities));
return menu;
}).sorted((menu1, menu2) -> {
//排序
return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
}).collect(Collectors.toList());

return level1List;
}

/**
* 递归查询所有菜单的子菜单
*
* @param root 当前分类
* @param all 所有的分类数据
* @return 子菜单的集合
*/
private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all) {
List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
return categoryEntity.getParentCid().equals(root.getCatId());
}).map(categoryEntity -> {
//1.递归找子菜单
categoryEntity.setChildren(getChildren(categoryEntity, all));
return categoryEntity;
}).sorted((menu1, menu2) -> {
//2.菜单的排序
return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
}).collect(Collectors.toList());
return children;
}

}

1.1.2 配置路由和网关和路径重写

前端访问的地址

image-20230529170214769

前端代码中的配置

image-20230529170149102

修改访问的基准路径,统一访问网关,再由网关做请求的转发,转发到指定的服务中去(renren-fast-vue\static\config\index.js文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 开发环境
*/
;(function () {
window.SITE_CONFIG = {};

// api接口请求地址
window.SITE_CONFIG['baseUrl'] = 'http://localhost:88';

// cdn地址 = 域名 + 版本号
window.SITE_CONFIG['domain'] = './'; // 域名
window.SITE_CONFIG['version'] = ''; // 版本号(年月日时分)
window.SITE_CONFIG['cdnUrl'] = window.SITE_CONFIG.domain + window.SITE_CONFIG.version;
})();

将renren-fast注册到注册中心中去

修改pom.xml中的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 修改springBoot的版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/>
</parent>
<!-- 依赖common文件,获取nacos相关的注解 -->
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>gulimall-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

在启动类上添加@EnableDiscoveryClient注解,在配置文件中添加上nacos地址的配置

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

修改网关中的断言,让前台的请求转发到正确的路径上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
cloud:
gateway:
routes:
# 路由的唯一id
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
# 路径重写
filters:
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
# 前端项目中的基准路径 http://localhost:88/api
# http://localhost:88/api/captcha.jpg 路径重写为 http://localhost:8080/renren-fast/captcha.jpg

解决登录的时候的跨域问题

浏览器中的跨域的报错信息

image-20230530111856491

跨域问题的介绍

image-20230530112115261

跨域流程

文档介绍

image-20230530112630678

跨域的解决方案

  1. 使用nginx部署为同一域
  2. 配置当前请求允许跨域 (在网关中通过过滤器给请求添加响应头)

跨域的配置

添加跨域的配置类

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
package com.eatguigu.gulimall.gateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

/**
* @author GongChangjiang
* @version 1.0
* @Date 2023/5/30
* @Description 跨域配置类
*/
@Configuration
public class CorsConfig {

@Bean
public CorsWebFilter corsWebFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
//1.配置跨域
//允许所有的请求头跨域
corsConfiguration.addAllowedHeader("*");
//允许所有的请求方式跨域
corsConfiguration.addAllowedMethod("*");
//任意请求来源都可以跨域
//corsConfiguration.addAllowedOrigin("*"); 老版本的springBoot使用这个,新版本的使用下面这个
corsConfiguration.addAllowedOriginPattern("*");
//允许携带cookie跨域
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}

注掉renren-fast工程中的跨域配置,避免产生两次跨域的问题

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
/**
* Copyright (c) 2016-2019 人人开源 All rights reserved.
*
* https://www.renren.io
*
* 版权所有,侵权必究!
*/

package io.renren.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

// @Override
// public void addCorsMappings(CorsRegistry registry) {
// registry.addMapping("/**")
// .allowedOrigins("*")
// .allowCredentials(true)
// .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
// .maxAge(3600);
// }
}

再次测试,成功的进入后台管理系统

1.2.3 查询-页面中树形显示

新增一个商品系统的目录,并在该目录下创建一个分类维护的二级分类

1.添加一个一级分类

image-20230529161308671

image-20230529161317694

2.在商品系统的一级分类下添加一个分类维护的二级分类

image-20230529161648257

3.路径的问题

image-20230529162235482

4.路径与文件的对应关系

image-20230529162946584

5.发送请求的示例

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
<template>
<div>
<!-- 树形控件 -->
<el-tree
:data="data"
:props="defaultProps"
@node-click="handleNodeClick"
></el-tree>
</div>
</template>

<script>
export default {
//定义变量
data() {
return {
data: [],
defaultProps: {
children: "children",
label: "label",
},
};
},
//钩子函数
created() {
this.getMenus();
},

//方法
methods: {
handleNodeClick(data) {
console.log(data);
},
//获取所有的菜单
getMenus() {
//正式的发送请求
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get",
}).then(({ data }) => {
console.log(data);
});
},
},
};
</script>
<style>
</style>

6.树形显示商品的分类信息

前端修改网关商品模块的路由(注意:匹配精确的路由放在模糊的路由的上面)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
spring:
cloud:
gateway:
routes:
#这个路由精确一些,放在上面,避免被下面模糊的路由吞掉
- id: product_route
uri: lb://gulimall-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}

# 路由的唯一id
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
# 路径重写
filters:
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}


# 前端项目中的基准路径 http://localhost:88/api
# http://localhost:88/api/captcha.jpg 路径重写为 http://localhost:8080/renren-fast/captcha.jpg

前端发送请求,正确地获取数据

image-20230530172846989

在页面上显示数据

Element的配置

image-20230530173658950

前端代码

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
<template>
<div>
<!-- 树形控件 -->
<el-tree
:data="menus"
:props="defaultProps"
@node-click="handleNodeClick"
></el-tree>
</div>
</template>

<script>
export default {
//定义变量
data() {
return {
menus: [],
defaultProps: {//这里的配置看官方的文档
children: "children",//父节点上的子节点
label: "name",//每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
},
};
},
//钩子函数
created() {
//调用获取商品分类数据的方法
this.getMenus();
},

//方法
methods: {
handleNodeClick(data) {
console.log(data);
},
//获取所有的菜单
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get",
}).then(({ data }) => {
this.menus = data.data;
});
},
},
};
</script>

<style>
</style>

显示的效果

image-20230530173801824

1.2.4 删除-删除商品分类

前端页面修改

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
<template>
<!-- 树形控件-->
<!-- :expand-on-click-node="false"设置为点击箭头的时候才会展开,避免点击删除的按钮的时候展开 -->
<!-- show-checkbox表示开启选择框,批量选择-->
<!-- node-key表示在整个树中唯一的表示 -->
<el-tree
:data="menus"
:props="defaultProps"
:expand-on-click-node="false"
show-checkbox
node-key="catId"
:default-expanded-keys="expandedKey"
>
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<el-button
v-if="node.level <= 2"
type="text"
size="mini"
@click="() => append(data)"
>
添加
</el-button>
<!-- 添加判断,没有下一级节点的时候就显示删除的按钮 -->
<el-button
v-if="data.children.length <= 0"
type="text"
size="mini"
@click="() => remove(node, data)"
>
删除
</el-button>
</span>
</span></el-tree
>
</template>

<script>
export default {
//定义变量
data() {
return {
menus: [],
defaultProps: {
//这里的配置看官方的文档
children: "children", //父节点上的子节点
label: "name", //每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
},
expandedKey:[] //需要展开的数组,用于删除之后树形控件仍然展开
};
},
//钩子函数
created() {
//调用获取商品分类数据的方法
this.getMenus();
},

//方法
methods: {
append(data) {
console.log("append: ", data);
},
//删除节点的方法
remove(node, data) {
var ids = [data.catId];
this.$confirm(`是否要删除[${data.name}]菜单?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
this.$http({
url: this.$http.adornUrl("/product/category/delete"),
method: "post",
data: this.$http.adornData(ids, false),
}).then(({ data }) => {
this.$message({
message: "菜单删除成功",
type: "success",
});
//刷新一下页面
this.getMenus();
//设置需要默认展开的菜单(依然展开刚才删除的节点)
this.expandedKey = [node.parent.data.catId]
});
})
.catch(() => {
this.$message({
type: "info",
message: "已取消删除",
});
});
},
//获取所有的菜单
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get",
}).then(({ data }) => {
this.menus = data.data;
});
},
},
};
</script>

<style>
</style>

配置逻辑删除

配置全局的逻辑删除规则

1
2
3
4
5
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

在showStatus上添加上逻辑删除的注解

1
@TableLogic(value = "1",delval = "0")

后端代码的实现

controlller

1
2
3
4
5
6
7
8
9
10
11
/**
* 删除
*/
@RequestMapping("/delete")
//@RequiresPermissions("product:category:delete")
public R delete(@RequestBody Long[] catIds) {
//检查当前删除的菜单,是否被别的地方引用
//categoryService.removeByIds(Arrays.asList(catIds));
categoryService.removeMenuByIds(Arrays.asList(catIds));
return R.ok();
}

service 这里后面需要优化

1
2
3
4
5
6
7
8
9
10
/**
* 删除分类的方法
* @param list 分类id的集合
*/
@Override
public void removeMenuByIds(List<Long> list) {
//TODO 检查当前删除的菜单是否被其他的地方引用 后面优化
//逻辑删除
categoryDao.deleteBatchIds(list);
}

1.2.5 添加-添加商品分类

前端代码

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
<template>
<div>
<!-- 树形控件-->
<!-- :expand-on-click-node="false"设置为点击箭头的时候才会展开,避免点击删除的按钮的时候展开 -->
<!-- show-checkbox表示开启选择框,批量选择-->
<!-- node-key表示在整个树中唯一的表示 -->
<el-tree
:data="menus"
:props="defaultProps"
:expand-on-click-node="false"
show-checkbox
node-key="catId"
:default-expanded-keys="expandedKey"
>
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<el-button
v-if="node.level <= 2"
type="text"
size="mini"
@click="() => append(data)"
>
添加
</el-button>
<!-- 添加判断,没有下一级节点的时候就显示删除的按钮 -->
<el-button
v-if="data.children.length <= 0"
type="text"
size="mini"
@click="() => remove(node, data)"
>
删除
</el-button>
</span>
</span></el-tree
>
<!-- 添加分类的时候弹出的对话框 -->
<el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
<el-form :model="category">
<el-form-item label="分类名称">
<el-input v-model="category.name" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="addCategory">确 定</el-button>
</span>
</el-dialog>
</div>
</template>

<script>
export default {
//定义变量
data() {
return {
menus: [],
defaultProps: {
//这里的配置看官方的文档
children: "children", //父节点上的子节点
label: "name", //每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
},
expandedKey: [], //需要展开的数组,用于删除之后树形控件仍然展开
dialogVisible: false, //添加菜单的对话框的开启或者关闭
//表单绑定的对象
category: {
name: "", //分类的名称
parentCid: 0, //父类的id
catLevel: 0, //分类的级别
showStatus: 1, //是否显示该分类
sort: 0, //排序
},
};
},
//钩子函数
created() {
//调用获取商品分类数据的方法
this.getMenus();
},

//方法
methods: {
//添加分类(弹出的对话框确定按钮执行的方法)
addCategory() {
console.log("提交的数据:", this.category);
//将添加的数据返回给后端
this.$http({
url: this.$http.adornUrl("/product/category/save"),
method: "post",
data: this.$http.adornData(this.category, false),
}).then(({ data }) => {
this.$message({
message: "菜单添加成功",
type: "success",
});
//刷新一下页面
this.getMenus()
//设置需要默认展开的菜单(依然展开刚才添加的节点)
this.expandedKey = [this.category.parentCid];
});
//关闭对话框
this.dialogVisible = false;
},
//添加分类的方法
append(data) {
//清空一下表单的数据
this.category.name = "";
//打开添加的对话框
this.dialogVisible = true;
//为分类的对象赋值
this.category.parentCid = data.catId;
this.category.catLevel = data.catLevel * 1 + 1; //防止catLevel是一个字符串
},
//删除节点的方法
remove(node, data) {
var ids = [data.catId];
this.$confirm(`是否要删除[${data.name}]菜单?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
this.$http({
url: this.$http.adornUrl("/product/category/delete"),
method: "post",
data: this.$http.adornData(ids, false),
}).then(({ data }) => {
this.$message({
message: "菜单删除成功",
type: "success",
});
//刷新一下页面
this.getMenus();
//设置需要默认展开的菜单(依然展开刚才删除的节点)
this.expandedKey = [node.parent.data.catId];
});
})
.catch(() => {
this.$message({
type: "info",
message: "已取消删除",
});
});
},
//获取所有的菜单
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get",
}).then(({ data }) => {
this.menus = data.data;
});
},
},
};
</script>

<style>
</style>

后端代码

1
2
3
4
5
6
7
8
9
10
 /**
* 保存
*/
@RequestMapping("/save")
//@RequiresPermissions("product:category:save")
public R save(@RequestBody CategoryEntity category) {
categoryService.save(category);

return R.ok();
}

实现的效果

image-20230605163004850

1.2.6 修改-修改商品的分类

前端的代码(太复杂了,没有写完)

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
<template>
<div>
<!-- 树形控件-->
<!-- :expand-on-click-node="false"设置为点击箭头的时候才会展开,避免点击删除的按钮的时候展开 -->
<!-- show-checkbox表示开启选择框,批量选择-->
<!-- node-key表示在整个树中唯一的表示 -->
<!-- draggable 实现节点自由拖拽的效果 -->
<el-tree
:data="menus"
:props="defaultProps"
:expand-on-click-node="false"
show-checkbox
node-key="catId"
:default-expanded-keys="expandedKey"
draggable
:allow-drop="allowDrop"
@node-drop="handleDrop"
>
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<el-button
v-if="node.level <= 2"
type="text"
size="mini"
@click="() => append(data)"
>
添加
</el-button>
<el-button type="text" size="mini" @click="() => edit(data)">
修改
</el-button>
<!-- 添加判断,没有下一级节点的时候就显示删除的按钮 -->
<el-button
v-if="data.children.length <= 0"
type="text"
size="mini"
@click="() => remove(node, data)"
>
删除
</el-button>
</span>
</span></el-tree
>
<!-- 添加分类的时候弹出的对话框 -->
<el-dialog
:title="title"
:visible.sync="dialogVisible"
width="30%"
:close-on-click-modal="false"
>
<el-form :model="category">
<el-form-item label="分类名称">
<el-input v-model="category.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="图标">
<el-input v-model="category.icon" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="计量单位">
<el-input
v-model="category.productUnit"
autocomplete="off"
></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="submitDate">确 定</el-button>
</span>
</el-dialog>
</div>
</template>

<script>
export default {
//定义变量
data() {
return {
updateNodes: [], //经过修改之后的节点的信息
maxLevel: 0, //最大的层级
title: "", //弹出框的提示信息
dialogType: "", //弹出的对话框的类型 edit/add
menus: [],
defaultProps: {
//这里的配置看官方的文档
children: "children", //父节点上的子节点
label: "name", //每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
},
expandedKey: [], //需要展开的数组,用于删除之后树形控件仍然展开
dialogVisible: false, //添加菜单的对话框的开启或者关闭
//表单绑定的对象
category: {
name: "", //分类的名称
parentCid: 0, //父类的id
catLevel: 0, //分类的级别
showStatus: 1, //是否显示该分类
sort: 0, //排序
catId: null, //分类的id信息
icon: "", //商品分类的图标
productUnit: "", //计量单位
},
};
},
//钩子函数
created() {
//调用获取商品分类数据的方法
this.getMenus();
},

//方法
methods: {
//拖拽成功之后调用这个函数进行回调的处理
handleDrop(draggingNode, dropNode, dropType, ev) {
console.log("handleDrop: ", draggingNode, dropNode, dropType);
//当前的节点的最新父节点id
let pCid = 0; //当前节点的组最新父节点id
let siblings = null; //当前的节点的兄弟节点
//判断的节点的进入的方式
if (dropType == "before" || dropType == "after") {
pCid =
dropNode.parent.data.catId == undefined
? 0
: dropNode.parent.data.catId;
siblings = dropNode.parent.childNodes;
} else {
pCid = dropNode.data.catId;
siblings = dropNode.childNodes;
}
//当前的节点的最新的顺序
for (let i = 0; i < siblings.length; i++) {
if (siblings[i].data.catId == draggingNode.data.catId) {
//如果当前遍历的是正在拖拽的节点
let catLevel = draggingNode.catLevel;
if (siblings[i].level != draggingNode.level) {
//当前的节点的层级发生的变化
catLevel = siblings[i].level;
//修改子节点的层级
this.updateChildNodeLevel(siblings[i]);
}
this.updateNodes.push({
catId: siblings[i].data.catId,
sort: i,
parentCid: pCid,
catLevel: catLevel,
});
} else {
this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });
}
}
//当前的拖拽节点的最新的层级
console.log("updateNodes:", this.updateNodes);
},
//递归修改子节点的层级
updateChildNodeLevel(node) {
if ((node.childNodes, length > 0)) {
for (let i = 0; i < node.childNodes.length; i++) {
var cNode = node.childNodes[i].data;
this.updateNodes.push({
catId: cNode.catId,
catLevel: node.childNodes[i].level,
});
//递归调用
this.updateChildNodeLevel(node.childNodes[i]);
}
}
},
//拖拽之后节点的位置的判断 是否的可以放在该位置上面
allowDrop(draggingNode, dropNode, type) {
//判断被拖动的当前节点以及所在的父节点的总层数不能大于三
//被拖动的当前节点的总层数
console.log("allowDrop:", draggingNode, dropNode, type);
//获取当前节点的总层数
this.countNodeLevel(draggingNode.data);
let deep = this.maxLevel - draggingNode.data.catLevel + 1;
console.log("深度:", deep);
if ((type = "inner")) {
return deep + dropNode.level <= 3;
} else {
return deep + dropNode.parent.level <= 3;
}
},
//统计当前的节点的总层数
countNodeLevel(node) {
//找到所有子节点 求出最大深度
if (node.children != null && node.children.length > 0) {
for (let i = 0; i < node.children.length; i++) {
if (node.children[i].catLevel > this.maxLevel) {
this.maxLevel = node.children[i].catLevel;
}
//递归一下
this.countNodeLevel(node.children[i]);
}
}
},
//弹出修改的对话框,并做数据的回显
edit(data) {
//修改的title
this.title = "修改分类";
//设置弹出的对话框为修改的类型
this.dialogType = "edit";
//发送请求回显数据
this.$http({
url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
method: "get",
}).then(({ data }) => {
//请求成功 回显数据
console.log("回显的数据:", data);
this.category.name = data.data.name;
this.category.catId = data.data.catId;
this.category.icon = data.data.icon;
this.category.productUnit = data.data.productUnit;
this.category.parentCid = data.data.parentCid;
});
//弹出对话框
this.dialogVisible = true;
},
//保存或者添加数据
submitDate() {
if (this.dialogType == "add") {
//调用添加的方法
this.addCategory();
}
if (this.dialogType == "edit") {
//修改的方法
this.editCategory();
}
},
//修改商品的分类信息
editCategory() {
//只发送我们要修改的数据
var { catId, name, icon, productUnit } = this.category;
this.$http({
url: this.$http.adornUrl("/product/category/update"),
method: "post",
data: this.$http.adornData({ catId, name, icon, productUnit }, false),
}).then(({ data }) => {
this.$message({
message: "分类修改成功",
type: "success",
});
//关闭弹出框
this.dialogVisible = false;
//刷新一下表单
this.getMenus();
//设置默认展开的菜单
this.expandedKey = [this.category.parentCid];
});
},
//添加分类(弹出的对话框确定按钮执行的方法)
addCategory() {
console.log("提交的数据:", this.category);
//将添加的数据返回给后端
this.$http({
url: this.$http.adornUrl("/product/category/save"),
method: "post",
data: this.$http.adornData(this.category, false),
}).then(({ data }) => {
this.$message({
message: "分类添加成功",
type: "success",
});
//刷新一下页面
this.getMenus();
//设置需要默认展开的菜单(依然展开刚才添加的节点)
this.expandedKey = [this.category.parentCid];
});
//关闭对话框
this.dialogVisible = false;
},
//添加分类的方法
append(data) {
//添加的title
this.title = "添加分类";
//设置当前提交的对话框为添加的对话框
this.dialogType = "add";
//清空一下表单的数据
this.category.name = "";
//打开添加的对话框
this.dialogVisible = true;
//为分类的对象赋值
this.category.parentCid = data.catId;
this.category.catLevel = data.catLevel * 1 + 1; //防止catLevel是一个字符串
this.catId = null;
this.category.icon = "";
this.category.productUnit = "";
this.category.sort = 0;
this.category.showStatus = 1;
},
//删除节点的方法
remove(node, data) {
var ids = [data.catId];
this.$confirm(`是否要删除[${data.name}]菜单?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
this.$http({
url: this.$http.adornUrl("/product/category/delete"),
method: "post",
data: this.$http.adornData(ids, false),
}).then(({ data }) => {
this.$message({
message: "菜单删除成功",
type: "success",
});
//刷新一下页面
this.getMenus();
//设置需要默认展开的菜单(依然展开刚才删除的节点)
this.expandedKey = [node.parent.data.catId];
});
})
.catch(() => {
this.$message({
type: "info",
message: "已取消删除",
});
});
},
//获取所有的菜单
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get",
}).then(({ data }) => {
this.menus = data.data;
});
},
},
};
</script>

<style>
</style>

老师的前端的代码

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
<template>
<div>
<el-switch v-model="draggable" active-text="开启拖拽" inactive-text="关闭拖拽"></el-switch>
<el-button v-if="draggable" @click="batchSave">批量保存</el-button>
<el-button type="danger" @click="batchDelete">批量删除</el-button>
<el-tree
:data="menus"
:props="defaultProps"
:expand-on-click-node="false"
show-checkbox
node-key="catId"
:default-expanded-keys="expandedKey"
:draggable="draggable"
:allow-drop="allowDrop"
@node-drop="handleDrop"
ref="menuTree"
>
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<el-button
v-if="node.level <=2"
type="text"
size="mini"
@click="() => append(data)"
>Append</el-button>
<el-button type="text" size="mini" @click="edit(data)">edit</el-button>
<el-button
v-if="node.childNodes.length==0"
type="text"
size="mini"
@click="() => remove(node, data)"
>Delete</el-button>
</span>
</span>
</el-tree>

<el-dialog
:title="title"
:visible.sync="dialogVisible"
width="30%"
:close-on-click-modal="false"
>
<el-form :model="category">
<el-form-item label="分类名称">
<el-input v-model="category.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="图标">
<el-input v-model="category.icon" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="计量单位">
<el-input v-model="category.productUnit" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="submitData">确 定</el-button>
</span>
</el-dialog>
</div>
</template>

<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';

export default {
//import引入的组件需要注入到对象中才能使用
components: {},
props: {},
data() {
return {
pCid: [],
draggable: false,
updateNodes: [],
maxLevel: 0,
title: "",
dialogType: "", //edit,add
category: {
name: "",
parentCid: 0,
catLevel: 0,
showStatus: 1,
sort: 0,
productUnit: "",
icon: "",
catId: null
},
dialogVisible: false,
menus: [],
expandedKey: [],
defaultProps: {
children: "children",
label: "name"
}
};
},

//计算属性 类似于data概念
computed: {},
//监控data中的数据变化
watch: {},
//方法集合
methods: {
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get"
}).then(({ data }) => {
console.log("成功获取到菜单数据...", data.data);
this.menus = data.data;
});
},
batchDelete() {
let catIds = [];
let checkedNodes = this.$refs.menuTree.getCheckedNodes();
console.log("被选中的元素", checkedNodes);
for (let i = 0; i < checkedNodes.length; i++) {
catIds.push(checkedNodes[i].catId);
}
this.$confirm(`是否批量删除【${catIds}】菜单?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
this.$http({
url: this.$http.adornUrl("/product/category/delete"),
method: "post",
data: this.$http.adornData(catIds, false)
}).then(({ data }) => {
this.$message({
message: "菜单批量删除成功",
type: "success"
});
this.getMenus();
});
})
.catch(() => {});
},
batchSave() {
this.$http({
url: this.$http.adornUrl("/product/category/update/sort"),
method: "post",
data: this.$http.adornData(this.updateNodes, false)
}).then(({ data }) => {
this.$message({
message: "菜单顺序等修改成功",
type: "success"
});
//刷新出新的菜单
this.getMenus();
//设置需要默认展开的菜单
this.expandedKey = this.pCid;
this.updateNodes = [];
this.maxLevel = 0;
// this.pCid = 0;
});
},
handleDrop(draggingNode, dropNode, dropType, ev) {
console.log("handleDrop: ", draggingNode, dropNode, dropType);
//1、当前节点最新的父节点id
let pCid = 0;
let siblings = null;
if (dropType == "before" || dropType == "after") {
pCid =
dropNode.parent.data.catId == undefined
? 0
: dropNode.parent.data.catId;
siblings = dropNode.parent.childNodes;
} else {
pCid = dropNode.data.catId;
siblings = dropNode.childNodes;
}
this.pCid.push(pCid);

//2、当前拖拽节点的最新顺序,
for (let i = 0; i < siblings.length; i++) {
if (siblings[i].data.catId == draggingNode.data.catId) {
//如果遍历的是当前正在拖拽的节点
let catLevel = draggingNode.level;
if (siblings[i].level != draggingNode.level) {
//当前节点的层级发生变化
catLevel = siblings[i].level;
//修改他子节点的层级
this.updateChildNodeLevel(siblings[i]);
}
this.updateNodes.push({
catId: siblings[i].data.catId,
sort: i,
parentCid: pCid,
catLevel: catLevel
});
} else {
this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });
}
}

//3、当前拖拽节点的最新层级
console.log("updateNodes", this.updateNodes);
},
updateChildNodeLevel(node) {
if (node.childNodes.length > 0) {
for (let i = 0; i < node.childNodes.length; i++) {
var cNode = node.childNodes[i].data;
this.updateNodes.push({
catId: cNode.catId,
catLevel: node.childNodes[i].level
});
this.updateChildNodeLevel(node.childNodes[i]);
}
}
},
allowDrop(draggingNode, dropNode, type) {
//1、被拖动的当前节点以及所在的父节点总层数不能大于3

//1)、被拖动的当前节点总层数
console.log("allowDrop:", draggingNode, dropNode, type);
//
this.countNodeLevel(draggingNode);
//当前正在拖动的节点+父节点所在的深度不大于3即可
let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;
console.log("深度:", deep);

// this.maxLevel
if (type == "inner") {
// console.log(
// `this.maxLevel:${this.maxLevel};draggingNode.data.catLevel:${draggingNode.data.catLevel};dropNode.level:${dropNode.level}`
// );
return deep + dropNode.level <= 3;
} else {
return deep + dropNode.parent.level <= 3;
}
},
countNodeLevel(node) {
//找到所有子节点,求出最大深度
if (node.childNodes != null && node.childNodes.length > 0) {
for (let i = 0; i < node.childNodes.length; i++) {
if (node.childNodes[i].level > this.maxLevel) {
this.maxLevel = node.childNodes[i].level;
}
this.countNodeLevel(node.childNodes[i]);
}
}
},
edit(data) {
console.log("要修改的数据", data);
this.dialogType = "edit";
this.title = "修改分类";
this.dialogVisible = true;

//发送请求获取当前节点最新的数据
this.$http({
url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
method: "get"
}).then(({ data }) => {
//请求成功
console.log("要回显的数据", data);
this.category.name = data.data.name;
this.category.catId = data.data.catId;
this.category.icon = data.data.icon;
this.category.productUnit = data.data.productUnit;
this.category.parentCid = data.data.parentCid;
this.category.catLevel = data.data.catLevel;
this.category.sort = data.data.sort;
this.category.showStatus = data.data.showStatus;
/**
* parentCid: 0,
catLevel: 0,
showStatus: 1,
sort: 0,
*/
});
},
append(data) {
console.log("append", data);
this.dialogType = "add";
this.title = "添加分类";
this.dialogVisible = true;
this.category.parentCid = data.catId;
this.category.catLevel = data.catLevel * 1 + 1;
this.category.catId = null;
this.category.name = "";
this.category.icon = "";
this.category.productUnit = "";
this.category.sort = 0;
this.category.showStatus = 1;
},

submitData() {
if (this.dialogType == "add") {
this.addCategory();
}
if (this.dialogType == "edit") {
this.editCategory();
}
},
//修改三级分类数据
editCategory() {
var { catId, name, icon, productUnit } = this.category;
this.$http({
url: this.$http.adornUrl("/product/category/update"),
method: "post",
data: this.$http.adornData({ catId, name, icon, productUnit }, false)
}).then(({ data }) => {
this.$message({
message: "菜单修改成功",
type: "success"
});
//关闭对话框
this.dialogVisible = false;
//刷新出新的菜单
this.getMenus();
//设置需要默认展开的菜单
this.expandedKey = [this.category.parentCid];
});
},
//添加三级分类
addCategory() {
console.log("提交的三级分类数据", this.category);
this.$http({
url: this.$http.adornUrl("/product/category/save"),
method: "post",
data: this.$http.adornData(this.category, false)
}).then(({ data }) => {
this.$message({
message: "菜单保存成功",
type: "success"
});
//关闭对话框
this.dialogVisible = false;
//刷新出新的菜单
this.getMenus();
//设置需要默认展开的菜单
this.expandedKey = [this.category.parentCid];
});
},

remove(node, data) {
var ids = [data.catId];
this.$confirm(`是否删除【${data.name}】菜单?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
this.$http({
url: this.$http.adornUrl("/product/category/delete"),
method: "post",
data: this.$http.adornData(ids, false)
}).then(({ data }) => {
this.$message({
message: "菜单删除成功",
type: "success"
});
//刷新出新的菜单
this.getMenus();
//设置需要默认展开的菜单
this.expandedKey = [node.parent.data.catId];
});
})
.catch(() => {});

console.log("remove", node, data);
}
},
//生命周期 - 创建完成(可以访问当前this实例)
created() {
this.getMenus();
},
//生命周期 - 挂载完成(可以访问DOM元素)
mounted() {},
beforeCreate() {}, //生命周期 - 创建之前
beforeMount() {}, //生命周期 - 挂载之前
beforeUpdate() {}, //生命周期 - 更新之前
updated() {}, //生命周期 - 更新之后
beforeDestroy() {}, //生命周期 - 销毁之前
destroyed() {}, //生命周期 - 销毁完成
activated() {} //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

后端的代码

1
2
3
4
5
6
7
8
/**
* 批量修改商品的分类信息
*/
@RequestMapping("/update/sort")
public R updateSort(@RequestBody CategoryEntity[] categoryEntities) {
categoryService.updateBatchById(Arrays.asList(categoryEntities));
return R.ok();
}

1.2 品牌管理

1.2.1 品牌的增删改查功能

这里前端的代码是和先前生成的后端的代码一起生成的

image-20230611111440999

商品品牌的列表显示

优化之后的前端的代码 (表头的优化 需要按钮显示的使用按钮进行显示)

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
<template>
<div class="mod-config">
<el-form
:inline="true"
:model="dataForm"
@keyup.enter.native="getDataList()"
>
<el-form-item>
<el-input
v-model="dataForm.key"
placeholder="参数名"
clearable
></el-input>
</el-form-item>
<el-form-item>
<el-button @click="getDataList()">查询</el-button>
<el-button
v-if="isAuth('product:brand:save')"
type="primary"
@click="addOrUpdateHandle()"
>新增</el-button
>
<el-button
v-if="isAuth('product:brand:delete')"
type="danger"
@click="deleteHandle()"
:disabled="dataListSelections.length <= 0"
>批量删除</el-button
>
</el-form-item>
</el-form>
<el-table
:data="dataList"
border
v-loading="dataListLoading"
@selection-change="selectionChangeHandle"
style="width: 100%"
>
<el-table-column
type="selection"
header-align="center"
align="center"
width="50"
>
</el-table-column>
<el-table-column
prop="brandId"
header-align="center"
align="center"
label="品牌id"
>
</el-table-column>
<el-table-column
prop="name"
header-align="center"
align="center"
label="品牌名"
>
</el-table-column>
<el-table-column
prop="logo"
header-align="center"
align="center"
label="品牌logo地址"
>
</el-table-column>
<el-table-column
prop="descript"
header-align="center"
align="center"
label="介绍"
>
</el-table-column>
<el-table-column
prop="showStatus"
header-align="center"
align="center"
label="显示状态"
>
<template slot-scope="scope">
<el-switch
v-model="scope.row.showStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
@change="updateBrandStatus(scope.row)"
>
</el-switch>
</template>
</el-table-column>
<el-table-column
prop="firstLetter"
header-align="center"
align="center"
label="检索首字母"
>
</el-table-column>
<el-table-column
prop="sort"
header-align="center"
align="center"
label="排序"
>
</el-table-column>
<el-table-column
fixed="right"
header-align="center"
align="center"
width="150"
label="操作"
>
<template slot-scope="scope">
<el-button
type="text"
size="small"
@click="addOrUpdateHandle(scope.row.brandId)"
>修改</el-button
>
<el-button
type="text"
size="small"
@click="deleteHandle(scope.row.brandId)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper"
>
</el-pagination>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdate"
@refreshDataList="getDataList"
></add-or-update>
</div>
</template>

<script>
import AddOrUpdate from "./brand-add-or-update";
export default {
data() {
return {
dataForm: {
key: "",
},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalPage: 0,
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false,
};
},
components: {
AddOrUpdate,
},
activated() {
this.getDataList();
},
methods: {
//修改品牌的显示的状态的
updateBrandStatus(data) {
console.log("修改的状态信息:", data);
let { brandId, showStatus } = data;
//发送修改状态的请求
this.$http({
url: this.$http.adornUrl(`/product/brand/update`),
method: "post",
data: this.$http.adornData(
{ brandId: brandId, showStatus: showStatus },
false
),
}).then(({ data }) => {
this.$message({
message: "状态更新成功",
type: "success",
});
});
},
// 获取数据列表
getDataList() {
this.dataListLoading = true;
this.$http({
url: this.$http.adornUrl("/product/brand/list"),
method: "get",
params: this.$http.adornParams({
page: this.pageIndex,
limit: this.pageSize,
key: this.dataForm.key,
}),
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataList = data.page.list;
this.totalPage = data.page.totalCount;
} else {
this.dataList = [];
this.totalPage = 0;
}
this.dataListLoading = false;
});
},
// 每页数
sizeChangeHandle(val) {
this.pageSize = val;
this.pageIndex = 1;
this.getDataList();
},
// 当前页
currentChangeHandle(val) {
this.pageIndex = val;
this.getDataList();
},
// 多选
selectionChangeHandle(val) {
this.dataListSelections = val;
},
// 新增 / 修改
addOrUpdateHandle(id) {
this.addOrUpdateVisible = true;
this.$nextTick(() => {
this.$refs.addOrUpdate.init(id);
});
},
// 删除
deleteHandle(id) {
var ids = id
? [id]
: this.dataListSelections.map((item) => {
return item.brandId;
});
this.$confirm(
`确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).then(() => {
this.$http({
url: this.$http.adornUrl("/product/brand/delete"),
method: "post",
data: this.$http.adornData(ids, false),
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.getDataList();
},
});
} else {
this.$message.error(data.msg);
}
});
});
},
},
};
</script>

后端代码(修改品牌的显示状态)

1
2
3
4
5
6
7
8
9
10
 /**
* 修改
*/
@RequestMapping("/update")
//@RequiresPermissions("product:brand:update")
public R update(@RequestBody BrandEntity brand){
brandService.updateById(brand);

return R.ok();
}

品牌添加功能

阿里云对象存储OSS | The Blog (qingling.icu)

image-20230611153418426

前端的代码

品牌管理的列表页面

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
<template>
<div class="mod-config">
<el-form
:inline="true"
:model="dataForm"
@keyup.enter.native="getDataList()"
>
<el-form-item>
<el-input
v-model="dataForm.key"
placeholder="参数名"
clearable
></el-input>
</el-form-item>
<el-form-item>
<el-button @click="getDataList()">查询</el-button>
<el-button
v-if="isAuth('product:brand:save')"
type="primary"
@click="addOrUpdateHandle()"
>新增</el-button
>
<el-button
v-if="isAuth('product:brand:delete')"
type="danger"
@click="deleteHandle()"
:disabled="dataListSelections.length <= 0"
>批量删除</el-button
>
</el-form-item>
</el-form>
<el-table
:data="dataList"
border
v-loading="dataListLoading"
@selection-change="selectionChangeHandle"
style="width: 100%"
>
<el-table-column
type="selection"
header-align="center"
align="center"
width="50"
>
</el-table-column>
<el-table-column
prop="brandId"
header-align="center"
align="center"
label="品牌id"
>
</el-table-column>
<el-table-column
prop="name"
header-align="center"
align="center"
label="品牌名"
>
</el-table-column>
<el-table-column
prop="logo"
header-align="center"
align="center"
label="品牌logo地址"
>
<template slot-scope="scope">
<img :src="scope.row.logo" style="width: 100px; height: 80px">
</template>
</el-table-column>
<el-table-column
prop="descript"
header-align="center"
align="center"
label="介绍"
>
</el-table-column>
<el-table-column
prop="showStatus"
header-align="center"
align="center"
label="显示状态"
>
<template slot-scope="scope">
<el-switch
v-model="scope.row.showStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
@change="updateBrandStatus(scope.row)"
>
</el-switch>
</template>
</el-table-column>
<el-table-column
prop="firstLetter"
header-align="center"
align="center"
label="检索首字母"
>
</el-table-column>
<el-table-column
prop="sort"
header-align="center"
align="center"
label="排序"
>
</el-table-column>
<el-table-column
fixed="right"
header-align="center"
align="center"
width="150"
label="操作"
>
<template slot-scope="scope">
<el-button
type="text"
size="small"
@click="addOrUpdateHandle(scope.row.brandId)"
>修改</el-button
>
<el-button
type="text"
size="small"
@click="deleteHandle(scope.row.brandId)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper"
>
</el-pagination>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdate"
@refreshDataList="getDataList"
></add-or-update>
</div>
</template>

<script>
import AddOrUpdate from "./brand-add-or-update";
export default {
data() {
return {
dataForm: {
key: "",
},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalPage: 0,
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false,
};
},
components: {
AddOrUpdate,
},
activated() {
this.getDataList();
},
methods: {
//修改品牌的显示的状态的
updateBrandStatus(data) {
console.log("修改的状态信息:", data);
let { brandId, showStatus } = data;
//发送修改状态的请求
this.$http({
url: this.$http.adornUrl(`/product/brand/update`),
method: "post",
data: this.$http.adornData(
{ brandId: brandId, showStatus: showStatus },
false
),
}).then(({ data }) => {
this.$message({
message: "状态更新成功",
type: "success",
});
});
},
// 获取数据列表
getDataList() {
this.dataListLoading = true;
this.$http({
url: this.$http.adornUrl("/product/brand/list"),
method: "get",
params: this.$http.adornParams({
page: this.pageIndex,
limit: this.pageSize,
key: this.dataForm.key,
}),
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataList = data.page.list;
this.totalPage = data.page.totalCount;
} else {
this.dataList = [];
this.totalPage = 0;
}
this.dataListLoading = false;
});
},
// 每页数
sizeChangeHandle(val) {
this.pageSize = val;
this.pageIndex = 1;
this.getDataList();
},
// 当前页
currentChangeHandle(val) {
this.pageIndex = val;
this.getDataList();
},
// 多选
selectionChangeHandle(val) {
this.dataListSelections = val;
},
// 新增 / 修改
addOrUpdateHandle(id) {
this.addOrUpdateVisible = true;
this.$nextTick(() => {
this.$refs.addOrUpdate.init(id);
});
},
// 删除
deleteHandle(id) {
var ids = id
? [id]
: this.dataListSelections.map((item) => {
return item.brandId;
});
this.$confirm(
`确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).then(() => {
this.$http({
url: this.$http.adornUrl("/product/brand/delete"),
method: "post",
data: this.$http.adornData(ids, false),
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.getDataList();
},
});
} else {
this.$message.error(data.msg);
}
});
});
},
},
};
</script>

品牌管理的添加和修改组件

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
<template>
<el-dialog
:title="!dataForm.brandId ? '新增' : '修改'"
:close-on-click-modal="false"
:visible.sync="visible"
>
<el-form
:model="dataForm"
:rules="dataRule"
ref="dataForm"
@keyup.enter.native="dataFormSubmit()"
label-width="140px"
>
<el-form-item label="品牌名" prop="name">
<el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
</el-form-item>
<el-form-item label="品牌logo地址" prop="logo">
<!-- <el-input v-model="dataForm.logo" placeholder="品牌logo地址"></el-input> -->
<!-- 自定义的组件 -->
<SingleUpload v-model="dataForm.logo"></SingleUpload>
</el-form-item>
<el-form-item label="介绍" prop="descript">
<el-input v-model="dataForm.descript" placeholder="介绍"></el-input>
</el-form-item>
<el-form-item label="显示状态" prop="showStatus">
<el-switch
v-model="dataForm.showStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
>
</el-switch>
</el-form-item>
<el-form-item label="检索首字母" prop="firstLetter">
<el-input
v-model="dataForm.firstLetter"
placeholder="检索首字母"
></el-input>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model.number="dataForm.sort" placeholder="排序"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
</span>
</el-dialog>
</template>

<script>
import SingleUpload from "@/components/upload/singleUpload";
export default {
components: { SingleUpload }, //声明一下组件
data() {
return {
visible: false,
dataForm: {
brandId: 0,
name: "",
logo: "",
descript: "",
showStatus: 1,
firstLetter: "",
sort: 0,
},
dataRule: {
name: [{ required: true, message: "品牌名不能为空", trigger: "blur" }],
logo: [
{ required: true, message: "品牌logo地址不能为空", trigger: "blur" },
],
descript: [
{ required: true, message: "介绍不能为空", trigger: "blur" },
],
showStatus: [
{
required: true,
message: "显示状态[0-不显示;1-显示]不能为空",
trigger: "blur",
},
],
firstLetter: [
{
validator: (rule, value, callback) => {
if (value == "") {
//检测是不是空串
callback(new Error("检索首字母不能为空"));
} else if (!/^[a-zA-Z]$/.test(value)) {
//检测输入的是不是字母
callback(new Error("首字母必须是a-z或者A-Z"));
} else {
//成功
callback();
}
},
trigger: "blur",
},
],
sort: [
{
validator: (rule, value, callback) => {
if (value === "") {
//检测是不是空串
callback(new Error("排序不能为空"));
} else if (!Number.isInteger(value) || value < 0) {
//检测输入的是不是字母
callback(new Error("排序必须为整数并且大于0"));
} else {
//成功
callback();
}
},
trigger: "blur",
},
],
},
};
},
methods: {
init(id) {
this.dataForm.brandId = id || 0;
this.visible = true;
this.$nextTick(() => {
this.$refs["dataForm"].resetFields();
if (this.dataForm.brandId) {
this.$http({
url: this.$http.adornUrl(
`/product/brand/info/${this.dataForm.brandId}`
),
method: "get",
params: this.$http.adornParams(),
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataForm.name = data.brand.name;
this.dataForm.logo = data.brand.logo;
this.dataForm.descript = data.brand.descript;
this.dataForm.showStatus = data.brand.showStatus;
this.dataForm.firstLetter = data.brand.firstLetter;
this.dataForm.sort = data.brand.sort;
}
});
}
});
},
// 表单提交
dataFormSubmit() {
this.$refs["dataForm"].validate((valid) => {
if (valid) {
this.$http({
url: this.$http.adornUrl(
`/product/brand/${!this.dataForm.brandId ? "save" : "update"}`
),
method: "post",
data: this.$http.adornData({
brandId: this.dataForm.brandId || undefined,
name: this.dataForm.name,
logo: this.dataForm.logo,
descript: this.dataForm.descript,
showStatus: this.dataForm.showStatus,
firstLetter: this.dataForm.firstLetter,
sort: this.dataForm.sort,
}),
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.visible = false;
this.$emit("refreshDataList");
},
});
} else {
this.$message.error(data.msg);
}
});
}
});
},
},
};
</script>

后端的代码

图片上传相关的后台接口

配置文件
apllication.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
alicloud:
access-key: XXXXXXXXXXXXXXXXXXXX
secret-key: XXXXXXXXXXXXXXXXXXXX
oss:
endpoint: oss-cn-beijing.aliyuncs.com
bucket: gulimall-0611 #因为是公用的配置,这个配置是自己配置的

application:
name: gulimall-third-party

server:
port: 30000

Controller

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package com.atguigu.gulimall.thirdparty.controller;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.atguigu.common.utils.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* @author Jason Gong
* @version 1.0
* @Date 2023/6/11
* @Description 阿里云对象存储的控制器方法
*/

@RestController
public class OSSController {

@Autowired
OSS ossClient;

@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@Value("${spring.cloud.alicloud.oss.bucket}")
private String bucket;
@Value("${spring.cloud.alicloud.access-key}")
private String accessId;
@Value("${spring.cloud.alicloud.secret-key}")
private String accessKey;

@RequestMapping("/oss/policy")
public R policy() {
// 填写Host地址,格式为https://bucketname.endpoint。
String host = "https://"+bucket+"."+endpoint;
// 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
//String callbackUrl = "https://192.168.0.0:8888";
// 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
String format = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
String dir = format+"/";

// 创建ossClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);
Map<String, String> respMap = null;
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);

respMap = new LinkedHashMap<String, String>();
respMap.put("accessId", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
// respMap.put("expire", formatISO8601Date(expiration));
} catch (Exception e) {
System.out.println(e.getMessage());
}
return R.ok().put("data",respMap);
}
}

配置网关,通过网关访问到该接口

1
2
3
4
5
6
- id: third_party_route
uri: lb://gulimall-third-party
predicates:
- Path=/api/thirdparty/**
filters:
- RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}

1.2.2 JSR303-数据校验

数据校验 | The Blog (qingling.icu)

1.依赖文件

1
2
3
4
5
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.7.Final</version>
</dependency>

2.在实体类上添加上校验的规则

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
package com.atguigu.gulimall.product.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;

import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;

/**
* 品牌
*
* @author JasonGong
* @email JasonGong@gmail.com
* @date 2023-05-19 00:23:36
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;

/**
* 品牌id
*/
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名必须提交") //非空的校验
private String name;
/**
* 品牌logo地址
*/
@NotEmpty
@URL(message = "logo必须是一个合法的URL地址")//URL的校验
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty
@Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母")//正则表达式的校验
private String firstLetter;
/**
* 排序
*/
@NotNull
@Min(value = 0, message = "排序必须大于等于0")//最小值是0
private Integer sort;

}

3.接口上开启检验,并自定义校验出错时的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 保存
*"@Valid" 开启校验
* @param result 检验之后响应的结果信息
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
if(result.hasErrors()){
Map<String,String> map = new HashMap<>();
//获取检验的错误结果
for (FieldError fieldError : result.getFieldErrors()) {
//获取到错误的提示
String defaultMessage = fieldError.getDefaultMessage();
//获取错误的属性的名字
String field = fieldError.getField();
map.put(field,defaultMessage);
}
return R.error(400,"提交的数据不合法").put("data",map);
}else {
brandService.save(brand);
}
return R.ok();
}

返回的json格式示例

image-20230612165021780

1.2.3 统一异常处理

统一异常处理 | The Blog (qingling.icu)

统一异常返回状态码

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
package com.atguigu.common.exception;

/**
* @author Jason Gong
* @version 1.0
* @Date 2023/6/12
* @Description 返回的状态码信息
*/
public enum BizCodeEnum {
UNKNOWN_EXCEPTION(10000,"系统未知异常"),
VALID_EXCEPTION(10001,"参数格式校验");

private Integer code;
private String msg;

BizCodeEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}

public Integer getCode() {
return code;
}

public String getMsg() {
return msg;
}
}

创建异常处理类

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
package com.atguigu.gulimall.product.exception;

import com.atguigu.common.exception.BizCodeEnum;
import com.atguigu.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

/**
* @author Jason Gong
* @version 1.0
* @Date 2023/6/12
* @Description 异常处理类
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {

/**
* 统一处理数据校验的异常
* @param e 数据校验的异常
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e){
log.error("数据校验出现问题:{},异常类型:{}",e.getMessage(),e.getClass());
Map<String,String> map = new HashMap<>();
//获取检验的错误结果
BindingResult result = e.getBindingResult();
for (FieldError fieldError : result.getFieldErrors()) {
//获取到错误的提示
String defaultMessage = fieldError.getDefaultMessage();
//获取错误的属性的名字
String field = fieldError.getField();
map.put(field,defaultMessage);
}
return R.error(BizCodeEnum.VALID_EXCEPTION.getCode(),BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data",map);
}

/**
* 捕获任意的异常
*/
@ExceptionHandler(value = Throwable.class)
public R handleException(){
return R.error(BizCodeEnum.UNKNOWN_EXCEPTION.getCode(), BizCodeEnum.UNKNOWN_EXCEPTION.getMsg());
}
}

1.2.4 关联分类功能

功能截图

image-20230618232437618

后端代码的实现

查询品牌和分类的关联关系

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 查询商品的关联分类数据
* @param brandId 品牌的id
* @return 关联的分类数据
*/
@GetMapping("/catelog/list")
public R catelogList(@RequestParam("brandId") Long brandId){
LambdaQueryWrapper<CategoryBrandRelationEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CategoryBrandRelationEntity::getBrandId,brandId);
List<CategoryBrandRelationEntity> categoryBrandRelationEntityList = categoryBrandRelationService.list(queryWrapper);
return R.ok().put("data", categoryBrandRelationEntityList);
}

新增品牌和商品的关联关系

controller

1
2
3
4
5
6
7
8
/**
* 保存商品的关联的分类数据信息
*/
@PostMapping("/save")
public R saveCategoryBrandRelation(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){
categoryBrandRelationService.saveDetail(categoryBrandRelation);
return R.ok();
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 保存商品和分类的关联数据
*
* @param categoryBrandRelation 商品和分类的关联数据表
*/
@Override
public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
//获取品牌的id和关联分类的id
Long brandId = categoryBrandRelation.getBrandId();
Long catelogId = categoryBrandRelation.getCatelogId();
//根据id查询对应的品牌名和分类名
BrandEntity brand = brandDao.selectById(brandId);
CategoryEntity category = categoryDao.selectById(catelogId);
//设置值并保存
categoryBrandRelation.setBrandName(brand.getName());
categoryBrandRelation.setCatelogName(category.getName());
this.save(categoryBrandRelation);
}

注意:这里品牌和关联分类之间的关系表中使用了冗余的字段,所以在修改品牌的时候,关系表中的字段也要修改一下

1
2
3
4
5
6
7
8
9
10
11
@Override
public void updateDetail(BrandEntity brand) {
//保证冗余字段的数据的一致性
//更新品牌表中的数据
this.updateById(brand);
if (!StringUtils.isEmpty(brand.getName())) {
//同步更新关联表中的数据
categoryBrandRelationService.updateBrand(brand.getBrandId(),brand.getName());
//TODO 更新其他的关联
}
}

关联的分类也是冗余的字段 也要同步修改

1
2
3
4
5
6
7
8
9
10
11
/**
* 级联更新所有的关联的数据
* @param category 商品分类的实体
*/
@Override
public void updateCascade(CategoryEntity category) {
//更新分类表中的数据
this.updateById(category);
//更新分类与品牌名关联表中分类的名称
categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
}

功能实现之后的截图

image-20230619111515367

1.3 平台属性

创建属性管理的菜单(通过sql导入所有的菜单,导入所有以后需要使用的菜单)

资料中的sys_menus.sql文件保存的是所有的菜单信息

image-20230616152846339

谷粒商城的接口的在线文档:https://easydoc.net/s/78237135

image-20230616153304953

Object的划分

  1. PO 持久对象

    PO就是对应数据库中某个表的一条记录,多个记录可以用PO的集合。PO中应该不包含对数据库的操作。

  2. DO 领域对象

    就是从现实世界中抽象出来的有形或者无形的业务实体。

  3. TO 数据传输对象

    不同的应用程序之间传输的对象

  4. DTO 数据传输对象

    泛指展示层与服务层之间的数据传输对象

  5. VO 值对象

    视图对象 接收页面传入过来的数据,封装对象;将业务处理完成的对象,封装成页面需要的数据

  6. BO 业务对象

  7. POJO 简单无规则的java对象

  8. DAO 数据访问对象

1.3.1 属性分组功能

实现一个分类 联动的显示相应的分组信息

抽取商品分类的组件

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
<template>
<el-tree
:data="menus"
:props="defaultProps"
node-key="catId"
ref="menuTree"
></el-tree>
</template>
<script>
export default {
components: {},
data() {
return {
menus: [],
expandedKey: [],
defaultProps: {
children: "children",
label: "name",
},
};
},
created() {
this.getMenus();
},
methods: {
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get",
}).then(({ data }) => {
console.log("商品分类数据:", data.data);
this.menus = data.data;
});
},
},
};
</script>

<style>
</style>

属性分组页面

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
<template>
<el-row :gutter="20">
<el-col :span="6"> <category></category></el-col>
<el-col :span="18">
<div class="mod-config">
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
<el-form-item>
<el-input v-model="dataForm.key" placeholder="参数名" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button @click="getDataList()">查询</el-button>
<el-button v-if="isAuth('product:attrgroup:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
<el-button v-if="isAuth('product:attrgroup:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
</el-form-item>
</el-form>
<el-table
:data="dataList"
border
v-loading="dataListLoading"
@selection-change="selectionChangeHandle"
style="width: 100%;">
<el-table-column
type="selection"
header-align="center"
align="center"
width="50">
</el-table-column>
<el-table-column
prop="attrGroupId"
header-align="center"
align="center"
label="分组id">
</el-table-column>
<el-table-column
prop="attrGroupName"
header-align="center"
align="center"
label="组名">
</el-table-column>
<el-table-column
prop="sort"
header-align="center"
align="center"
label="排序">
</el-table-column>
<el-table-column
prop="descript"
header-align="center"
align="center"
label="描述">
</el-table-column>
<el-table-column
prop="icon"
header-align="center"
align="center"
label="组图标">
</el-table-column>
<el-table-column
prop="catelogId"
header-align="center"
align="center"
label="所属分类id">
</el-table-column>
<el-table-column
fixed="right"
header-align="center"
align="center"
width="150"
label="操作">
<template slot-scope="scope">
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.attrGroupId)">修改</el-button>
<el-button type="text" size="small" @click="deleteHandle(scope.row.attrGroupId)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper">
</el-pagination>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
</div></el-col>
</el-row>
</template>
<script>
import Category from "../common/category.vue";
import AddOrUpdate from './attrgroup-add-or-update'
export default {
data () {
return {
dataForm: {
key: ''
},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalPage: 0,
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false
}
},
components: {
AddOrUpdate,Category
},
activated () {
this.getDataList()
},
methods: {
// 获取数据列表
getDataList () {
this.dataListLoading = true
this.$http({
url: this.$http.adornUrl('/product/attrgroup/list'),
method: 'get',
params: this.$http.adornParams({
'page': this.pageIndex,
'limit': this.pageSize,
'key': this.dataForm.key
})
}).then(({data}) => {
if (data && data.code === 0) {
this.dataList = data.page.list
this.totalPage = data.page.totalCount
} else {
this.dataList = []
this.totalPage = 0
}
this.dataListLoading = false
})
},
// 每页数
sizeChangeHandle (val) {
this.pageSize = val
this.pageIndex = 1
this.getDataList()
},
// 当前页
currentChangeHandle (val) {
this.pageIndex = val
this.getDataList()
},
// 多选
selectionChangeHandle (val) {
this.dataListSelections = val
},
// 新增 / 修改
addOrUpdateHandle (id) {
this.addOrUpdateVisible = true
this.$nextTick(() => {
this.$refs.addOrUpdate.init(id)
})
},
// 删除
deleteHandle (id) {
var ids = id ? [id] : this.dataListSelections.map(item => {
return item.attrGroupId
})
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$http({
url: this.$http.adornUrl('/product/attrgroup/delete'),
method: 'post',
data: this.$http.adornData(ids, false)
}).then(({data}) => {
if (data && data.code === 0) {
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
this.getDataList()
}
})
} else {
this.$message.error(data.msg)
}
})
})
}
}
}
</script>
<style>
</style>

页面效果

image-20230617173759895

父子组件中数据的传递

子组件给父组件传递的数据,事件传递,子组件给父组件发送一个事件,事件传递

子组件调整

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
<template>
<el-tree
:data="menus"
:props="defaultProps"
node-key="catId"
ref="menuTree"
@node-click="nodeclick"
></el-tree>
</template>
<script>
export default {
components: {},
data() {
return {
menus: [],
expandedKey: [],
defaultProps: {
children: "children",
label: "name",
},
};
},
created() {
this.getMenus();
},
methods: {
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get",
}).then(({ data }) => {
console.log("商品分类数据:", data.data);
this.menus = data.data;
});
},
nodeclick(data,node,component) {
console.log("子组件被点击,传递的数据有:data:",data,"node:",node,"component:",component);
//子组件向父组件传递数据
this.$emit("tree-node-click",data,node,component);
},
},
};
</script>

<style>
</style>

父组件接受相应的数据

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
<template>
<el-row :gutter="20">
<el-col :span="6">
<category @tree-node-click="treeNodeClick"></category
></el-col>
<el-col :span="18">
<div class="mod-config">
<el-form
:inline="true"
:model="dataForm"
@keyup.enter.native="getDataList()"
>
<el-form-item>
<el-input
v-model="dataForm.key"
placeholder="参数名"
clearable
></el-input>
</el-form-item>
<el-form-item>
<el-button @click="getDataList()">查询</el-button>
<el-button
v-if="isAuth('product:attrgroup:save')"
type="primary"
@click="addOrUpdateHandle()"
>新增</el-button
>
<el-button
v-if="isAuth('product:attrgroup:delete')"
type="danger"
@click="deleteHandle()"
:disabled="dataListSelections.length <= 0"
>批量删除</el-button
>
</el-form-item>
</el-form>
<el-table
:data="dataList"
border
v-loading="dataListLoading"
@selection-change="selectionChangeHandle"
style="width: 100%"
>
<el-table-column
type="selection"
header-align="center"
align="center"
width="50"
>
</el-table-column>
<el-table-column
prop="attrGroupId"
header-align="center"
align="center"
label="分组id"
>
</el-table-column>
<el-table-column
prop="attrGroupName"
header-align="center"
align="center"
label="组名"
>
</el-table-column>
<el-table-column
prop="sort"
header-align="center"
align="center"
label="排序"
>
</el-table-column>
<el-table-column
prop="descript"
header-align="center"
align="center"
label="描述"
>
</el-table-column>
<el-table-column
prop="icon"
header-align="center"
align="center"
label="组图标"
>
</el-table-column>
<el-table-column
prop="catelogId"
header-align="center"
align="center"
label="所属分类id"
>
</el-table-column>
<el-table-column
fixed="right"
header-align="center"
align="center"
width="150"
label="操作"
>
<template slot-scope="scope">
<el-button
type="text"
size="small"
@click="addOrUpdateHandle(scope.row.attrGroupId)"
>修改</el-button
>
<el-button
type="text"
size="small"
@click="deleteHandle(scope.row.attrGroupId)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="total, sizes, prev, pager, next, jumper"
>
</el-pagination>
<!-- 弹窗, 新增 / 修改 -->
<add-or-update
v-if="addOrUpdateVisible"
ref="addOrUpdate"
@refreshDataList="getDataList"
></add-or-update></div
></el-col>
</el-row>
</template>
<script>
import Category from "../common/category.vue";
import AddOrUpdate from "./attrgroup-add-or-update";
export default {
data() {
return {
catId: 0,
dataForm: {
key: "",
},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalPage: 0,
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false,
};
},
components: {
AddOrUpdate,
Category,
},
activated() {
this.getDataList();
},
methods: {
treeNodeClick(data, node, component) {
//获取子组件传递过来的值
console.log(
"获取子组件传递过来的值:data:",
data,
"node",
node,
"component:",
component
);
//查询三级分类信息 当点击三级分类id的时候 才查询相关的信息
if(node.level == 3){
this.catId = data.catId;
this.getDataList();
}
},
// 获取数据列表
getDataList() {
this.dataListLoading = true;
this.$http({
url: this.$http.adornUrl(`/product/attrgroup/list/${this.catId}`),
method: "get",
params: this.$http.adornParams({
page: this.pageIndex,
limit: this.pageSize,
key: this.dataForm.key,
}),
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataList = data.page.list;
this.totalPage = data.page.totalCount;
} else {
this.dataList = [];
this.totalPage = 0;
}
this.dataListLoading = false;
});
},
// 每页数
sizeChangeHandle(val) {
this.pageSize = val;
this.pageIndex = 1;
this.getDataList();
},
// 当前页
currentChangeHandle(val) {
this.pageIndex = val;
this.getDataList();
},
// 多选
selectionChangeHandle(val) {
this.dataListSelections = val;
},
// 新增 / 修改
addOrUpdateHandle(id) {
this.addOrUpdateVisible = true;
this.$nextTick(() => {
this.$refs.addOrUpdate.init(id);
});
},
// 删除
deleteHandle(id) {
var ids = id
? [id]
: this.dataListSelections.map((item) => {
return item.attrGroupId;
});
this.$confirm(
`确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).then(() => {
this.$http({
url: this.$http.adornUrl("/product/attrgroup/delete"),
method: "post",
data: this.$http.adornData(ids, false),
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.getDataList();
},
});
} else {
this.$message.error(data.msg);
}
});
});
},
},
};
</script>

<style>
</style>

后端代码

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
@Override
public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
//首先先判断catelogId是不是0 如果是0的话就查询所有的属性分组信息
if (catelogId == 0) {
IPage<AttrGroupEntity> page = this.page(
new Query<AttrGroupEntity>().getPage(params),
new QueryWrapper<AttrGroupEntity>()
);
return new PageUtils(page);
} else {
//查询的时候传递过来的关键词
String key = (String) params.get("key");
//SQL语句:select * from pms_attr_group where catelog_id = ? and (attr_group_id = key or attr_group_name = %key%)
LambdaQueryWrapper<AttrGroupEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AttrGroupEntity::getCatelogId, catelogId);
if (!StringUtils.isEmpty(key)) {
queryWrapper.and((obj) -> {
obj.eq(AttrGroupEntity::getAttrGroupId, key).or().like(AttrGroupEntity::getAttrGroupName, key);
});
}
IPage<AttrGroupEntity> page = this.page(
new Query<AttrGroupEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}
}

属性分组的添加功能

前端代码

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
<template>
<el-dialog
:title="!dataForm.attrGroupId ? '新增' : '修改'"
:close-on-click-modal="false"
:visible.sync="visible"
@closed="dialogClose"
>
<el-form
:model="dataForm"
:rules="dataRule"
ref="dataForm"
@keyup.enter.native="dataFormSubmit()"
label-width="100px"
>
<el-form-item label="组名" prop="attrGroupName">
<el-input
v-model="dataForm.attrGroupName"
placeholder="组名"
></el-input>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="dataForm.sort" placeholder="排序"></el-input>
</el-form-item>
<el-form-item label="描述" prop="descript">
<el-input v-model="dataForm.descript" placeholder="描述"></el-input>
</el-form-item>
<el-form-item label="组图标" prop="icon">
<el-input v-model="dataForm.icon" placeholder="组图标"></el-input>
</el-form-item>
<el-form-item label="所属分类id" prop="catelogId">
<!-- <el-input v-model="dataForm.catelogId" placeholder="所属分类id"></el-input> -->
<el-cascader
v-model="dataForm.catelogPath"
:options="categorys"
:props="props"
placeholder="试试搜索:手机"
filterable
></el-cascader>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
</span>
</el-dialog>
</template>

<script>
import Category from "../common/category.vue";

export default {
data() {
return {
props: {
value: "catId",
label: "name",
children: "children",
},
categorys: [], //所有的三级分类信息
visible: false,
dataForm: {
attrGroupId: 0,
attrGroupName: "",
sort: "",
descript: "",
icon: "",
catelogPath: [],
catelogId: 0,
},
dataRule: {
attrGroupName: [
{ required: true, message: "组名不能为空", trigger: "blur" },
],
sort: [{ required: true, message: "排序不能为空", trigger: "blur" }],
descript: [
{ required: true, message: "描述不能为空", trigger: "blur" },
],
icon: [{ required: true, message: "组图标不能为空", trigger: "blur" }],
catelogId: [
{ required: true, message: "所属分类id不能为空", trigger: "blur" },
],
},
};
},
created() {
this.getCategorys();
},
methods: {
dialogClose() {
this.dataForm.catelogPath = [];
},
getCategorys() {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get",
}).then(({ data }) => {
this.categorys = data.data;
});
},
init(id) {
this.dataForm.attrGroupId = id || 0;
this.visible = true;
this.$nextTick(() => {
this.$refs["dataForm"].resetFields();
if (this.dataForm.attrGroupId) {
this.$http({
url: this.$http.adornUrl(
`/product/attrgroup/info/${this.dataForm.attrGroupId}`
),
method: "get",
params: this.$http.adornParams(),
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataForm.attrGroupName = data.attrGroup.attrGroupName;
this.dataForm.sort = data.attrGroup.sort;
this.dataForm.descript = data.attrGroup.descript;
this.dataForm.icon = data.attrGroup.icon;
this.dataForm.catelogId = data.attrGroup.catelogId;
//查出catelog的完整路径
this.dataForm.catelogPath = data.attrGroup.catelogPath;
}
});
}
});
},
// 表单提交
dataFormSubmit() {
this.$refs["dataForm"].validate((valid) => {
if (valid) {
this.$http({
url: this.$http.adornUrl(
`/product/attrgroup/${
!this.dataForm.attrGroupId ? "save" : "update"
}`
),
method: "post",
data: this.$http.adornData({
attrGroupId: this.dataForm.attrGroupId || undefined,
attrGroupName: this.dataForm.attrGroupName,
sort: this.dataForm.sort,
descript: this.dataForm.descript,
icon: this.dataForm.icon,
catelogId:
this.dataForm.catelogPath[this.dataForm.catelogPath.length - 1],
}),
}).then(({ data }) => {
if (data && data.code === 0) {
this.$message({
message: "操作成功",
type: "success",
duration: 1500,
onClose: () => {
this.visible = false;
this.$emit("refreshDataList");
},
});
} else {
this.$message.error(data.msg);
}
});
}
});
},
},
};
</script>

后端代码

在商品分类的children上加上@JsonInclude注解,解决后面的级联选择多出一个空白的选择的问题

1
2
3
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@TableField(exist = false)//这个注解要加,因为这个属性不是数据库中的字段
private List<CategoryEntity> children;//这里的属性名不是瞎起的,与ElementUI中的显示相对应,这里改了,前端也要改

修改的时候,级联选择部分数据的回显

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 找到catelog的完整路径
* @param catelogId 路径的最后一个catelog的id
* @return 完整的路径
*/
@Override
public Long[] findCatelogPath(Long catelogId) {
List<Long> paths = new ArrayList<>();
List<Long> parentPath = findParentPath(catelogId, paths);
Collections.reverse(parentPath);
return (Long[]) parentPath.toArray(new Long[parentPath.size()]);
}
/**
* 递归找出所有的路径
*/
private List<Long> findParentPath(Long catelogId,List<Long> paths){
paths.add(catelogId);
//根据id查询相关的信息
CategoryEntity category = this.getById(catelogId);
if(category.getParentCid()!=0){
findParentPath(category.getParentCid(),paths);
}
return paths;
}

属性关联商品的规格参数和基本属性信息

功能描述

image-20230625090118059

查询商品下关联的属性分组信息

查询商品关联属性分组的后端关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 根据分组的id查询关联的所有属性
*
* @param attrgroupId 分组的id
* @return 关联的所有属性组成集合
*/
@Override
public List<AttrEntity> getRelationAttr(Long attrgroupId) {
//根据中间表查询出属性分组的id信息
List<AttrAttrgroupRelationEntity> relationEntityList = relationDao.selectList(
new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrGroupId, attrgroupId));
List<Long> attrIds = new ArrayList<>();
for (AttrAttrgroupRelationEntity relationEntity : relationEntityList) {
attrIds.add(relationEntity.getAttrId());
}
//根据属性分组的id查询属性分组的详细信息
return (List<AttrEntity>) this.listByIds(attrIds);
}

移除关联属性分组的功能后端的关键代码

service层的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 批量删除商品关联的属性分组信息
*
* @param vos 属性id和属性分组id组成的集合
*/
@Override
public void deleteRelation(AttrGroupRelationVo[] vos) {
List<AttrAttrgroupRelationEntity> entities = Arrays.asList(vos).stream().map((item) -> {
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
BeanUtils.copyProperties(item, relationEntity);
return relationEntity;
}).collect(Collectors.toList());
relationDao.deleteBatchRelation(entities);
}

dao层的代码

使用动态的sql批量的删除属性和分组的关联关系

1
2
3
4
5
6
7
<!--void deleteBatchRelation(List<AttrAttrgroupRelationEntity> entities);-->
<delete id="deleteBatchRelation">
delete from `pms_attr_attrgroup_relation` where
<foreach collection="entities" item="item" separator=" OR ">
(attr_id = #{item.attrId}) and attr_group_id = #{item.attrGroupId}
</foreach>
</delete>

查询属性分组没有关联的属性

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
/**
* 获取当前分组没有关联的所有属性
*
* @param params 分页相关的参数
* @param attrgroupId 属性分组的id
* @return 分页相关的数据
*/
@Override
public PageUtils getNoRelationAttr(Map<String, Object> params, Long attrgroupId) {
//当前的分组只能关联自己所属的分类里面的所有属性 例如手机的分类里面只能关联手机的所有属性
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupId);
Long catelogId = attrGroupEntity.getCatelogId();
//当前分组只能关联别的分组没有引用的属性
List<AttrGroupEntity> groups = attrGroupDao.selectList(
new LambdaQueryWrapper<AttrGroupEntity>().eq(AttrGroupEntity::getCatelogId, catelogId));
List<Long> collect = groups.stream().map((item) -> {
return item.getAttrGroupId();
}).collect(Collectors.toList());
List<AttrAttrgroupRelationEntity> groupId = relationDao.selectList(
new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().in(AttrAttrgroupRelationEntity::getAttrGroupId, collect));
List<Long> attrIds = groupId.stream().map((item) -> {
return item.getAttrId();
}).collect(Collectors.toList());
LambdaQueryWrapper<AttrEntity> queryWrapper =
new LambdaQueryWrapper<AttrEntity>().eq(AttrEntity::getCatelogId, catelogId).eq(AttrEntity::getAttrType,ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode());
if (attrIds != null && attrIds.size() > 0) {
queryWrapper.notIn(AttrEntity::getAttrId, attrIds);
}
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
queryWrapper.eq(AttrEntity::getAttrId, key).or().like(AttrEntity::getAttrName, key);
}
IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), queryWrapper);
return new PageUtils(page);
}

给属性关联相关的规格参数和销售属性

1
2
3
4
5
6
7
8
9
@Override
public void saveBatch(List<AttrGroupRelationVo> vos) {
List<AttrAttrgroupRelationEntity> entities = vos.stream().map((item) -> {
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
BeanUtils.copyProperties(item, relationEntity);
return relationEntity;
}).collect(Collectors.toList());
this.saveBatch(entities);
}

1.3.2 规格参数功能

功能截图

image-20230619111338153

新增功能的后端关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 保存规格参数信息
* @param attr 页面中提交的规格参数相关的信息组成的实体
*/
@Transactional
@Override
public void saveAttr(AttrVo attr) {
AttrEntity attrEntity = new AttrEntity();
BeanUtils.copyProperties(attr,attrEntity);
//保存基本的数据
this.save(attrEntity);
//保存关联关系
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
relationEntity.setAttrGroupId(attr.getAttrGroupId());
relationEntity.setAttrId(attrEntity.getAttrId());
relationDao.insert(relationEntity);
}

查询列表后端的关键代码

这里使用的stream流处理数据,没有使用多表联合查询来查询数据

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
@Override
public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId) {
LambdaQueryWrapper<AttrEntity> queryWrapper = new LambdaQueryWrapper<>();
if (catelogId != 0) {//等于0就是查询所有的分类信息下的规格参数信息
queryWrapper.eq(AttrEntity::getCatelogId, catelogId);
}
//获取检索的条件
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
queryWrapper.like(AttrEntity::getAttrName, key).or().eq(AttrEntity::getAttrId, key);
}
IPage<AttrEntity> page = this.page(
new Query<AttrEntity>().getPage(params),
queryWrapper
);
PageUtils pageUtils = new PageUtils(page);
List<AttrEntity> records = page.getRecords();
List<AttrRespVo> respVos = records.stream().map((attrEntity) -> {
AttrRespVo attrRespVo = new AttrRespVo();
BeanUtils.copyProperties(attrEntity, attrRespVo);
//设置三级商品分类名和分组名
//设置所属的商品的分类名
CategoryEntity categoryEntity = categoryDao.selectById(attrRespVo.getCatelogId());
if (categoryEntity != null) {
attrRespVo.setCatelogName(categoryEntity.getName());
}
//设置分组名(需要从中间表查询分组的id,再查询分组名)
//先从中间表查询到分组的id
AttrAttrgroupRelationEntity relationEntity = relationDao.selectOne(
new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrId, attrRespVo.getAttrId()));
//从分组的表中查询分组名
if (relationEntity != null) {
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(relationEntity.getAttrGroupId());
attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
}
return attrRespVo;
}).collect(Collectors.toList());
pageUtils.setList(respVos);
return pageUtils;
}

规格参数修改功能后端的关键代码

修改前的数据回显

查询属性的详情信息

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
/**
* 查询属性的详细信息
*
* @param attrId 属性的id
* @return 属性的详细信息封装的集合
*/
@Override
public AttrRespVo getAttrInfo(Long attrId) {
AttrRespVo attrRespVo = new AttrRespVo();
//查询属性的基本信息
AttrEntity attr = this.getById(attrId);
BeanUtils.copyProperties(attr, attrRespVo);
//查询分组id和分类的详细路径
//分组信息的设置
AttrAttrgroupRelationEntity relationEntity = relationDao.selectOne(
new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrId, attrRespVo.getAttrId()));
if (relationEntity != null) {
attrRespVo.setAttrGroupId(relationEntity.getAttrGroupId());
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(relationEntity.getAttrGroupId());
if (attrGroupEntity != null) {
attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
}
}
//设置分类信息
Long catelogId = attr.getCatelogId();
//查询分类的完整路径
Long[] catelogPath = categoryService.findCatelogPath(catelogId);
if (catelogPath != null) {
attrRespVo.setCatelogPath(catelogPath);
}
//设置分类名
CategoryEntity category = categoryDao.selectById(catelogId);
if (category != null) {
attrRespVo.setCatelogName(category.getName());
}
return attrRespVo;
}

修改的时候页面回显的效果

image-20230624192906124

修改功能后端的关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 修改属性的方法
*
* @param attr 页面传输过来的商品属性的实体
*/
@Transactional
@Override
public void updateAttr(AttrVo attr) {
//修改基本的数据
AttrEntity attrEntity = new AttrEntity();
BeanUtils.copyProperties(attr, attrEntity);
this.updateById(attrEntity);
//修改关联表的数据
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
BeanUtils.copyProperties(attr, relationEntity);
LambdaQueryWrapper<AttrAttrgroupRelationEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AttrAttrgroupRelationEntity::getAttrId, relationEntity.getAttrId());
Integer count = relationDao.selectCount(queryWrapper);
if (count > 0) {//判断是修改还是添加的操作
relationDao.update(relationEntity, queryWrapper);
} else {
relationDao.insert(relationEntity);
}
}

1.3.3 销售属性功能

分类属性列表显示

controller层的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* /product/attr/sale/list/{catelogId}
* 路径 /product/attr/base/list/{catelogId}
* 规格参数的列表显示
*
* @param params 条件查询的参数
* @param catelogId 商品分类的id
* @return 规格参数的分页数据
*/
@GetMapping("/{attrType}/list/{catelogId}")
public R baseAttrList(@RequestParam Map<String, Object> params, @PathVariable Long catelogId, @PathVariable("attrType") String type) {
log.warn("规格参数分页的查询条件:{},商品分类的id:{}", params, catelogId);
PageUtils page = attrService.queryBaseAttrPage(params, catelogId,type);
return R.ok().put("page", page);
}

service层的代码

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
@Override
public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId, String type) {
LambdaQueryWrapper<AttrEntity> queryWrapper = new LambdaQueryWrapper<>();
//路径中属性的attrType是base就是基本属性,否则就代表的是销售属性
queryWrapper.eq(AttrEntity::getAttrType, "base".equalsIgnoreCase(type) ? 1 : 0);
if (catelogId != 0) {//等于0就是查询所有的分类信息下的规格参数信息
queryWrapper.eq(AttrEntity::getCatelogId, catelogId);
}
//获取检索的条件
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
queryWrapper.like(AttrEntity::getAttrName, key).or().eq(AttrEntity::getAttrId, key);
}
IPage<AttrEntity> page = this.page(
new Query<AttrEntity>().getPage(params),
queryWrapper
);
PageUtils pageUtils = new PageUtils(page);
List<AttrEntity> records = page.getRecords();
List<AttrRespVo> respVos = records.stream().map((attrEntity) -> {
AttrRespVo attrRespVo = new AttrRespVo();
BeanUtils.copyProperties(attrEntity, attrRespVo);
//设置三级商品分类名和分组名
//设置所属的商品的分类名
CategoryEntity categoryEntity = categoryDao.selectById(attrRespVo.getCatelogId());
if (categoryEntity != null) {
attrRespVo.setCatelogName(categoryEntity.getName());
}
//设置分组名(需要从中间表查询分组的id,再查询分组名)
//先从中间表查询到分组的id
AttrAttrgroupRelationEntity relationEntity = relationDao.selectOne(
new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrId, attrRespVo.getAttrId()));
//从分组的表中查询分组名
if (relationEntity != null) {
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(relationEntity.getAttrGroupId());
if (attrGroupEntity != null) {
attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
}
}
return attrRespVo;
}).collect(Collectors.toList());
pageUtils.setList(respVos);
return pageUtils;
}

完整的功能展示

image-20230625090037616

1.4 商品维护

1.4.1 SPU管理

条件查询功能(Spu检索)

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
/**
* Spu检索功能
*
* @param params 分页相关的数据
* @return 分页对象
*/
@Override
public PageUtils queryPageByCondition(Map<String, Object> params) {
LambdaQueryWrapper<SpuInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
//关键字检索
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
//这里使用了or,为了和下面的区分开来,要使用and,将下面这个条件用括号括起来
//SELECT COUNT(1) FROM pms_spu_info WHERE (((spu_name LIKE ? OR id = ?)) AND catalog_id = ? AND brand_id = ? AND publish_status = ?)
queryWrapper.and((wrapper) -> {
wrapper.like(SpuInfoEntity::getSpuName, key).or().eq(SpuInfoEntity::getId, key);
});
}
//分类
String catelogId = (String) params.get("catelogId");
if (!StringUtils.isEmpty(catelogId)) {
queryWrapper.eq(SpuInfoEntity::getCatalogId, catelogId);
}
//品牌
String brandId = (String) params.get("brandId");
if (!StringUtils.isEmpty(brandId)) {
queryWrapper.eq(SpuInfoEntity::getBrandId, brandId);
}
//状态
String status = (String) params.get("status");
if (!StringUtils.isEmpty(status)) {
queryWrapper.eq(SpuInfoEntity::getPublishStatus, status);
}

IPage<SpuInfoEntity> page = this.page(
new Query<SpuInfoEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}

实现的功能

image-20230627102918192

前端显示时间格式的数据有问题

配置文件中的配置

1
2
3
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8

商品规格的回显

1
2
3
4
5
6
7
8
9
/**
* 查询商品的规格属性
* /product/attr/base/listforspu/{spuId}
*/
@GetMapping("/base/listforspu/{spuId}")
public R baseAttrList(@PathVariable("spuId") Long spuId){
List<ProductAttrValueEntity> entities = productAttrValueService.baseAttrListForSpu(spuId);
return R.ok().put("data",entities);
}

修改商品的规格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 修改商品的规格参数
* @param spuId 商品的id
* @param productAttrValueEntities 提交的修改之后的商品规格参数组成的集合
*/
@Transactional
@Override
public void updateSpuAttr(Long spuId, List<ProductAttrValueEntity> productAttrValueEntities) {
//1.删除这个spuId之前对应的所有属性
this.baseMapper.delete(new LambdaQueryWrapper<ProductAttrValueEntity>().eq(ProductAttrValueEntity::getSpuId,spuId));
//2.添加修改之后的商品的属性信息
List<ProductAttrValueEntity> collect = productAttrValueEntities.stream().map(item -> {
item.setSpuId(spuId);
return item;
}).collect(Collectors.toList());
this.saveBatch(collect);
}

实现的功能

image-20230628165931139

中间遇到的问题,点击规格,页面显示400的问题

image-20230628171558364

解决方案

第一步:在gulimall-admin数据库中执行以下的sql语句

1
INSERT INTO sys_menu (menu_id, parent_id, name, url, perms, type, icon, order_num) VALUES (76, 37, '规格维护', 'product/attrupdate', '', 2, 'log', 0);

第二步:将前端src/router/index.js替换成如下的内容

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/**
* 全站路由配置
*
* 建议:
* 1. 代码中路由统一使用name属性跳转(不使用path属性)
*/
import Vue from 'vue'
import Router from 'vue-router'
import http from '@/utils/httpRequest'
import { isURL } from '@/utils/validate'
import { clearLoginInfo } from '@/utils'

Vue.use(Router)

// 开发环境不使用懒加载, 因为懒加载页面太多的话会造成webpack热更新太慢, 所以只有生产环境使用懒加载
const _import = require('./import-' + process.env.NODE_ENV)

// 全局路由(无需嵌套上左右整体布局)
const globalRoutes = [
{ path: '/404', component: _import('common/404'), name: '404', meta: { title: '404未找到' } },
{ path: '/login', component: _import('common/login'), name: 'login', meta: { title: '登录' } }
]

// 主入口路由(需嵌套上左右整体布局)
const mainRoutes = {
path: '/',
component: _import('main'),
name: 'main',
redirect: { name: 'home' },
meta: { title: '主入口整体布局' },
children: [
// 通过meta对象设置路由展示方式
// 1. isTab: 是否通过tab展示内容, true: 是, false: 否
// 2. iframeUrl: 是否通过iframe嵌套展示内容, '以http[s]://开头': 是, '': 否
// 提示: 如需要通过iframe嵌套展示内容, 但不通过tab打开, 请自行创建组件使用iframe处理!
{ path: '/home', component: _import('common/home'), name: 'home', meta: { title: '首页' } },
{ path: '/theme', component: _import('common/theme'), name: 'theme', meta: { title: '主题' } },
{ path: '/demo-echarts', component: _import('demo/echarts'), name: 'demo-echarts', meta: { title: 'demo-echarts', isTab: true } },
{ path: '/demo-ueditor', component: _import('demo/ueditor'), name: 'demo-ueditor', meta: { title: 'demo-ueditor', isTab: true } },
{ path: '/product-attrupdate', component: _import('modules/product/attrupdate'), name: 'attr-update', meta: { title: '规格维护', isTab: true } }
],
beforeEnter(to, from, next) {
let token = Vue.cookie.get('token')
if (!token || !/\S/.test(token)) {
clearLoginInfo()
next({ name: 'login' })
}
next()
}
}

const router = new Router({
mode: 'hash',
scrollBehavior: () => ({ y: 0 }),
isAddDynamicMenuRoutes: false, // 是否已经添加动态(菜单)路由
routes: globalRoutes.concat(mainRoutes)
})

router.beforeEach((to, from, next) => {
// 添加动态(菜单)路由
// 1. 已经添加 or 全局路由, 直接访问
// 2. 获取菜单列表, 添加并保存本地存储
if (router.options.isAddDynamicMenuRoutes || fnCurrentRouteType(to, globalRoutes) === 'global') {
next()
} else {
http({
url: http.adornUrl('/sys/menu/nav'),
method: 'get',
params: http.adornParams()
}).then(({ data }) => {
if (data && data.code === 0) {
fnAddDynamicMenuRoutes(data.menuList)
router.options.isAddDynamicMenuRoutes = true
sessionStorage.setItem('menuList', JSON.stringify(data.menuList || '[]'))
sessionStorage.setItem('permissions', JSON.stringify(data.permissions || '[]'))
next({ ...to, replace: true })
} else {
sessionStorage.setItem('menuList', '[]')
sessionStorage.setItem('permissions', '[]')
next()
}
}).catch((e) => {
console.log(`%c${e} 请求菜单列表和权限失败,跳转至登录页!!`, 'color:blue')
router.push({ name: 'login' })
})
}
})

/**
* 判断当前路由类型, global: 全局路由, main: 主入口路由
* @param {*} route 当前路由
*/
function fnCurrentRouteType(route, globalRoutes = []) {
var temp = []
for (var i = 0; i < globalRoutes.length; i++) {
if (route.path === globalRoutes[i].path) {
return 'global'
} else if (globalRoutes[i].children && globalRoutes[i].children.length >= 1) {
temp = temp.concat(globalRoutes[i].children)
}
}
return temp.length >= 1 ? fnCurrentRouteType(route, temp) : 'main'
}

/**
* 添加动态(菜单)路由
* @param {*} menuList 菜单列表
* @param {*} routes 递归创建的动态(菜单)路由
*/
function fnAddDynamicMenuRoutes(menuList = [], routes = []) {
var temp = []
for (var i = 0; i < menuList.length; i++) {
if (menuList[i].list && menuList[i].list.length >= 1) {
temp = temp.concat(menuList[i].list)
} else if (menuList[i].url && /\S/.test(menuList[i].url)) {
menuList[i].url = menuList[i].url.replace(/^\//, '')
var route = {
path: menuList[i].url.replace('/', '-'),
component: null,
name: menuList[i].url.replace('/', '-'),
meta: {
menuId: menuList[i].menuId,
title: menuList[i].name,
isDynamic: true,
isTab: true,
iframeUrl: ''
}
}
// url以http[s]://开头, 通过iframe展示
if (isURL(menuList[i].url)) {
route['path'] = `i-${menuList[i].menuId}`
route['name'] = `i-${menuList[i].menuId}`
route['meta']['iframeUrl'] = menuList[i].url
} else {
try {
route['component'] = _import(`modules/${menuList[i].url}`) || null
} catch (e) { }
}
routes.push(route)
}
}
if (temp.length >= 1) {
fnAddDynamicMenuRoutes(temp, routes)
} else {
mainRoutes.name = 'main-dynamic'
mainRoutes.children = routes
router.addRoutes([
mainRoutes,
{ path: '*', redirect: { name: '404' } }
])
sessionStorage.setItem('dynamicMenuRoutes', JSON.stringify(mainRoutes.children || '[]'))
console.log('\n')
console.log('%c!<-------------------- 动态(菜单)路由 s -------------------->', 'color:blue')
console.log(mainRoutes.children)
console.log('%c!<-------------------- 动态(菜单)路由 e -------------------->', 'color:blue')
}
}

export default router

1.4.2 发布商品

根据商品的分类联动的查询商品的品牌

image-20230626101113941

后端关键部分的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 查询商品分类关联的品牌信息
*
* @return 某一商品分类下关联的品牌信息组成的集合
*/
@Override
public List<BrandEntity> getBrandsByCatId(Long catId) {
List<CategoryBrandRelationEntity> categoryBrandRelationEntityList = relationDao.selectList(
new LambdaQueryWrapper<CategoryBrandRelationEntity>().eq(CategoryBrandRelationEntity::getCatelogId, catId));
List<BrandEntity> brandEntityList = categoryBrandRelationEntityList.stream().map(item -> {
Long brandId = item.getBrandId();
return brandService.getById(brandId);
}).collect(Collectors.toList());
return brandEntityList;
}

根据JSON数据生成实体类

生成工具: https://www.bejson.com/

image-20230626142854091

前端提交的发布商品的详细信息

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
{
"spuName": "华为Mate30 Pro",
"spuDescription": "华为手机",
"catalogId": 225,
"brandId": 1,
"weight": 0.198,
"publishStatus": 0,
"decript": ["https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/7b9f237d-9d37-4087-83bd-13d0ea76eda4_73366cc235d68202.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/e5c4fbe5-0685-4a12-8623-a4972c623d6c_528211b97272d88a.jpg"],
"images": ["https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/7627fc94-1ac7-4406-8e1c-3866a002918b_0d40c24b264aa511.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/e5364a2e-9f46-433b-827b-33c1ff8c991c_919c850652e98031.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/997dd5dd-be60-4547-871c-23b1273df596_1f15cdbcf9e1273c.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/788f45d7-d399-4482-88db-4dd939a51f4a_2b1837c6c50add30.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/b18dd3f9-3a38-4e9a-86a0-8c24db552a09_3c24f9cd69534030.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/d19426e9-c518-4fc8-85dd-7fae758a4000_23d9fbb256ea5d4a.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/7200dec5-af25-4264-87dc-19985c4cbe23_335b2c690e43a8f8.jpg"],
"bounds": {
"buyBounds": 500,
"growBounds": 500
},
"baseAttrs": [{
"attrId": 3,
"attrValues": "2019",
"showDesc": 1
}, {
"attrId": 4,
"attrValues": "mate30pro",
"showDesc": 1
}, {
"attrId": 10,
"attrValues": "45W",
"showDesc": 1
}, {
"attrId": 11,
"attrValues": "LCD;OLED",
"showDesc": 1
}, {
"attrId": 8,
"attrValues": "16GB",
"showDesc": 1
}],
"skus": [{
"attr": [{
"attrId": 7,
"attrName": "手机颜色",
"attrValue": "星河银"
}, {
"attrId": 9,
"attrName": "商品产地",
"attrValue": "中国"
}],
"skuName": "华为Mate30 Pro 星河银 中国",
"price": "5799",
"skuTitle": "华为(HUAWEI) 华为Mate30 Pro全网通智能手机 (5G/4G)",
"skuSubtitle": "星河银的副标题",
"images": [{
"imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/7627fc94-1ac7-4406-8e1c-3866a002918b_0d40c24b264aa511.jpg",
"defaultImg": 1
}, {
"imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/e5364a2e-9f46-433b-827b-33c1ff8c991c_919c850652e98031.jpg",
"defaultImg": 0
}, {
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/788f45d7-d399-4482-88db-4dd939a51f4a_2b1837c6c50add30.jpg",
"defaultImg": 0
}, {
"imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/b18dd3f9-3a38-4e9a-86a0-8c24db552a09_3c24f9cd69534030.jpg",
"defaultImg": 0
}, {
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "",
"defaultImg": 0
}],
"descar": ["星河银", "中国"],
"fullCount": 3,
"discount": 0.98,
"countStatus": 0,
"fullPrice": 10000,
"reducePrice": 50,
"priceStatus": 0,
"memberPrice": [{
"id": 2,
"name": "铜牌会员",
"price": 5788
}, {
"id": 3,
"name": "银牌会员",
"price": 5699
}]
}, {
"attr": [{
"attrId": 7,
"attrName": "手机颜色",
"attrValue": "亮黑色"
}, {
"attrId": 9,
"attrName": "商品产地",
"attrValue": "中国"
}],
"skuName": "华为Mate30 Pro 亮黑色 中国",
"price": "6299",
"skuTitle": "华为(HUAWEI) 华为Mate30 Pro全网通智能手机 (5G/4G)",
"skuSubtitle": "亮黑色的副标题",
"images": [{
"imgUrl": "",
"defaultImg": 1
}, {
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/997dd5dd-be60-4547-871c-23b1273df596_1f15cdbcf9e1273c.jpg",
"defaultImg": 0
}, {
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/7200dec5-af25-4264-87dc-19985c4cbe23_335b2c690e43a8f8.jpg",
"defaultImg": 0
}],
"descar": ["亮黑色", "中国"],
"fullCount": 0,
"discount": 0,
"countStatus": 0,
"fullPrice": 0,
"reducePrice": 0,
"priceStatus": 0,
"memberPrice": [{
"id": 2,
"name": "铜牌会员",
"price": 0
}, {
"id": 3,
"name": "银牌会员",
"price": 0
}]
}, {
"attr": [{
"attrId": 7,
"attrName": "手机颜色",
"attrValue": "翡冷翠"
}, {
"attrId": 9,
"attrName": "商品产地",
"attrValue": "中国"
}],
"skuName": "华为Mate30 Pro 翡冷翠 中国",
"price": "6299",
"skuTitle": "华为(HUAWEI) 华为Mate30 Pro全网通智能手机 (5G/4G)",
"skuSubtitle": "翡冷翠的副标题",
"images": [{
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/997dd5dd-be60-4547-871c-23b1273df596_1f15cdbcf9e1273c.jpg",
"defaultImg": 0
}, {
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/d19426e9-c518-4fc8-85dd-7fae758a4000_23d9fbb256ea5d4a.jpg",
"defaultImg": 1
}, {
"imgUrl": "",
"defaultImg": 0
}],
"descar": ["翡冷翠", "中国"],
"fullCount": 0,
"discount": 0,
"countStatus": 0,
"fullPrice": 0,
"reducePrice": 0,
"priceStatus": 0,
"memberPrice": [{
"id": 2,
"name": "铜牌会员",
"price": 0
}, {
"id": 3,
"name": "银牌会员",
"price": 0
}]
}, {
"attr": [{
"attrId": 7,
"attrName": "手机颜色",
"attrValue": "罗兰紫"
}, {
"attrId": 9,
"attrName": "商品产地",
"attrValue": "中国"
}],
"skuName": "华为Mate30 Pro 罗兰紫 中国",
"price": "5799",
"skuTitle": "华为(HUAWEI) 华为Mate30 Pro全网通智能手机 (5G/4G)",
"skuSubtitle": "罗兰紫的副标题",
"images": [{
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/e5364a2e-9f46-433b-827b-33c1ff8c991c_919c850652e98031.jpg",
"defaultImg": 0
}, {
"imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/997dd5dd-be60-4547-871c-23b1273df596_1f15cdbcf9e1273c.jpg",
"defaultImg": 1
}, {
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "",
"defaultImg": 0
}, {
"imgUrl": "",
"defaultImg": 0
}],
"descar": ["罗兰紫", "中国"],
"fullCount": 0,
"discount": 0,
"countStatus": 0,
"fullPrice": 0,
"reducePrice": 0,
"priceStatus": 0,
"memberPrice": [{
"id": 2,
"name": "铜牌会员",
"price": 0
}, {
"id": 3,
"name": "银牌会员",
"price": 0
}]
}]
}

下载生成的实体类代码

image-20230626143051042

生成的实体类(将生成的实体类复制到项目中指定的位置)

image-20230626143228035

发布商品信息(核心业务)

后端关键代码

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/**
* 发布商品的功能
*
* @param vo 需要保存的商品信息封装的数据
*/
@Transactional
@Override
public void saveSpuInfo(SpuSaveVo vo) {
//保存spu的基本信息 pms_spu_info
SpuInfoEntity infoEntity = new SpuInfoEntity();
BeanUtils.copyProperties(vo, infoEntity);
infoEntity.setCreateTime(new Date());
infoEntity.setUpdateTime(new Date());
this.saveBaseSpuInfo(infoEntity);
//保存spu的描述图片 pms_spu_info_desc
List<String> decript = vo.getDecript();
SpuInfoDescEntity descEntity = new SpuInfoDescEntity();
descEntity.setSpuId(infoEntity.getId());
descEntity.setDecript(String.join(",", decript));
spuInfoDescService.saveSpuInfoDecript(descEntity);
//保存spu的图片集 pms_spu_images
List<String> images = vo.getImages();
spuImagesService.saveImages(infoEntity.getId(), images);
//保存spu的规格参数 pms_product_attr_value
List<BaseAttrs> baseAttrs = vo.getBaseAttrs();
List<ProductAttrValueEntity> collect = baseAttrs.stream().map(attr -> {
ProductAttrValueEntity valueEntity = new ProductAttrValueEntity();
valueEntity.setAttrId(attr.getAttrId());
AttrEntity attrEntity = attrService.getById(attr.getAttrId());
valueEntity.setAttrName(attrEntity.getAttrName());
valueEntity.setAttrValue(attr.getAttrValues());
valueEntity.setQuickShow(attr.getShowDesc());
valueEntity.setSpuId(infoEntity.getId());
return valueEntity;
}).collect(Collectors.toList());
productAttrValueService.saveProductAttr(collect);
//保存spu的积分信息
Bounds bounds = vo.getBounds();
SpuBoundTo spuBoundTo = new SpuBoundTo();
spuBoundTo.setSpuId(infoEntity.getId());
BeanUtils.copyProperties(bounds, spuBoundTo);
R r = couponFeignService.saveSpuBounds(spuBoundTo);
if (r.getCode() != 0) {
log.error("远程保存spu的积分信息失败");
}
//保存当前spu对应的sku信息
List<Skus> skus = vo.getSkus();
if (skus != null && skus.size() != 0) {
skus.forEach(item -> {
String defaultImg = "";
for (Images img : item.getImages()) {
if (img.getDefaultImg() == 1) {
defaultImg = img.getImgUrl();
}
}
SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
BeanUtils.copyProperties(item, skuInfoEntity);
skuInfoEntity.setBrandId(infoEntity.getBrandId());
skuInfoEntity.setCatalogId(infoEntity.getCatalogId());
skuInfoEntity.setSaleCount(0L);
skuInfoEntity.setSpuId(infoEntity.getId());
skuInfoEntity.setSkuDefaultImg(defaultImg);
// 保存sku的基本信息pms_sku_info
skuInfoService.saveSkuInfo(skuInfoEntity);
Long skuId = skuInfoEntity.getSkuId();
List<SkuImagesEntity> imagesEntities = item.getImages().stream().map(img -> {
SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
skuImagesEntity.setSkuId(skuId);
skuImagesEntity.setImgUrl(img.getImgUrl());
skuImagesEntity.setDefaultImg(img.getDefaultImg());
return skuImagesEntity;
}).filter(entity -> {
//返回true就添加上去
return !StringUtils.isEmpty(entity.getImgUrl());
}).collect(Collectors.toList());
// 保存sku的图片信息pms_sku_images
skuImagesService.saveBatch(imagesEntities);
// sku的销售属性信息pms_sku_sale_attr_value
List<Attr> attrs = item.getAttr();
List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attrs.stream().map(attr -> {
SkuSaleAttrValueEntity skuSaleAttrValueEntity = new SkuSaleAttrValueEntity();
BeanUtils.copyProperties(attr, skuSaleAttrValueEntity);
skuSaleAttrValueEntity.setSkuId(skuId);
return skuSaleAttrValueEntity;
}).collect(Collectors.toList());
skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);
// sku的优惠信息、满减等信息gulimall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_member_price
SkuReductionTo skuReductionTo = new SkuReductionTo();
BeanUtils.copyProperties(item, skuReductionTo);
List<MemberPrice> memberPrices = item.getMemberPrice().stream().map(memberPrice -> {
MemberPrice memberPriceTo = new MemberPrice();
BeanUtils.copyProperties(memberPrice, memberPriceTo);
return memberPriceTo;
}).collect(Collectors.toList());
skuReductionTo.setMemberPrice(memberPrices);
skuReductionTo.setSkuId(skuId);
if (skuReductionTo.getFullCount() > 0 || skuReductionTo.getFullPrice().compareTo(new BigDecimal("0")) == 1) {
R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
if (r1.getCode() != 0) {
log.error("远程调用sku的优惠信息、满减信息失败");
}
}
});
}
}

最终的实现效果

image-20230626184303186

image-20230626182832804

1.4.3 商品管理

Sku检索

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
/**
* 检索条件
* {
* page: 1,//当前页码
* limit: 10,//每页记录数
* sidx: 'id',//排序字段
* order: 'asc/desc',//排序方式
* key: '华为',//检索关键字
* catelogId: 0,
* brandId: 0,
* min: 0,
* max: 0
* }
*/
@Override
public PageUtils queryPageByCondition(Map<String, Object> params) {
LambdaQueryWrapper<SkuInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
//Preparing: SELECT sku_id,sku_name,sku_title,catalog_id,sale_count,price,brand_id,sku_default_img,sku_subtitle,sku_desc,spu_id
// FROM pms_sku_info
// WHERE (( (sku_id = ? OR sku_name LIKE ?) ) AND catalog_id = ? AND brand_id = ? AND price >= ? AND price <= ?) LIMIT ?,?
//检索关键字
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
queryWrapper.and((wrapper) -> {
wrapper.eq(SkuInfoEntity::getSkuId, key).or().like(SkuInfoEntity::getSkuName, key);
});
}
//分类
String catelogId = (String) params.get("catelogId");
if (!StringUtils.isEmpty(catelogId) && !"0".equalsIgnoreCase(catelogId)) {
queryWrapper.eq(SkuInfoEntity::getCatalogId, catelogId);
}
//品牌
String brandId = (String) params.get("brandId");
if (!StringUtils.isEmpty(brandId) && !"0".equalsIgnoreCase(brandId)) {
queryWrapper.eq(SkuInfoEntity::getBrandId, brandId);
}
//价格区间
String min = (String) params.get("min");
if (!StringUtils.isEmpty(min)) {
queryWrapper.ge(SkuInfoEntity::getPrice, min);
}
String max = (String) params.get("max");
if (!StringUtils.isEmpty(max)) {
try {
BigDecimal bigDecimal = new BigDecimal(max);
if(bigDecimal.compareTo(new BigDecimal("0"))==1){//说明大于0
queryWrapper.le(SkuInfoEntity::getPrice, max);
}
} catch (Exception e) {
log.error("com/atguigu/gulimall/product/service/impl/SkuInfoServiceImpl");
e.printStackTrace();
throw new RuntimeException(e);
}
}
IPage<SkuInfoEntity> page = this.page(
new Query<SkuInfoEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}

实现的效果

image-20230627112218894

2.库存系统

2.1 仓库维护

仓库的分页加条件查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public PageUtils queryPage(Map<String, Object> params) {

//分页查询的条件
LambdaQueryWrapper<WareInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
queryWrapper.like(WareInfoEntity::getName, key).or()
.eq(WareInfoEntity::getId, key)
.or().like(WareInfoEntity::getAddress, key)
.or().like(WareInfoEntity::getAreacode, key);
}
IPage<WareInfoEntity> page = this.page(
new Query<WareInfoEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}

实现的效果

image-20230627153518692

2.2 库存工作单

2.3 商品库存

商品库存的条件查询

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
/**
* {
* page: 1,//当前页码
* limit: 10,//每页记录数
* sidx: 'id',//排序字段
* order: 'asc/desc',//排序方式
* wareId: 123,//仓库id
* skuId: 123//商品id
* }
*/
@Override
public PageUtils queryPage(Map<String, Object> params) {
LambdaQueryWrapper<WareSkuEntity> queryWrapper = new LambdaQueryWrapper<>();
String wareId = (String) params.get("wareId");
if (!StringUtils.isEmpty(wareId)) {
queryWrapper.eq(WareSkuEntity::getWareId, wareId);
}
String skuId = (String) params.get("skuId");
if (!StringUtils.isEmpty(skuId)) {
queryWrapper.eq(WareSkuEntity::getSkuId, skuId);
}
IPage<WareSkuEntity> page = this.page(
new Query<WareSkuEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}

实现的效果

image-20230627160605717

2.4 采购单维护

2.4.1 采购需求

采购需求的分页加条件查询

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
/**
* status: 0,//状态
* wareId: 1,//仓库id
*/
@Override
public PageUtils queryPage(Map<String, Object> params) {
LambdaQueryWrapper<PurchaseDetailEntity> queryWrapper = new LambdaQueryWrapper<>();
String status = (String) params.get("status");
if (!StringUtils.isEmpty(status)) {
queryWrapper.eq(PurchaseDetailEntity::getStatus, status);
}
String wareId = (String) params.get("wareId");
if (!StringUtils.isEmpty(wareId)) {
queryWrapper.eq(PurchaseDetailEntity::getWareId, wareId);
}
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
queryWrapper.and((wrapper->{
wrapper.eq(PurchaseDetailEntity::getPurchaseId, key).or().like(PurchaseDetailEntity::getSkuId,key);
}));
}
IPage<PurchaseDetailEntity> page = this.page(
new Query<PurchaseDetailEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}

实现的效果

image-20230627161955351

合并采购单

业务需求介绍

image-20230627162707377

获取没有领取的采购单,将采购需求合并到这个采购单

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 获取没有领取的采购单
*/
@Override
public PageUtils queryPageUnreceivePurchase(Map<String, Object> params) {
IPage<PurchaseEntity> page = this.page(
new Query<PurchaseEntity>().getPage(params),
new LambdaQueryWrapper<PurchaseEntity>().eq(PurchaseEntity::getStatus,0).or().eq(PurchaseEntity::getStatus,1)
);

return new PageUtils(page);
}

合并采购需求

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
/**
* 合并采购需求
*
* @param mergeVo 前端传递的需要合并的采购需求
*/
@Transactional
@Override
public void mergePurchase(MergeVo mergeVo) {
//判断采购单是不是存在
Long purchaseId = mergeVo.getPurchaseId();
if (purchaseId == null) {//说明没有选择需要合并的采购单,需要先创建一个采购单
PurchaseEntity purchaseEntity = new PurchaseEntity();
purchaseEntity.setStatus(WareConstant.PurchaseStatesEnum.CREATED.getCode());
purchaseEntity.setCreateTime(new Date());
purchaseEntity.setUpdateTime(new Date());
this.save(purchaseEntity);
purchaseId = purchaseEntity.getId();
}
List<Long> items = mergeVo.getItems();
Long finalPurchaseId = purchaseId;
List<PurchaseDetailEntity> detailEntityList = items.stream().map(item -> {
PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();
detailEntity.setId(item);
detailEntity.setPurchaseId(finalPurchaseId);
detailEntity.setStatus(WareConstant.PurchaseDeatilStatesEnum.ASSIGNED.getCode());
return detailEntity;
}).collect(Collectors.toList());
detailService.updateBatchById(detailEntityList);
//更新一下时间
PurchaseEntity purchaseEntity = new PurchaseEntity();
purchaseEntity.setId(purchaseId);
purchaseEntity.setUpdateTime(new Date());
this.updateById(purchaseEntity);
}

2.4.2 采购单

领取采购单

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
/**
* @param ids 采购单id
*/
@Override
public void received(List<Long> ids) {
//确认当前的采购单是新建或者分配状态
List<PurchaseEntity> purchaseEntities = ids.stream().map(id -> {
PurchaseEntity byId = this.getById(id);
return byId;
}).filter(item -> {
if (item.getStatus() == WareConstant.PurchaseStatesEnum.CREATED.getCode() ||
item.getStatus() == WareConstant.PurchaseDeatilStatesEnum.ASSIGNED.getCode()) {
return true;
}
return false;
}).map(item->{
//改变采购单状态
item.setStatus(WareConstant.PurchaseStatesEnum.RECEIVE.getCode());
item.setUpdateTime(new Date());
return item;
}).collect(Collectors.toList());
this.updateBatchById(purchaseEntities);
//改变采购项状态
purchaseEntities.forEach(item->{
List<PurchaseDetailEntity> entities = detailService.listDetailByPurchaseId(item.getId());
List<PurchaseDetailEntity> detailEntityList = entities.stream().map(entity -> {
PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();
purchaseDetailEntity.setId(entity.getId());
purchaseDetailEntity.setStatus(WareConstant.PurchaseDeatilStatesEnum.BUYING.getCode());
return purchaseDetailEntity;
}).collect(Collectors.toList());
detailService.updateBatchById(detailEntityList);
});
}

完成采购的功能

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
@Transactional
@Override
public void done(PurchaseDoneVo doneVo) {
Long id = doneVo.getId();
//改变采购项的状态
Boolean flag = true;
List<PurchaseItemDoneVo> items = doneVo.getItems();
List<PurchaseDetailEntity> purchaseItemDoneVos = new ArrayList<>();
for (PurchaseItemDoneVo item : items) {
PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();
if (item.getStatus() == WareConstant.PurchaseDeatilStatesEnum.HASERROR.getCode()) {
flag = false;
detailEntity.setStatus(item.getStatus());
} else {
detailEntity.setStatus(WareConstant.PurchaseDeatilStatesEnum.FINISH.getCode());
//将成功采购的进行入库
PurchaseDetailEntity purchaseDetailEntity = detailService.getById(item.getItemId());
wareSkuService.addStock(purchaseDetailEntity.getSkuId(),purchaseDetailEntity.getWareId(),purchaseDetailEntity.getSkuNum());
}
detailEntity.setId(item.getItemId());
purchaseItemDoneVos.add(detailEntity);
}
detailService.updateBatchById(purchaseItemDoneVos);
//改变采购单的状态
PurchaseEntity purchaseEntity = new PurchaseEntity();
purchaseEntity.setId(id);
purchaseEntity.setStatus(flag ? WareConstant.PurchaseStatesEnum.FINISH.getCode() : WareConstant.PurchaseStatesEnum.HASERROR.getCode());
purchaseEntity.setUpdateTime(new Date());
this.updateById(purchaseEntity);
}

修改库存的功能

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
@Transactional
@Override
public void addStock(Long skuId, Long wareId, Integer skuNum) {
//判断是添加的操作还是修改的操作
List<WareSkuEntity> wareSkuEntities = wareSkuDao.selectList(new LambdaQueryWrapper<WareSkuEntity>().eq(WareSkuEntity::getSkuId, skuId).eq(WareSkuEntity::getWareId, wareId));
if (wareSkuEntities == null || wareSkuEntities.size() == 0) {
WareSkuEntity wareSkuEntity = new WareSkuEntity();
wareSkuEntity.setSkuId(skuId);
wareSkuEntity.setWareId(wareId);
wareSkuEntity.setStock(skuNum);
wareSkuEntity.setStockLocked(0);
//远程查询sku的名字
try {
R r = productFeignService.info(skuId);
if(r.getCode() == 0){
Map<String,Object> skuInfo = (Map<String, Object>) r.get("skuInfo");
wareSkuEntity.setSkuName((String) skuInfo.get("skuName"));
}
} catch (Exception e) {
log.warn("远程获取商品的Sku信息失败!");
}
wareSkuDao.insert(wareSkuEntity);
} else {
wareSkuDao.addStock(skuId, wareId, skuNum);
}
}

sql语句

1
2
3
4
5
6
7
 <!--void addStock(@Param("skuId") Long skuId, @Param("wareId") Long wareId, @Param("skuNum") Integer skuNum);-->
<insert id="addStock">
update `wms_ware_sku`
set stock = stock + #{skuNum}
where sku_id = #{skuId}
and ware_id = #{wareId}
</insert>

3.基础篇总结

image-20230628170218776

个人总结:

  • 细节很重要,编写代码的时候要注意细节问题(比如网关的配置,模糊路径和精确路径先后配置的问题)
  • 要有良好的编码习惯,注意代码解耦和代码复用
  • 业务逻辑很重要,编码之前要搞懂了业务逻辑之后再开始写代码
  • 学习技术的时候要融会贯通,学习编程的思想
  • 学技术一定要学会看开发文档(技术说明书),可以先从开发文档提供的demo开始

六.高级篇程序设计

1.ElasticSearch

详细的教程

ElasticSearch | The Blog (qingling.icu)

2.商城业务

2.1 商品上架

1.商品Mapping

创建一个product索引和指定映射关系

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
68
69
70
71
PUT product
{
"mappings": {
"properties": {
"skuId": {
"type": "long"
},
"spuId": {
"type": "keyword"
},
"skuTitle": {
"type": "text",
"analyzer": "ik_smart"
},
"skuPrice": {
"type": "keyword"
},
"skuImg": {
"type": "keyword",
"index": false,
"doc_values": false
},
"saleCount": {
"type": "long"
},
"hasStock": {
"type": "boolean"
},
"hotScore": {
"type": "long"
},
"brandId": {
"type": "long"
},
"catalogId": {
"type": "long"
},
"brandName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"brandImg": {
"type": "keyword",
"index": false,
"doc_values": false
},
"catalogName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"attrs": {
"type": "nested",
"properties": {
"attrId": {
"type": "long"
},
"attrName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"attrValue": {
"type": "keyword"
}
}
}
}
}
}

2.上架功能后端代码

所需的实体类

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
package com.atguigu.common.to.es;

import lombok.Data;

import java.math.BigDecimal;
import java.util.List;

/**
* @author Jason Gong
* @version 1.0
* @Date 2023/7/5
* @Description 对应ES中保存的文档的实体类
*/
@Data
public class SkuEsModel {
private Long skuId;

private Long spuId;

private String skuTitle;

private BigDecimal skuPrice;

private String skuImg;

private Long saleCount;

private Boolean hasStock;

private Long hotScore;

private Long brandId;

private Long catalogId;

private String brandName;

private String brandImg;

private String catalogName;

private List<Attrs> attrs;

@Data
public static class Attrs {

private Long attrId;

private String attrName;

private String attrValue;

}

}

商品上架的后端关键代码

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
68
69
70
71
72
73
74
75
76
/**
* 商品上架的功能
*/
@Override
public void up(Long spuId) {
//查询当前spuId对应的所有sku信息
List<SkuInfoEntity> skus = skuInfoService.getSkusBySpuId(spuId);
List<Long> skuIds = skus.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());
//规格属性信息的封装(可以被检索的规格属性信息)
List<ProductAttrValueEntity> baseAttrs = productAttrValueService.baseAttrListForSpu(spuId);
List<Long> attrIds = baseAttrs.stream().map(attr -> {
return attr.getAttrId();
}).collect(Collectors.toList());
//获取可以检索的属性id的集合
List<Long> searchAttrIds = attrService.selectSearchAttrs(attrIds);
//将其转换成set集合
Set<Long> idSet = new HashSet<>(searchAttrIds);
//最终需要封装的attrs
List<SkuEsModel.Attrs> attrs = baseAttrs.stream().filter(item -> {
return idSet.contains(item.getAttrId());
}).map(item -> {
SkuEsModel.Attrs skuAttr = new SkuEsModel.Attrs();
BeanUtils.copyProperties(item, skuAttr);
return skuAttr;
}).collect(Collectors.toList());
//库存
Map<Long, Boolean> stockMap = null;
try {
R r = wareFeignService.getSkusHasStock(skuIds);
List<SkuHasStockVo> skuHasStockVos = r.getData(new TypeReference<List<SkuHasStockVo>>(){});
stockMap = skuHasStockVos.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));
} catch (Exception e) {
log.error("库存服务查询异常:原因{}",e);
}
//封装sku的信息
Map<Long, Boolean> finalStockMap = stockMap;
List<SkuEsModel> upProducts = skus.stream().map(sku -> {
//组装需要的数据
SkuEsModel skuEsModel = new SkuEsModel();
BeanUtils.copyProperties(sku, skuEsModel);
//价格 图片
skuEsModel.setSkuPrice(sku.getPrice());
skuEsModel.setSkuImg(sku.getSkuDefaultImg());
//设置库存信息
if(finalStockMap == null){
skuEsModel.setHasStock(true);
}else {
skuEsModel.setHasStock(finalStockMap.get(sku.getSkuId()));
}
//热度评分信息
skuEsModel.setHotScore(0L);
//品牌名 分类名 品牌价格
BrandEntity brand = brandService.getById(sku.getBrandId());
skuEsModel.setBrandName(brand.getName());
skuEsModel.setBrandImg(brand.getLogo());
CategoryEntity category = categoryService.getById(sku.getCatalogId());
skuEsModel.setCatalogName(category.getName());
//设置attrs
skuEsModel.setAttrs(attrs);
return skuEsModel;
}).collect(Collectors.toList());
R r = searchFeignService.productStatusUp(upProducts);
if(r.getCode() == 0){
//远程调用成功
spuInfoDao.updateSpuStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode());
}else {
//远程调用失败
//TODO 重复调用的问题
/**
* feign的调用流程
* 1.构造请求的数据,将对象转化成json
* 2.发送请求进行执行(执行成功之后会解码响应数据)
* 3.执行请求会有重试机制
*/
}
}
1
2
3
4
5
6
7
8
9
10
/**
* 过滤出属性id中可以被检索的属性的id
* @param attrIds 原属性id的集合
* @return 可以被检索的属性id的集合
*/
@Override
public List<Long> selectSearchAttrs(List<Long> attrIds) {
//select attr_id from `pms_attr` where attr_id in (?) and search_type=1
return attrDao.selectSearchAttrs(attrIds);
}
1
2
3
4
5
6
7
<select id="selectSearchAttrs" resultType="java.lang.Long">
select `attr_id`from `pms_attr` where `attr_id` in
<foreach collection="attrIds" item="attr" separator="," open="(" close=")">
#{attr}
</foreach>
and `search_type`=1
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 查询库存的方法
*/
@Override
public List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds) {
List<SkuHasStockVo> skuHasStockVos = skuIds.stream().map(skuId -> {
SkuHasStockVo skuHasStockVo = new SkuHasStockVo();
Long count = wareSkuDao.getSkuStock(skuId);
skuHasStockVo.setSkuId(skuId);
if(count != null){
skuHasStockVo.setHasStock(count > 0);
}else {
skuHasStockVo.setHasStock(false);
}
return skuHasStockVo;
}).collect(Collectors.toList());
return skuHasStockVos;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 存储商品数据
*/
@Override
public Boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {
//将数据保存到ES中
//在ES中建立好索引
BulkRequest bulkRequest = new BulkRequest();
for (SkuEsModel skuEsModel : skuEsModels) {
IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
indexRequest.id(skuEsModel.getSkuId().toString());
indexRequest.source(JSON.toJSONString(skuEsModel), XContentType.JSON);
bulkRequest.add(indexRequest);
}
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
boolean hasFailure = bulk.hasFailures();
List<String> ids = Arrays.stream(bulk.getItems()).map(item -> {
return item.getId();
}).collect(Collectors.toList());
log.info("上架商品的id:{}",ids);
return !hasFailure;
}

2.2 首页

项目的页面视图部署在对应的微服务项目的静态资源中

image-20230706112521327

2.2.1整合thymeleaf

添加thymeleaf的依赖

1
2
3
4
5
<!-- thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

将资料中的静态资源放在微服务工程的对应位置,并在配置文件中加入对应的配置

image-20230706114304695

首页页面

image-20230706145050684

在index.html上加上thymeleaf的名称空间

1
<html xmlns:th="http://www.thymeleaf.org">

修改页面的时候需要频繁的重启项目,使用热更新来解决该问题

导入devtools的依赖

1
2
3
4
5
6
<!--devtools-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>

重启项目之后会显示如下的效果

image-20230706154112026

这时我们在每次修改页面之后使用Ctrl+Shift+F9键可以热更新

image-20230706161401528

2.2.2 首页面三级分类显示

首页面的跳转以及一级分类的显示

1
2
3
4
5
6
7
8
9
10
/**
* 首页面的跳转
*/
@GetMapping({"/","index.html"})
public String indexPage(Model model){
//查询所有的一级分类
List<CategoryEntity> categorys = categoryService.getLevelOneCategorys();
model.addAttribute("categorys",categorys);
return "index";
}

用于用户端首页面三级分类封装的数据

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
package com.atguigu.gulimall.product.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
* @author Jason Gong
* @version 1.0
* @website https://qingling.icu
* @Date 2023/7/7
* @Description 用户端首页面三级分类封装的数据
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Catelog2Vo {

/**
* 一级父分类的id
*/
private String catalog1Id;

/**
* 三级子分类
*/
private List<Category3Vo> catalog3List;

private String id;

private String name;


/**
* 三级分类vo
* 静态内部类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Category3Vo {

/**
* 父分类、二级分类id
*/
private String catalog2Id;

private String id;

private String name;
}
}

前端相关的修改

1
2
3
 <li th:each=" category:${categorys}">
<a href="#" class="header_main_left_a" ctg-data="3" th:attr="ctg-data=${category.catId}"><b th:text="${category.name}"></b></a>
</li>

image-20230707151505310

controller层代码

1
2
3
4
5
6
7
8
/**
* 首页面的商品分分类信息
*/
@ResponseBody
@GetMapping("/index/catalog.json")
public Map<String, List<Catelog2Vo>> getCatalogJson(){
return categoryService.getCatalogJson();
}

service层代码

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
@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {
//查出所有的一级分类
List<CategoryEntity> levelOneCategorys = getLevelOneCategorys();
//封装数据
Map<String, List<Catelog2Vo>> catelog2VoMap = levelOneCategorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//查询以及分类下的二级分类
List<CategoryEntity> categoryEntities = categoryDao.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, v.getCatId()));
List<Catelog2Vo> catelog2Vos = null;
if (categoryEntities != null) {
catelog2Vos = categoryEntities.stream().map(item -> {
Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, item.getCatId().toString(), item.getName());
//当前二级分类的三级分类
List<CategoryEntity> levelThreeCatelog = categoryDao.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, item.getCatId()));
if(levelThreeCatelog != null){
//封装成指定的数据
List<Catelog2Vo.Category3Vo> category3VoList = levelThreeCatelog.stream().map(threeCatelog -> {
Catelog2Vo.Category3Vo category3Vo = new Catelog2Vo.Category3Vo();
category3Vo.setCatalog2Id(item.getCatId().toString());
category3Vo.setName(threeCatelog.getName());
category3Vo.setId(threeCatelog.getCatId().toString());
return category3Vo;
}).collect(Collectors.toList());
catelog2Vo.setCatalog3List(category3VoList);
}
return catelog2Vo;
}).collect(Collectors.toList());
}
return catelog2Vos;
}));
return catelog2VoMap;
}

页面实现的效果

image-20230707151543593

2.3 搭建域名访问环境

1.安装SwitchHosts软件

可以快捷的编辑操作hosts文件

打开的时候以管理员的身份打开

image-20230707153628394

image-20230707154052952

在添加的方案中添加如下的内容,点击右下角的保存按钮保存(保存失败的更改hosts文件的权限,将只读勾选掉)

1
192.168.195.100 gulimall.com

保存成功之后可以直接访问域名,解析到虚拟机的ip

Tips:有科学上网的测试的时候要先关闭科学上网的软件

安装了ES的访问gulimall.com:9200可以看到相应的内容

image-20230707160328001

2.正式搭建项目的域名访问环境

image-20230708092745456

nginx的配置文件

image-20230708093600923

配置nginx

1
2
3
4
5
6
7
8
#切换到nginx挂载在本机的配置文件所在的目录
cd /mydata/nginx/conf
#切换到nginx的分配置文件下复制一份 定义gulimall的配置,主配置文件中有include /etc/nginx/conf.d/*.conf;这么一段配置,主配置文件
#会包含conf.d目录下的所有配置
cd /mydata/nginx/conf/conf.d
cp default.conf gulimall.conf 复制一份
#在gulimall.conf配置文件中配置我们的配置
#配置如下配置

niginx的配置文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
listen 80;
server_name gulimall.com;

location / {
#服务的IP 192.168.0.112
#服务的端口: 10000
proxy_pass http://192.168.0.112:10000;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

这时我们在访问gulimall.com的时候就可以访问一下的页面了

image-20230708095940126

3.让nginx代理到网关

在nginx.conf配置文件中加上如下的配置

1
2
3
4
5
6
#配置上游服务器
#gulimall是上游服务器的组名
upstream gulimall{
#网关的地址
server 192.168.0.112:88;
}

修改gulimall.conf中的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 80;
server_name gulimall.com;

location / {
proxy_pass http://gulimall;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

在网关的配置文件中加上一下的配置

1
2
3
4
5
# nginx代理相关的断言
- id: gulimall_host_route
uri: lb://gulimall-product
predicates:
- Host=**.gulimall.com,gulimall.com

这时我们重启网关的时候发现无法访问到相应的页面 是由于nginx访问网关的时候丢了host

这时我们需要加上如下的配置

1
proxy_set_header Host $host;

image-20230708104321024

image-20230708104848501

2.4 性能压测

压力测试与性能监控 | The Blog (qingling.icu)

JMeter压力测试

image-20230708152102009

性能监控

1
2
jconsole
jvisualvm

image-20230709155555491

2.4.1优化中间件对性能的影响

网关的优化、数据库索引的优化、thymeleaf的优化、静态资源的优化、nginx动静分离

image-20230710111805705

2.5 缓存使用

2.5.1 本地缓存与分布式缓存

为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而 db 承担数据落盘工作。
哪些数据适合放入缓存?
1.即时性、数据一致性要求不高的
2.访问量大且更新频率不高的数据(读多,写少)

本地缓存

image-20230710143906286

分布式缓存

image-20230710143341531

2.5.2 整合redis

1.安装Redis

image-20230710144052877

2.项目中使用Redis

SpringBoot整合Redis | The Blog (qingling.icu)

2.1 引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.2 添加配置

1
2
3
4
spring:
redis:
host: 192.168.195.100
port: 6379

2.3 测试使用

1
2
3
4
5
6
7
8
9
10
@Autowired
private StringRedisTemplate stringRedisTemplate;

@Test
void testRedis(){
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
ops.set("hello","word_"+ UUID.randomUUID().toString());
String hello = ops.get("hello");
System.out.println(hello);
}

image-20230710145648233

lettuce堆外内存溢出bug

产生原因:

1)、springboot2.0以后默认使用 lettuce作为操作redis的客户端,它使用netty进行网络通信

2)、lettuce的bug导致netty堆外内存溢出。netty如果没有指定堆外内存,默认使用Xms的值,可以使用-Dio.netty.maxDirectMemory进行设置

解决方案:由于是lettuce的bug造成,不要直接使用-Dio.netty.maxDirectMemory去调大虚拟机堆外内存,治标不治本。

  • 1)、升级lettuce客户端
  • 2)、切换使用jedis

修改依赖文件,使用jedis作为客户端工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

2.5.3 高并发下缓存失效问题

缓存穿透

image-20230710153635804

缓存雪崩

image-20230710153651752

缓存击穿

image-20230710153709464

分布式下如何加锁?

image-20230710163735955

锁-时序问题

image-20230710165738655

分布式锁演进-基本原理

image-20230714163437984

分布式锁的最终形态

image-20230714171221296

2.5.4 分布式锁-Redisson

github地址: https://github.com/redisson/redisson

1.引入依赖

1
2
3
4
5
6
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>

2.配置redisson

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
package com.atguigu.gulimall.product.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

/**
* @author Jason Gong
* @version 1.0
* @website https://qingling.icu
* @Date 2023/7/14
* @Description Redisson的配置
*/
@Configuration
public class MyRedissonConfig {

/**
* 所有对redisson的操作通过RedissonClient
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() throws IOException {
//创建配置
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.195.100:6379");
//根据配置config创建RedissonClient实例
return Redisson.create(config);
}
}

3.测试使用

1
2
3
4
5
6
7
@Autowired
private RedissonClient redissonClient;

@Test
void testRedisson(){
System.out.println(redissonClient);
}

image-20230714175205635

4.简单的使用分布式锁

上一个线程加锁之后没有解锁的话,后面的线程会一直等待前面的线程释放锁,自己再加锁。

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
@ResponseBody
@GetMapping("/hello")
public String hello() {
//打印时间需要
SimpleDateFormat sdf = new SimpleDateFormat();// 格式化时间
sdf.applyPattern("yyyy-MM-dd HH:mm:ss");// a为am/pm的标记
Date date = new Date();// 获取当前时间

//获取同一把锁,只要锁的名字一样,就是同一把锁
RLock lock = redissonClient.getLock("my-lock");
//加锁
lock.lock();//阻塞式等待,锁没有释放的情况下会一直等待下去
//1.锁的自动续期,如果业务耗费的时间超长,运行时会自动给锁续上新的30s(默认时续期30s),不必担心业务时间长,锁自动过期被删掉
//2.加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s以后自动s
System.out.println("现在时间:" + sdf.format(date));
try {
//执行业务
System.out.println("加锁成功,执行业务,线程Id:"+Thread.currentThread().getId());
Thread.sleep(10000);//等待10秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
//解锁
System.out.println("释放锁,线程Id:"+Thread.currentThread().getId());
lock.unlock();
}
return "hello";
}

image-20230714181620263

my-lock对应的值会跟随线程的变化而变化

image-20230714182057982

读写锁

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
/**
* 写锁没有释放的情况下,读锁必须等待写锁的释放,才能进行读的操作
* 读写锁的测试
* 写锁
*/
@ResponseBody
@GetMapping("/write")
public String writeValue(){
RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
String s = "";
RLock rLock = lock.writeLock();//写锁
try {
rLock.lock();
s = UUID.randomUUID().toString();
Thread.sleep(30000);
//写的操作
redisTemplate.opsForValue().set("writeValue",s);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
rLock.unlock();
}
return s;
}

/**
* 读写锁的测试
* 读锁
*/
@GetMapping("/read")
@ResponseBody
public String readValue(){
RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
String s = "";
RLock rLock = lock.readLock();//读锁
try {
rLock.lock();
//读的操作
s = redisTemplate.opsForValue().get("writeValue");
} catch (Exception e) {
e.printStackTrace();
}finally {
rLock.unlock();
}
return s;
}

2.5.5 分布式缓存一致性问题

双写模式

image-20230716100845389

失效模式

image-20230716100909465

缓存数据一致性-解决方案

image-20230716101939869

缓存数据一致性-解决-Canal

image-20230716102510738

2.5.6 缓存-SpringCache

1、简介

  • Spring 从 3.1 开始定义了 org.springframework.cache.Cache和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术;并支持使用 JCache(JSR-107)注解简化我们开发;

  • Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;
    Cache 接 口 下 Spring 提 供 了 各 种 xxxCache 的 实 现 ; 如 RedisCache , EhCacheCache ,ConcurrentMapCache 等;

  • 每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

  • 使用 Spring 缓存抽象时我们需要关注以下两点;

    1、确定方法需要被缓存以及他们的缓存策略

    2、从缓存中读取之前缓存存储的数据

2.以前的笔记教程

SpringBoot整合Redis | The Blog (qingling.icu)

3.项目中整合SpringCache简化开发

3.1.引入依赖

1
2
3
4
5
<!--我们使redis作为缓存的使用场景,在前面我们还要引入redis的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

3.2.编写配置文件

1
2
#使用缓存的类型
spring.cache.type=redis

3.3.测试使用缓存

常用的缓存注解

@Cacheable 触发将数据保存到缓存的操作

@CachePut 不影响方法执行更新缓存

@CacheEvict 触发将数据从缓存中删除的操作

@Caching 组合多个缓存操作

@CacheConfig 在类级别上共享缓存的相关配置

1.在启动类上添加缓存的注解

1
@EnableCaching //开启缓存功能

2.测试使用

1
2
3
4
5
6
//每一个需要缓存的数据需要我们指定放到那个名字的缓存[缓存的分区(按照业务类型分)]
@Cacheable({"category"}) //代表当前方法的结果需要缓存,如果缓存中有,方法就不需要调用了,如果缓存中没有,就会调用方法,将方法的结果放入到缓存
@Override
public List<CategoryEntity> getLevelOneCategorys() {
return categoryDao.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, 0));
}

image-20230716112105848

自定义数据的存储

设置自定义的key值

1
@Cacheable(value = {"category"},key = "'levelOneCategorys'")  //自定义key值

tips: #root.method.name是SpEL表达式

1
@Cacheable(value = {"category"},key = "#root.method.name")   //使用方法名作为key值

设置缓存数据的存活时间

1
2
#设置一个小时的存活时间
spring.cache.redis.time-to-live=3600000

image-20230716152821097

将数据保存为json的格式

添加配置类

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
package com.atguigu.gulimall.product.config;

import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
* @author Jason Gong
* @version 1.0
* @website https://qingling.icu
* @Date 2023/7/16
* @Description 缓存的序列化的配置
*/
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
public class MyCacheConfig {
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
//让配置文件中所有的配置生效(下面的不配置的话,缓存的时间会失效)
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}

效果

image-20230716160027391

其它的常用配置

1
2
3
4
5
6
#缓存的前缀,如果指定了前缀就使用指定的前缀,没有指定前缀就使用缓存的名字作为前缀
spring.cache.redis.key-prefix=CACHE_
#是否使用缓存的前缀
spring.cache.redis.use-key-prefix=true
#是否缓存空值(缓存穿透的问题的解决)
spring.cache.redis.cache-null-values=true

2.6 检索服务

2.6.1 整合页面

image-20230716173751202

2.6.2 配置域名访问

image-20230716173453604

Nginx的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 80;
server_name *.gulimall.com;

location / {
proxy_set_header Host $host;
proxy_pass http://gulimall;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}

}

网关中配置断言

1
2
3
4
5
#搜索
- id: gulimall_search_route
uri: lb://gulimall-search
predicates:
- Host=search.gulimall.com

image-20230716173628024

2.6.3 首页和搜索页相互跳转

首页无法跳转到搜索页的原因,gumall改为gulimall即可

image-20230716181052580

搜索框跳转到搜索页,修改页面中的代码

image-20230717094656565

2.6.4 商品的检索

image-20230717095740594

检索条件的VO

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
package com.atguigu.gulimall.search.vo;

import lombok.Data;

import java.util.List;

/**
* @author Jason Gong
* @version 1.0
* @website https://qingling.icu
* @Date 2023/7/17
* @Description 检索条件的vo
*/
@Data
public class SearchParam {
/**
* 页面传递过来的全文匹配关键字
*/
private String keyword;

/**
* 品牌id,可以多选
*/
private List<Long> brandId;

/**
* 三级分类id
*/
private Long catalog3Id;

/**
* 排序条件:sort=price/salecount/hotscore_desc/asc
*/
private String sort;

/**
* 是否显示有货
*/
private Integer hasStock;

/**
* 价格区间查询
*/
private String skuPrice;

/**
* 按照属性进行筛选
*/
private List<String> attrs;

/**
* 页码
*/
private Integer pageNum = 1;

/**
* 原生的所有查询条件
*/
private String _queryString;
}

检索的结果

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package com.atguigu.gulimall.search.vo;

import com.atguigu.common.to.es.SkuEsModel;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

/**
* @author Jason Gong
* @version 1.0
* @website https://qingling.icu
* @Date 2023/7/17
* @Description 根据检索条件返回给页面的商品信息
*/
@Data
public class SearchResult {


/**
* 查询到的所有商品信息
*/
private List<SkuEsModel> product;


/**
* 当前页码
*/
private Integer pageNum;

/**
* 总记录数
*/
private Long total;

/**
* 总页码
*/
private Integer totalPages;

private List<Integer> pageNavs;

/**
* 当前查询到的结果,所有涉及到的品牌
*/
private List<BrandVo> brands;

/**
* 当前查询到的结果,所有涉及到的所有属性
*/
private List<AttrVo> attrs;

/**
* 当前查询到的结果,所有涉及到的所有分类
*/
private List<CatalogVo> catalogs;

private List<NavVo> navs;

@Data
public static class NavVo {
private String navName;
private String navValue;
private String link;
}


@Data
public static class BrandVo {

private Long brandId;

private String brandName;

private String brandImg;
}


@Data
public static class AttrVo {

private Long attrId;

private String attrName;

private List<String> attrValue;
}


@Data
public static class CatalogVo {

private Long catalogId;

private String catalogName;
}
}

未完待续(学习到177)……