一.项目简介 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.项目架构图 项目的架构图
微服务划分图
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的方式完成远程调用
4.负载均衡
分布式系统中,A服务需要调用B服务,B服务在多台机器上都存在,A调用任意一个服务均可完成功能。为了使每一个服务器都不要太忙或者太闲,我们可以使用负载均衡的调用每一个服务器,提升网站的健壮性。
常见的负载均衡算法:
轮询: 为第一个请求选择健康池中的第一台后端服务器,然后按顺序往后依次选择,直到最后一个,然后循环。
最小连接: 有限选择连接数量少,也就是压力最小的后端服务器,在会话较长的情况下可以考虑采取这种方式。
随机法: 通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。
源地址哈希法: 源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。
加权轮询法: 不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。
加权随机法: 与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。
5.服务注册|发现|注册中心
6.配置中心 每一个服务最终会有大量的配置,并且每个服务都可能部署在多台机器上。我们经常需要变更配置,我们可以让每个服务在配置中心获取自己的配置。
7.服务熔断和服务降级 在微服务的架构中,微服务之间通过网络进行通信,存在相互依赖,当其中的一个服务不可用时,有可能会造成雪崩效应。要防止这样的情况,必须要有容错机制来保护服务。
服务熔断: 设置服务的超时,当被调用的服务经常失败到达某个阈值,我们可以开启短路保护机制,后来的请求不再去调用这个服务。本地直接返回默认的数据。
服务降级: 在运维期间,当系统处于高峰期,系统资源紧张,我们可以让非核心的业务降级运行。降级:某些业务不处理,或者简单处理[抛异常、返回NULL、调用mock数据、调用Fallback处理逻辑]。
8.API网关 在微服务的架构中,API Gateway作为整体架构的重要组件,他抽象了微服务中都需要的公共功能,同时提供了客户端的负载均衡,服务自动熔断,灰度发布、统一认证、限流流控、日志统计等丰富功能,帮助我们解决很多API管理难题。
三.环境搭建 1.安装虚拟机 需要使用虚拟机安装相关的软件和部署集群
VMWare虚拟机安装Linux教程 | The Blog (qingling.icu)
将Linxu的IP地址固定下来
Linux设置静态IP | The Blog (qingling.icu)
2.Docker安装与配置 包含docker安装、开启开机自启动、配置镜像加速服务
Docker容器化技术 | The Blog (qingling.icu)
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插件:
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
常用命令
7.从Gitee上初始化一个项目 我们是先从码云上初始化一个项目 然后拉取到本地
创建一个仓库
复制刚才创建仓库的地址
将工程导入到IDEA中
导入成功
8.创建微服务模块
9.数据库设计
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中
同时创建好这个脚手架需要的数据库
修改配置文件中数据库账号密码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中,并安装依赖
下载完成之后,运行前端的项目
运行成功过之后访问 http://localhost:8001
成功的页面显示如下
**使用账号:admin密码:admin 登录 **
可以登录成功并显示以下的页面说明前后端的联调通过
11.快速开发-逆向工程的搭建 1.代码生成器快速使用案例 人人开源代码生成器的地址:https://gitee.com/renrenio/renren-generator.git
集成代码生成器
修改代码生成器的配置文件
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 =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 访问
4.使用生成代码的功能生成代码
解压之后的文件如下
压缩包中的文件如下所示
由于项目中没有使用shiro安全框架,我们注释掉安全框架的注解生成模板
配置每个模块的配置文件
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 global-config: db-config: id-type: auto
测试生成的代码
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 =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 spring: main: allow-circular-references: true datasource: type: com.alibaba.druid.pool.DruidDataSource 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 jackson: time-zone: GMT+8 date-format: yyyy-MM-dd HH:mm:ss web: resources: static-locations: classpath:/static/,classpath:/views/ mybatis-plus: mapperLocations: classpath:mapper/**/*.xml pagehelper: reasonable: true supportMethodsArguments: true params: count=countSql 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 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,即分布式事务解决方案
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注册中心
简介
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的服务
在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 通过访问: http://localhost:8848/nacos
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)); }
用户模块远程调用优惠卷模块
远程调用别的服务端步骤
引入open-feign的依赖
调用端编写一个接口,告诉SpringCloud这个接口需要调用远程服务,
声明接口的每一个方法都是调用哪个远程服务的那个请求
开启远程调用的功能,在调用端的启动类上添加**@EnableFeignClients**注解
在用户模块创建一个Feign包(维护的时候见名知义),声明调用的接口,在启动类上添加注解
1 2 3 @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;@FeignClient("gulimall-coupon") 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 <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 ,显示如下的内容,说明远程调用成功
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 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); }
测试的结果如下:
3.3 需求:应用不重启,实时的修改配置文件中的值
3.3.1 在Nacos配置中心中创建一个名为 应用名.properties (例如:gulimall-coupon.properties) 的配置文件
进入nacos管理的可视化页面,点击配置列表 ,点击 + ,新建一个配置 ,同时将配置文件中name值改为lihua
发布之后,就可以在配置列表中看到这个配置了
重启项目测试,配置中心的配置是否生效
没有生效的话,添加这个依赖,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了
3.3.2 想让配置文件实时生效的话,这时我们还要添加以下的注解,然后重启应用,让注解生效
注意: @RefreshScope 这里的注解添加在Controller 上,不是添加在启动类上
最终测试
这时我们在配置中心中修改配置并发布,这些配置就可以实时的生效了
我们在配置中心修改name和age的值,并发布
这时刷新请求接口的页面,我们可以看到页面中的数据发生了实时的变化
总结
实现nacos配置中心统一配置的流程
引入依赖
1 2 3 4 <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-config</artifactId > </dependency >
创建一个bootstrap.properties的配置文件,添加如下的内容:
1 2 3 4 5 spring.application.name =gulimall-coupon spring.cloud.nacos.config.server-addr =127.0.0.1:8848
需要给配置中心中新建一个数据集(Data Id)为 应用名.properties (例如:gulimall-coupon.properties)的配置。默认规则: 应用名.properties
给 应用名.properties 添加任何配置 这些配置都可以在配置中心配置生效
在Controller上添加**@RefreshScope**注解就可以实时的动态刷新配置,配置中心更新了配置并发布了之后,配置实时的生效。
注意
如果配置中心和当前应用的配置文件都配置了相同的项,优先使用配置中心的配置
细节
命名空间: 配置隔离
默认:public(保留空间)
1.可以分别设置开发、测试、生产环境的命名空间,不同的环境下使用不同的命名空间中的配置
通过命令空间实现环境隔离
在bootstrap.properties 设置使用哪个命名空间,可以在配置文件中添加如下的配置
1 2 3 4 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网关
简介
网关最为流量的入口,常用的功能包括**路由转发**、**权限校验**、**限流控制**等。SpringCloud gateway是springcloud官方提供的第二代网关框架,取代了Zull网关。
网关的功能
针对所有请求进行登陆统一鉴权(登录态) 、限流 、缓存 、日志(用户打点) 。
可以根据不同的请求路径pattern,来进行请求的鉴权、转发、和拒绝。
协议转化 。针对后段多种不同的协议,在网关层统一处理后以HTTP对外提供服务。
提供统一的错误码 。
请求转发,并且可以基于网关实现内网与外网的隔离 。
使用
创建一个springboot initializr的工程 ,勾选上Gateway的依赖
在网关模块的pom.xml文件中添加上对common工程的依赖
在网关的启动类上添加上**@EnableDiscoveryClient**的注解,把网关注册到注册中心
配置nacos的地址和服务名信息
1 2 3 4 spring.cloud.nacos.config.server-addr =127.0.0.1:8848 spring.application.name =gulimall-gateway
创建bootstrap.properties 配置文件,将网关模块的配置通过交给nacos配置中心管理
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
启动测试
启动之后出现的问题及解决方案:
a .单元测试@Test注解爆红,是我们更换了springboot的版本的原因,删除爆红的地方,重新导包即可
b .数据库相关的报错,是因为网关中没有使用数据库,就没有配置数据库的配置,我们通过@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})解决。
转发测试
测试案例: 我们在浏览器的地址栏输入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: baidu_route uri: https://www.baidu.com predicates: - Query=url,baidu - id: qq_route uri: https://www.qq.com predicates: - Query=url,qq
2.前端开发的基础知识 前端基础知识 | The Blog (qingling.icu)
前后端技术类比
五.基础篇程序设计 提示 :所有的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 -> 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; } private List<CategoryEntity> getChildren (CategoryEntity root, List<CategoryEntity> all) { List<CategoryEntity> children = all.stream().filter(categoryEntity -> { return categoryEntity.getParentCid().equals(root.getCatId()); }).map(categoryEntity -> { categoryEntity.setChildren(getChildren(categoryEntity, all)); return categoryEntity; }).sorted((menu1, menu2) -> { return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort()); }).collect(Collectors.toList()); return children; } }
1.1.2 配置路由和网关和路径重写 前端访问的地址
前端代码中的配置
修改访问的基准路径,统一访问网关,再由网关做请求的转发,转发到指定的服务中去(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 = {}; window .SITE_CONFIG ['baseUrl' ] = 'http://localhost:88' ; 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 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.2.6.RELEASE</version > <relativePath /> </parent > <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: admin_route uri: lb://renren-fast predicates: - Path=/api/** filters: - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
解决登录的时候的跨域问题
浏览器中的跨域的报错信息
跨域问题的介绍
跨域流程
文档介绍
跨域的解决方案
使用nginx部署为同一域
配置当前请求允许跨域 (在网关中通过过滤器给请求添加响应头)
跨域的配置
添加跨域的配置类
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;@Configuration public class CorsConfig { @Bean public CorsWebFilter corsWebFilter () { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource (); CorsConfiguration corsConfiguration = new CorsConfiguration (); corsConfiguration.addAllowedHeader("*" ); corsConfiguration.addAllowedMethod("*" ); corsConfiguration.addAllowedOriginPattern("*" ); 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 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 {}
再次测试,成功的进入后台管理系统
1.2.3 查询-页面中树形显示 新增一个商品系统的目录,并在该目录下创建一个分类维护的二级分类
1.添加一个一级分类
2.在商品系统的一级分类下添加一个分类维护的二级分类
3.路径的问题
4.路径与文件的对应关系
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: admin_route uri: lb://renren-fast predicates: - Path=/api/** filters: - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
前端发送请求,正确地获取数据
在页面上显示数据
Element的配置
前端代码
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>
显示的效果
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 logic-not-delete-value: 0
在showStatus上添加上逻辑删除的注解
1 @TableLogic(value = "1",delval = "0")
后端代码的实现
controlller
1 2 3 4 5 6 7 8 9 10 11 @RequestMapping("/delete") public R delete (@RequestBody Long[] catIds) { categoryService.removeMenuByIds(Arrays.asList(catIds)); return R.ok(); }
service 这里后面需要优化
1 2 3 4 5 6 7 8 9 10 @Override public void removeMenuByIds (List<Long> list) { 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") public R save (@RequestBody CategoryEntity category) { categoryService.save(category); return R.ok(); }
实现的效果
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 品牌的增删改查功能 这里前端的代码是和先前生成的后端的代码一起生成的
商品品牌的列表显示
优化之后的前端的代码 (表头的优化 需要按钮显示的使用按钮进行显示)
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") public R update (@RequestBody BrandEntity brand) { brandService.updateById(brand); return R.ok(); }
品牌添加功能
阿里云对象存储OSS | 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 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;@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 () { String host = "https://" +bucket+"." +endpoint; String format = new SimpleDateFormat ("yyyy/MM/dd" ).format(new Date ()); String dir = format+"/" ; 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 )); } 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.*;@Data @TableName("pms_brand") public class BrandEntity implements Serializable { private static final long serialVersionUID = 1L ; @TableId private Long brandId; @NotBlank(message = "品牌名必须提交") private String name; @NotEmpty @URL(message = "logo必须是一个合法的URL地址") private String logo; private String descript; private Integer showStatus; @NotEmpty @Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母") private String firstLetter; @NotNull @Min(value = 0, message = "排序必须大于等于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 @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格式示例
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;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;@Slf4j @RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller") public class GulimallExceptionControllerAdvice { @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 关联分类功能 功能截图
后端代码的实现
查询品牌和分类的关联关系
1 2 3 4 5 6 7 8 9 10 11 12 @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 @Override public void saveDetail (CategoryBrandRelationEntity categoryBrandRelation) { Long brandId = categoryBrandRelation.getBrandId(); Long catelogId = categoryBrandRelation.getCatelogId(); 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()); } }
关联的分类也是冗余的字段 也要同步修改
1 2 3 4 5 6 7 8 9 10 11 @Override public void updateCascade (CategoryEntity category) { this .updateById(category); categoryBrandRelationService.updateCategory(category.getCatId(),category.getName()); }
功能实现之后的截图
1.3 平台属性 创建属性管理的菜单(通过sql导入所有的菜单,导入所有以后需要使用的菜单)
资料中的sys_menus.sql文件保存的是所有的菜单信息
谷粒商城的接口的在线文档:https://easydoc.net/s/78237135
Object的划分
PO 持久对象
PO就是对应数据库中某个表的一条记录,多个记录可以用PO的集合。PO中应该不包含对数据库的操作。
DO 领域对象
就是从现实世界中抽象出来的有形或者无形的业务实体。
TO 数据传输对象
不同的应用程序之间传输的对象
DTO 数据传输对象
泛指展示层与服务层之间的数据传输对象
VO 值对象
视图对象 接收页面传入过来的数据,封装对象;将业务处理完成的对象,封装成页面需要的数据
BO 业务对象
POJO 简单无规则的java对象
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>
页面效果
父子组件中数据的传递
子组件给父组件传递的数据,事件传递,子组件给父组件发送一个事件,事件传递
子组件调整
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) { 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" ); 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; 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;
修改的时候,级联选择部分数据的回显
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; }
属性关联商品的规格参数和基本属性信息
功能描述
查询商品下关联的属性分组信息
查询商品关联属性分组的后端关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public List<AttrEntity> getRelationAttr (Long attrgroupId) { 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()); } return (List<AttrEntity>) this .listByIds(attrIds); }
移除关联属性分组的功能后端的关键代码
service层的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @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 <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 @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 规格参数功能 功能截图
新增功能的后端关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @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 ) { 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()); } 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 @Override public AttrRespVo getAttrInfo (Long attrId) { AttrRespVo attrRespVo = new AttrRespVo (); AttrEntity attr = this .getById(attrId); BeanUtils.copyProperties(attr, attrRespVo); 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; }
修改的时候页面回显的效果
修改功能后端的关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @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 @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 <>(); queryWrapper.eq(AttrEntity::getAttrType, "base" .equalsIgnoreCase(type) ? 1 : 0 ); if (catelogId != 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()); } 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; }
完整的功能展示
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 @Override public PageUtils queryPageByCondition (Map<String, Object> params) { LambdaQueryWrapper<SpuInfoEntity> queryWrapper = new LambdaQueryWrapper <>(); String key = (String) params.get("key" ); if (!StringUtils.isEmpty(key)) { 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); }
实现的功能
前端显示时间格式的数据有问题
配置文件中的配置
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 @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 @Transactional @Override public void updateSpuAttr (Long spuId, List<ProductAttrValueEntity> productAttrValueEntities) { this .baseMapper.delete(new LambdaQueryWrapper <ProductAttrValueEntity>().eq(ProductAttrValueEntity::getSpuId,spuId)); List<ProductAttrValueEntity> collect = productAttrValueEntities.stream().map(item -> { item.setSpuId(spuId); return item; }).collect(Collectors.toList()); this .saveBatch(collect); }
实现的功能
中间遇到的问题,点击规格,页面显示400的问题
解决方案
第一步:在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 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 )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 : [ { 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 ) => { 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' }) }) } }) 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' } 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 : '' } } 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 发布商品 根据商品的分类联动的查询商品的品牌
后端关键部分的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @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/
前端提交的发布商品的详细信息
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 } ] } ] }
下载生成的实体类代码
生成的实体类(将生成的实体类复制到项目中指定的位置)
发布商品信息(核心业务)
后端关键代码
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 @Transactional @Override public void saveSpuInfo (SpuSaveVo vo) { SpuInfoEntity infoEntity = new SpuInfoEntity (); BeanUtils.copyProperties(vo, infoEntity); infoEntity.setCreateTime(new Date ()); infoEntity.setUpdateTime(new Date ()); this .saveBaseSpuInfo(infoEntity); List<String> decript = vo.getDecript(); SpuInfoDescEntity descEntity = new SpuInfoDescEntity (); descEntity.setSpuId(infoEntity.getId()); descEntity.setDecript(String.join("," , decript)); spuInfoDescService.saveSpuInfoDecript(descEntity); List<String> images = vo.getImages(); spuImagesService.saveImages(infoEntity.getId(), images); 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); 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的积分信息失败" ); } 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); 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 -> { return !StringUtils.isEmpty(entity.getImgUrl()); }).collect(Collectors.toList()); skuImagesService.saveBatch(imagesEntities); 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); 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的优惠信息、满减信息失败" ); } } }); } }
最终的实现效果
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 @Override public PageUtils queryPageByCondition (Map<String, Object> params) { LambdaQueryWrapper<SkuInfoEntity> queryWrapper = new LambdaQueryWrapper <>(); 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 ){ 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); }
实现的效果
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); }
实现的效果
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 @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); }
实现的效果
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 @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); }
实现的效果
合并采购单
业务需求介绍
获取没有领取的采购单,将采购需求合并到这个采购单
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 @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 @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 ); 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 <insert id ="addStock" > update `wms_ware_sku` set stock = stock + #{skuNum} where sku_id = #{skuId} and ware_id = #{wareId} </insert >
3.基础篇总结
个人总结:
细节很重要,编写代码的时候要注意细节问题(比如网关的配置,模糊路径和精确路径先后配置的问题)
要有良好的编码习惯,注意代码解耦和代码复用
业务逻辑很重要,编码之前要搞懂了业务逻辑之后再开始写代码
学习技术的时候要融会贯通,学习编程的思想
学技术一定要学会看开发文档(技术说明书),可以先从开发文档提供的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;@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) { 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()); List<Long> searchAttrIds = attrService.selectSearchAttrs(attrIds); Set<Long> idSet = new HashSet <>(searchAttrIds); 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); } 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()); 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 { } }
1 2 3 4 5 6 7 8 9 10 @Override public List<Long> selectSearchAttrs (List<Long> attrIds) { 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 { 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 首页 项目的页面视图部署在对应的微服务项目的静态资源中
2.2.1整合thymeleaf 添加thymeleaf的依赖
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-thymeleaf</artifactId > </dependency >
将资料中的静态资源放在微服务工程的对应位置,并在配置文件中加入对应的配置
首页页面
在index.html上加上thymeleaf的名称空间
1 <html xmlns:th ="http://www.thymeleaf.org" >
修改页面的时候需要频繁的重启项目,使用热更新来解决该问题
导入devtools的依赖
1 2 3 4 5 6 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <optional > true</optional > </dependency >
重启项目之后会显示如下的效果
这时我们在每次修改页面之后使用Ctrl+Shift+F9键可以热更新
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;@Data @AllArgsConstructor @NoArgsConstructor public class Catelog2Vo { private String catalog1Id; private List<Category3Vo> catalog3List; private String id; private String name; @Data @AllArgsConstructor @NoArgsConstructor public static class Category3Vo { 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 >
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; }
页面实现的效果
2.3 搭建域名访问环境 1.安装SwitchHosts软件
可以快捷的编辑操作hosts文件
打开的时候以管理员的身份打开
在添加的方案中添加如下的内容,点击右下角的保存按钮保存(保存失败的更改hosts文件的权限,将只读勾选掉)
1 192.168.195.100 gulimall.com
保存成功之后可以直接访问域名,解析到虚拟机的ip
Tips:有科学上网的测试的时候要先关闭科学上网的软件
安装了ES的访问gulimall.com:9200可以看到相应的内容
2.正式搭建项目的域名访问环境
nginx的配置文件
配置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的时候就可以访问一下的页面了
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 - id: gulimall_host_route uri: lb://gulimall-product predicates: - Host=**.gulimall.com,gulimall.com
这时我们重启网关的时候发现无法访问到相应的页面 是由于nginx访问网关的时候丢了host
这时我们需要加上如下的配置
1 proxy_set_header Host $host;
2.4 性能压测 压力测试与性能监控 | The Blog (qingling.icu)
JMeter压力测试
性能监控
2.4.1优化中间件对性能的影响 网关的优化、数据库索引的优化、thymeleaf的优化、静态资源的优化、nginx动静分离
2.5 缓存使用 2.5.1 本地缓存与分布式缓存 为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而 db 承担数据落盘工作。哪些数据适合放入缓存? 1.即时性、数据一致性要求不高的 2.访问量大且更新频率不高的数据(读多,写少)
本地缓存
分布式缓存
2.5.2 整合redis 1.安装Redis
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); }
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 高并发下缓存失效问题 缓存穿透
缓存雪崩
缓存击穿
分布式下如何加锁?
锁-时序问题
分布式锁演进-基本原理
分布式锁的最终形态
2.5.4 分布式锁-Redisson github地址: https://github.com/redisson/redisson
1.引入依赖
1 2 3 4 5 6 <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;@Configuration public class MyRedissonConfig { @Bean(destroyMethod = "shutdown") public RedissonClient redisson () throws IOException { Config config = new Config (); config.useSingleServer().setAddress("redis://192.168.195.100:6379" ); return Redisson.create(config); } }
3.测试使用
1 2 3 4 5 6 7 @Autowired private RedissonClient redissonClient;@Test void testRedisson () { System.out.println(redissonClient); }
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" ); Date date = new Date (); RLock lock = redissonClient.getLock("my-lock" ); lock.lock(); System.out.println("现在时间:" + sdf.format(date)); try { System.out.println("加锁成功,执行业务,线程Id:" +Thread.currentThread().getId()); Thread.sleep(10000 ); } catch (InterruptedException e) { throw new RuntimeException (e); } finally { System.out.println("释放锁,线程Id:" +Thread.currentThread().getId()); lock.unlock(); } return "hello" ; }
my-lock对应的值会跟随线程的变化而变化
读写锁
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 分布式缓存一致性问题 双写模式
失效模式
缓存数据一致性-解决方案
缓存数据一致性-解决-Canal
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 <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.在启动类上添加缓存的注解
2.测试使用
1 2 3 4 5 6 @Cacheable({"category"}) @Override public List<CategoryEntity> getLevelOneCategorys () { return categoryDao.selectList(new LambdaQueryWrapper <CategoryEntity>().eq(CategoryEntity::getParentCid, 0 )); }
自定义数据的存储
设置自定义的key值
1 @Cacheable(value = {"category"},key = "'levelOneCategorys'")
tips: #root.method.name是SpEL表达式
1 @Cacheable(value = {"category"},key = "#root.method.name")
设置缓存数据的存活时间
1 2 spring.cache.redis.time-to-live =3600000
将数据保存为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;@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; } }
效果
其它的常用配置
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 整合页面
2.6.2 配置域名访问
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
2.6.3 首页和搜索页相互跳转 首页无法跳转到搜索页的原因,gumall改为gulimall即可
搜索框跳转到搜索页,修改页面中的代码
2.6.4 商品的检索
检索条件的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;@Data public class SearchParam { private String keyword; private List<Long> brandId; private Long catalog3Id; 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;@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)……