穀粒商城-基礎篇

peng_boke發表於2024-10-14

穀粒商城

1.環境搭建

1.1 使用vagrant建立linux環境

VirtualBox:https://www.virtualbox.org/wiki/Downloads

Vagrant:https://www.vagrantup.com/downloads

seata:http://seata.io/zh-cn/blog/download.html

Sentinel:https://github.com/alibaba/Sentinel

SwitchHosts:https://github.com/oldj/SwitchHosts

vue-devtools:https://github.com/vuejs/devtools

1.2安裝VirtualBox和Vagrant

(1)下載後先安裝VirtualBox,然後安裝Vagrant,一路next即可,建議不要安裝c盤

(2)cmd檢視Vagrant是否安裝成功

vagrant

image-20220211145606749

(3)vagrant init centos/7:初始化

https://app.vagrantup.com/boxes/search

image-20220211145659169

image-20220211145800646

(4)vagrant up:啟動虛擬機器

第一次啟動需要去下載centos/7

image-20220211154938097

說明啟動成功

image-20220211155431642

(5)vagrant ssh 開啟命令列

image-20220211155535791

1.3 網路配置

(1)找到配置檔案,修改配置

C:\Users\Peng\Vagrantfile

image-20220211155754022

(2)ipconfig 檢查本機VirtualBox地址,修改配置的網段需要一致,這裡都是56

image-20220211160519748

(3)vagrant reload 重啟虛擬機器

image-20220211160701279

(4)測試本機與虛擬機器能否互相ping通

檢視ip命令

winows:ipconfig

centos:ip addr

image-20220211161459821

image-20220211161541807

1.4安裝docker

(1)刪除老版本

sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

(2) 安裝工具包並設定儲存庫

sudo yum install -y yum-utils

sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

(3)安裝docker引擎

sudo yum install docker-ce docker-ce-cli containerd.io

(4)docker基本使用命令

啟動docker

sudo systemctl start docker

檢查docker版本

docker -v

檢視docker已有映象

sudo docker images

設定docker開機啟動

sudo systemctl enable docker

(5)設定阿里雲映象倉庫

建立檔案

sudo mkdir -p /etc/docker

修改配置, 設定映象

sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://vw9qapdy.mirror.aliyuncs.com"]
}
EOF

重啟後臺執行緒

sudo systemctl daemon-reload

重啟docker

sudo systemctl restart docker

docker查錯

 sudo dockerd --debug

1.5docker安裝mysql

(1)docker安裝mysql

sudo docker pull mysql:5.7

(2)docker啟動mysql

引數:

  • -p 3306:3306:將容器的3306埠對映到主機的3306埠

  • --name:給容器命名

  • -v /mydata/mysql/log:/var/log/mysql:將配置檔案掛載到主機/mydata/..

  • -e MYSQL_ROOT_PASSWORD=root:初始化root使用者的密碼為root

sudo 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

(3)檢視docker啟動的容器:

sudo docker ps

(4)配置mysql

進入掛載的mysql配置目錄

cd /mydata/mysql/conf

修改配置檔案 my.cnf

vi my.cnf

輸入以下內容

[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

# Esc
# :wq

docker重啟mysql使配置生效

docker restart mysql

1.6docker安裝redis

(1)docker拉取redis映象

docker pull redis

(2)docker啟動redis

mkdir -p /mydata/redis/conf

touch /mydata/redis/conf/redis.conf

(3)啟動redis容器

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

(4)配置redis持久化

echo "appendonly yes"  >> /mydata/redis/conf/redis.conf

#重啟生效
docker restart redis

(5)容器隨docker啟動自動執行

# mysql
docker update mysql --restart=always

# redis
docker update redis --restart=always

1.7開發環境

(1)maven

<mirror>
    <id>alimaven</id>
    <mirrorOf>central</mirrorOf>
    <name>aliyun maven</name>
    <url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
</mirror>

(2)外掛

image-20220211164207737

lombok

image-20220211164139925

mybatisx

image-20220211164116111

(3)vscode

地址:https://code.visualstudio.com/

安裝以下元件:

  • Auto Close Tag
  • Auto Rename Tag
  • Chinese
  • ESlint
  • HTML CSS Support
  • HTML Snippets
  • JavaScript ES6
  • Live Server
  • open in brower
  • Vetur

1.8配置git-ssh

配置使用者名稱
git config --global user.name "username"  //(名字,隨意寫)

配置郵箱
git config --global user.email "55333@qq.com" // 註冊賬號時使用的郵箱

配置ssh免密登入
ssh-keygen -t rsa -C "55333@qq.com"
三次回車後生成了金鑰:公鑰私鑰
cat ~/.ssh/id_rsa.pub

也可以檢視金鑰
瀏覽器登入碼雲後,個人頭像上點設定--ssh公鑰---隨便填個標題---複製

測試
ssh -T git@gitee.com

1.9 碼雲專案構建

(1)建立專案倉庫

image-20220211165857559

image-20220211165918177

(2)使用idea Clone專案

image-20220211171504066

image-20220211170158404

(3)建立微服務模組

  • com.peng.gulimall
  • gulimall-product
  • com.peng.gulimall.product

image-20220211171342764

選擇springWeb和OpenFeign

image-20220211171329570

(4)專案結構

  • 商品服務product
  • 儲存服務ware
  • 訂單服務order
  • 優惠券服務coupon
  • 使用者服務member

image-20220211220056358

(5)建立聚合服務

image-20220211220304551

<modelVersion>4.0.0</modelVersion>
<groupId>com.peng.gulimall</groupId>
<artifactId>gulimall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimall</name>
<description>聚合服務</description>
<packaging>pom</packaging>
<modules>
    <module>gulimail-product</module>
    <module>gulimall-coupon</module>
    <module>gulimall-member</module>
    <module>gulimall-order</module>
    <module>gulimall-ware</module>
</modules>

(6)修改.gitignore

**/mvnw
**/mvnw.cmd

**/.mvn
**/target/

.idea

**/.gitignore

image-20220211175954004

(7)重新整理後納入版本控制

image-20220211180119684

(8)idea安裝碼雲

image-20220211180239221

1.10資料庫

地址:

create database gulimall_admin default character set utf8mb4 collate utf8mb4_general_ci;
create database gulimall_oms default character set utf8mb4 collate utf8mb4_general_ci;
create database gulimall_pms default character set utf8mb4 collate utf8mb4_general_ci;
create database gulimall_sms default character set utf8mb4 collate utf8mb4_general_ci;
create database gulimall_ums default character set utf8mb4 collate utf8mb4_general_ci;
create database gulimall_wms default character set utf8mb4 collate utf8mb4_general_ci;

1.11 vagrant和virtualbox本地安裝

下載地址:

https://app.vagrantup.com/boxes/search

image-20220211145659169

 $ vagrant box add {title} {url}
 $ vagrant init {title}
 $ vagrant up

例如:

vagrant box add CentOS E:\Data\tool\vagrant\CentOS-7-x86_64-Vagrant-2004_01.VirtualBox.box

vagrant init CentOS 

vagrant up

image-20220215002735071

2.快速開發

2.1人人開源搭建後臺管理系統

(1)clone

地址:https://gitee.com/renrenio

$ git clone git@gitee.com:renrenio/renren-fast-vue.git

image-20220211222822716

$ git clone git@gitee.com:renrenio/renren-fast.git

image-20220211222841114

(2)複製到專案中

<modules>
    <module>gulimail-product</module>
    <module>gulimall-coupon</module>
    <module>gulimall-member</module>
    <module>gulimall-order</module>
    <module>gulimall-ware</module>
    <module>renren-fast</module>
</modules>

image-20220211223049763

(3)執行mysql

image-20220211225704433

(4)修改啟動埠號(預設80,容易衝突)和資料庫連線地址

server:
  port: 8888

url: jdbc:mysql://192.168.56.10:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root

image-20220212001119603

(5)執行

image-20220211225412670

2.2 搭建前端環境

(1)安裝node

地址:https://nodejs.org/zh-cn/download/

下載後選擇安裝地址(不建議c盤),一路next即可

檢查node版本

node -v 

設定npm映象地址

npm config set registry http://registry.npm.taobao.org/

(2)前後聯調

專案安裝npm

npm install

image-20220211230326893

啟動

npm run dev 

訪問http://localhost:8001/#/login

輸出使用者名稱admin密碼admin 驗證碼,登入成功,前後端聯調成功

image-20220211230622359

2.3逆向工程搭建&使用

(1)下載程式碼人人開原始碼生成器

$ git clone git@gitee.com:renrenio/renren-generator.git

image-20220211231046749

(2)renren-generator載入到專案中

image-20220211232849028

(3)修改mysql資料庫相關配置

url: jdbc:mysql://192.168.10.56:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root

image-20220211233140996

(4)修改程式碼生成配置

# 主目錄
mainPath=com.peng
#包名
package=com.peng.gulimall
moduleName=product
#作者
author=peng
#Email
email=pengpeng6135@163.com
#表字首(類名不會包含表字首)
tablePrefix=pms_

image-20220211233506098

(5)修改contoller模板
註釋import org.apache.shiro.authz.annotation.RequiresPermissions名稱空間

image-20220212002343153

註釋Controller.java.vm模板中所有的@RequiresPermissions("${moduleName}😒{pathName}:list")

image-20220211233741331

(6)新建公共模組

新建模組

image-20220211234026550

選擇maven專案

image-20220211234046280

新建gulimall-common

image-20220211234115991

配置gulimall-common的pom.xml

<parent>
    <artifactId>gulimall</artifactId>
    <groupId>com.peng.gulimall</groupId>
    <version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<packaging>pom</packaging>
<artifactId>gulimall-common</artifactId>
<description>每一個微服務公共的的依賴、bean、工具類</description>

image-20220211234657141

gulimall-common新增到pom.xml

image-20220211234518726

(7)gulimall-common依賴

  <dependencies>
        <!-- mybatisPLUS -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>

        <!--簡化實體類,用@Data代替getset方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
        <!-- httpcomponent包 -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.13</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.4.1</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>

        <!-- 資料庫驅動 https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>


    </dependencies>

(8)啟動,訪問 http://localhost:8888/#generator.html

image-20220212001308228

(9)生成程式碼

image-20220212001402745

(10)生成的程式碼複製到專案中去

image-20220212013646489

2.4測試基本基本的CRUD功能

(1)整合MyBatis-Plus

<!-- mybatisPLUS -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.3.2</version>
</dependency>

image-20220212005230572

(2)@MapperScan配置注入的資料訪問層

image-20220212013121589

(3)配置application.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.56.10:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.jdbc.Driver

mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto

image-20220212013545826

(4)測試

image-20220212013814420

2.5 所有微服務的CURD

  • 商品服務product
  • 儲存服務ware
  • 訂單服務order
  • 優惠券服務coupon
  • 使用者服務member

如果UndoLog的有關服務報錯,可以都先註釋掉,暫時用不上

image-20220212164459645

2.5.1 coupon 優惠卷服務

renren-generator工程,修改generator.properties

image-20220212160222795

#主目錄
mainPath=com.peng
#包名
package=com.peng.gulimall
#模組名
moduleName=coupon
#作者
author=peng
#email
email=pengpeng6135@163.com
#表字首(類名不會包含表字首) # 我們的sms資料庫中的表的字首都sms
如果寫了表字首,每一張表對於的javaBean就不會新增字首了
tablePrefix=sms_ 

renren-generator工程,修改application.yml資料庫資訊

image-20220212160450187

server:
  port: 8888

# mysql
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    #MySQL配置
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.56.10:3306/gulimall_sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root

gulimall-coupon工程,依賴於common,修改pom.xml

image-20220212160852383

<dependency>
    <groupId>com.peng.gulimall</groupId>
    <artifactId>gulimall-common</artifactId>
    <version>0.0.1-SNAPSHOT</version>        
</dependency>

gulimall-coupon工程,修改application.yml資料庫配置

image-20220212160923654

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.56.10:3306/gulimall_sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver


mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1
      logic-not-delete-value: 0

server:
  port: 7001

執行gulimallCouponApplication.java

image-20220212161128218

訪問http://localhost:8888/#generator.html

image-20220212161210063

生成完的程式碼賦值到gulimall-coupon,以下是專案結構

image-20220212163848164

配置mapper對映,並啟動

image-20220212164032885

@MapperScan("com.peng.gulimall.coupon.dao")

測試gulimall-coupon,訪問http://localhost:7001/coupon/coupon/list

image-20220212164051551

2.5.2 member 使用者服務

(1)renren-generator修改generator.properties

#模組名
moduleName=member
#表字首(類名不會包含表字首) # 我們的ums資料庫中的表的字首都ums
如果寫了表字首,每一張表對於的javaBean就不會新增字首了
tablePrefix=ums_ 

image-20220212165609185

(2)renren-generator修改application.yml

 url: jdbc:mysql://192.168.56.10:3306/gulimall_ums?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai  

image-20220212165703440

(3)gulimall-member修改pom.xml

<dependency>
    <groupId>com.peng.gulimall</groupId>
    <artifactId>gulimall-common</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

image-20220212165823132

(4)gulimall-member修改application.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.56.10:3306/gulimall_ums?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver


mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1
      logic-not-delete-value: 0

server:
  port: 8002

(5)入口增加mapper對映

image-20220212170017639

(6)訪問:http://localhost:8002/member/growthchangehistory/list

2.5.3 order 訂單服務

(1)renren-generator修改generator.properties

#模組名
moduleName=order
#表字首(類名不會包含表字首) # 我們的oms資料庫中的表的字首都oms
如果寫了表字首,每一張表對於的javaBean就不會新增字首了
tablePrefix=oms_ 

(2)renren-generator修改application.yml

url: jdbc:mysql://192.168.56.10:3306/gulimall_oms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai

(3)gulimall-member修改pom.xml

<dependency>
    <groupId>com.peng.gulimall</groupId>
    <artifactId>gulimall-common</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

(4)gulimall-member修改application.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.56.10:3306/gulimall_oms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver


mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1
      logic-not-delete-value: 0

server:
  port: 9000

(5)入口增加mapper對映
@MapperScan("com.peng.gulimall.order.dao")

(6)訪問:http://localhost:9000/order/mqmessage/list

2.5.4 ware 儲存服務

(1)renren-generator修改generator.properties

#模組名
moduleName=ware
#表字首(類名不會包含表字首) # 我們的wms資料庫中的表的字首都wms
如果寫了表字首,每一張表對於的javaBean就不會新增字首了
tablePrefix=wms_ 

(2)renren-generator修改application.yml

url: jdbc:mysql://192.168.56.10:3306/gulimall_wms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai

(3)gulimall-member修改pom.xml

<dependency>
    <groupId>com.peng.gulimall</groupId>
    <artifactId>gulimall-common</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

(4)gulimall-member修改application.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.56.10:3306/gulimall_wms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver


mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1
      logic-not-delete-value: 0

server:
  port: 10000

(5)入口增加mapper對映

@MapperScan("com.peng.gulimall.ware.dao")

(6)訪問:http://localhost:10000/ware/purchase/list

3.Spring Cloud alibaba

3.1 Spring Cloud alibaba

地址:https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/README-zh.md

引入依賴:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.2.7.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

image-20220212152535459

3.2 Spring Cloud alibaba Nacos 註冊中心

地址:https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/spring-cloud-alibaba-examples/nacos-example/nacos-discovery-example/readme-zh.md

(1)下載Nacos Server

地址:https://github.com/alibaba/nacos/releases

image-20220212171850357

image-20220212173819748

(2)解壓執行

預設是cluster,可以改成standalone,或者如下命令列執行,image-20220212225142450

單機執行

startup.cmd -m standalone

image-20220212212319512

(3)訪問:http://127.0.0.1:8848/nacos/index.html#/login

使用者名稱密碼都是nacos

image-20220212212410875

(4)gulimall-common的pom.xml匯入依賴:

 <dependency>
     <groupId>com.alibaba.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
 </dependency>
 
 /*
 //如果出現loadbalancer相關錯誤,在nacos"包中移除ribbion依賴,並加入loadbalancer依賴"
         <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <!--不使用Ribbon 進行客戶端負載均衡-->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
 */

(5)gulimall-member的application.yml增加nacos配置

cloud:
  nacos:
    discovery:
      server-addr: 127.0.0.1:8848
application:
  name: gulimall-member

image-20220212225347385

(6)gulimall-member使用 @EnableDiscoveryClient 註解開啟服務註冊與發現功能

@EnableDiscoveryClient

image-20220212225451384

(7)gulimall-coupon的application.yml增加nacos配置

cloud:
  nacos:
    discovery:
      server-addr: 127.0.0.1:8848
application:
  name: gulimall-coupon

image-20220212225610615

(8)gulimall-coupon使用 @EnableDiscoveryClient 註解開啟服務註冊與發現功能

@EnableDiscoveryClient

image-20220212225636440

(9)執行GulimallCouponApplication 和GulimallMemberApplication

image-20220212225745870

(10)登入nacos檢視,服務已經註冊進來

image-20220212225824470

注意:com.alibaba.cloud版本和nacos版本問題

com.alibaba.cloud是2.2.7.RELEASE版本,nacos如果是1.多版本就會註冊不進去,服務起不來

image-20220212230057326

3.3 Spring Cloud Openfeign 遠端呼叫

(1)匯入依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
 //如果出現loadbalancer相關錯誤,在nacos"包中移除ribbion依賴,並加入loadbalancer依賴"
<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-loadbalancer</artifactId>
 </dependency>

image-20220212230404036

(2)gulimall-coupon的CouponController增加測試介面

@RequestMapping("/member/list")
public R membercoupons(){
    // 全系統的所有返回都返回R
    // 應該去資料庫查使用者對於的優惠券,但這個我們簡化了,不去資料庫查了,構造了一個優惠券給他返回
    CouponEntity couponEntity = new CouponEntity();
    couponEntity.setCouponName("滿100-10");//優惠券的名字
    return R.ok().put("coupons",Arrays.asList(couponEntity));
}

image-20220212231408850

(2)開啟遠端呼叫功能 @EnableFeignClients,要指定遠端呼叫功能放的基礎包

@EnableFeignClients(basePackages="com.peng.gulimall.member.feign")//掃描介面方法註解

image-20220212231644234

(3)呼叫遠端服務的介面

package com.peng.gulimall.member.feign;

import com.peng.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
// 告訴spring cloud這個介面是一個遠端客戶端,要呼叫coupon服務(nacos中找到),
// 具體是呼叫coupon服務的/coupon/coupon/member/list對應的方法
@FeignClient("gulimall-coupon")
public interface CouponFeignService {
    // 遠端服務的url
    // 注意寫全優惠券類上還有對映
    // 注意我們這個地方不是控制層,所以這個請求對映請求的不是我們伺服器上的東西,而是nacos註冊中心的
    // 得到一個R物件
    @RequestMapping("/coupon/coupon/member/list")
    public R membercoupons();
}

image-20220212231828720

(4)在gulimall-member的MemberController寫一個測試介面

@Autowired
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"));
}

image-20220212232045341

(5)訪問:http://localhost:8005/member/member/coupons

image-20220212235117548

出現loadbalancer相關的錯誤

在nacos"包中移除ribbion依賴,並加入loadbalancer依賴"

image-20220212234532098

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <!--不使用Ribbon 進行客戶端負載均衡-->
    <exclusions>
        <exclusion>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </exclusion>
    </exclusions>
</dependency>

在需要遠端呼叫的服務加上loadbalancer

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

image-20220212235039315

不識別bootstrap.properties的錯誤

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

3.4 Spring Cloud alibaba Naloas 配置中心

地址:https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/readme-zh.md

(1)匯入依賴

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

image-20220212235406430
(2)增加bootstrap.properties配置,這個檔案是springboot裡規定的,他優先順序別application.properties高

# 改名字,對應nacos裡的配置檔名
spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848

image-20220213000006368

(3)增加application.properties配置

coupon.user.name = application.properties
coupon.user.age = 18

image-20220213000042792

(4)CouponController增加測試程式碼

//從application.properties中獲取//不要寫user.name,他是環境裡的變數
@Value("${coupon.user.name}")
private String name;
@Value("${coupon.user.age}")
private Integer age;
@RequestMapping("/test")
public R test(){

    return R.ok().put("name",name).put("age",age);
}

image-20220213000154849

(5)測試:http://localhost:7005/coupon/coupon/test

這裡就可以訪問本地application.properties的配置檔案

image-20220213000241323

(6)nacos增加配置併發布

image-20220213000435745

3.5 Spring Cloud alibaba Nacos 配置中心-名稱空間與配置分組

3.5.1 名稱空間

nacos新增名稱空間

image-20220213141406088

prop配置

coupon.user.name = nacos.prop
coupon.user.age = 30

image-20220213141430932

image-20220213141253434

# 可以選擇對應的名稱空間 # 寫上對應環境的名稱空間ID
spring.cloud.nacos.config.namespace=d408db52-7aa9-4348-81b4-cc5d39f91c55

image-20220213141122995

測試訪問:http://localhost:7005/coupon/coupon/test

image-20220213141201571

  • 名稱空間:用作配置隔離。(一般每個微服務一個名稱空間)

    • 預設public。預設新增的配置都在public空間下

    • 開發、測試、開發可以用名稱空間分割。properties每個空間有一份。也可以為每個微服務配置一個名稱空間,微服務互相隔離

    • 在bootstrap.properties裡配置

      spring.cloud.nacos.config.namespace=d408db52-7aa9-4348-81b4-cc5d39f91c55
      

      image-20220213140628230

  • 配置集:一組相關或不相關配置項的集合。

  • 配置集ID:類似於配置檔名,即Data ID

  • 配置分組:預設所有的配置集都屬於DEFAULT_GROUP。雙十一,618的優惠策略改分組即可

    spring.cloud.nacos.config.group=DEFAULT_GROUP
    

3.5.2 配置分組

(1)為coupon新增一個名稱空間

image-20220213141658701

(2)gulimall-coupon只讀取自己的配置(基於微服務隔離)

image-20220213142124267

(3)可以設定不同的分組

coupon.user.name = coupon.11

coupon.user.age = 60

image-20220213142734839

image-20220213142628274

(4)測試:http://localhost:7005/coupon/coupon/test

image-20220213142706963

(5)新增dev(開發環境)和prod(生產環境)分組,預設為dev分組

dev配置:

coupon.user.name = coupon.dev
coupon.user.age = 50

prod配置:

coupon.user.name = coupon.prod
coupon.user.age = 70

image-20220213143207865

測試訪問:http://localhost:7005/coupon/coupon/test

image-20220213143406444

3.6 Spring Cloud alibaba Nacos 配置中心-載入多配製集

(1)註釋application.yml

image-20220213150728605

(2)nacos新增配置

datasource.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.56.10:3306/gulimall_sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
  profiles:
    active: dev

mybatis.yml

mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1
      logic-not-delete-value: 0

other.yml

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: gulimall-coupon

server:
  port: 7005

image-20220213150320322

(3)修改bootstrap.properties

# 改名字,對應nacos裡的配置檔名

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

# 可以選擇對應的名稱空間 # 寫上對應環境的名稱空間ID
spring.cloud.nacos.config.namespace=8531791b-f632-4588-a406-090f42c04d48

# 分組 預設DEFAULT_GROUP
spring.cloud.nacos.config.group=dev

spring.cloud.nacos.config.extension-configs[0].data-id=datasource.yml
spring.cloud.nacos.config.extension-configs[0].group=dev
spring.cloud.nacos.config.extension-configs[0].refresh=true

spring.cloud.nacos.config.extension-configs[1].data-id=mybatis.yml
spring.cloud.nacos.config.extension-configs[1].group=dev
spring.cloud.nacos.config.extension-configs[1].refresh=true

spring.cloud.nacos.config.extension-configs[2].data-id=other.yml
spring.cloud.nacos.config.extension-configs[2].group=dev
spring.cloud.nacos.config.extension-configs[2].refresh=true

image-20220213150507873

(4)測試執行,nacos配置讀取正常

訪問:http://localhost:7005/coupon/coupon/test

image-20220213150624589

訪問:http://localhost:7005/coupon/coupon/list

image-20220213150644134

3.7 Spring Cloud Gateway

地址:https://spring.io/projects/spring-cloud-gateway

三大核心概念:

  • Route(路由):
  • Predicate(斷言):
  • Filter(過濾器):

(1)spring嚮導建立閘道器,選擇閘道器

image-20220213152405548

image-20220213152539303

(2)nacos增加gateway配置

增加gateway名稱空間

image-20220213155346010

gulimall-gateway.properties

spring:
    application:
        name: gulimall-gateway

image-20220213155415901

(3)bootstrap.properties配置

spring.application.name=gulimall-gateway
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=debd0e0d-de9a-48c8-b7c1-dbed3b775402

image-20220213155518505

(4)application.properties配置

spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.application.name=gulimall-gateway
server.port=88

image-20220213155631774

(5)application.yml路由配置

spring:
  cloud:
    gateway:
      routes:
        - id: test_route
          uri: https://www.baidu.com
          predicates:
            - Query=url,baidu

        - id: qq_route
          uri: https://www.qq.com
          predicates:
            - Query=url,qq

image-20220213155716208

(6)主程式加上@EnableDiscoveryClient,@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})

如果不加exclude= {DataSourceAutoConfiguration.class,排除datasource相關配置,會報錯Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured,這裡暫時用不上datasource的一些相關屬性

在gateway服務中開啟註冊服務發現@EnableDiscoveryClient,配置nacos註冊中心地址applicaion.properties。這樣gateway也註冊到了nacos中,其他服務就能找到nacos,閘道器也能透過nacos找到其他服務

@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient

image-20220213155747723

4.前端

VBoxManage.exe internalcommands sethduuid “D:\VirtualBox VMs\Peng_default_1644565405392_61498\Peng_default_1644565405392_61498.vbox”

4.1 ES6

4.1 let和var

let不會作用到{}外,var會越域跳到{}外

 {
     var a = 1;
     let b = 2;
 }
console.log(a);  // 1
console.log(b);  // ReferenceError: b is not defined

var可以多次宣告同一變數,let會報錯

var m = 1;
var m = 2;
let n = 3;
//  let n = 4
console.log(m); // 2
console.log(n); // Identifier 'n' has already been declared

var定義之前可以使用,let定義之前不可使用。(變數提升問題)

// var 會變數提升
// let 不存在變數提升
console.log(x); // undefined
var x = 10;
console.log(y); //ReferenceError: y is not defined
let y = 20;

const宣告之後不允許改變

// let
// 1. const宣告之後不允許改變
// 2. 一但宣告必須初始化,否則會報錯
const a = 1;
a = 3; //Uncaught TypeError: Assignment to constant variable.

4.2 解構表示式

陣列解構

//陣列解構
let arr = [1,2,3];
// // let a = arr[0];
// // let b = arr[1];
// // let c = arr[2];
let [a,b,c] = arr;
console.log(a,b,c)

物件結構

const person = {
    name: "jack",
    age: 21,
    language: ['java', 'js', 'css']
}
// const name = person.name;
// const age = person.age;
// const language = person.language;

//物件解構 // 把name屬性變為abc,宣告瞭abc、age、language三個變數
const { name: abc, age, language } = person;
 console.log(abc, age, language)

字串擴充套件

//4、字串擴充套件
let str = "hello.vue";
console.log(str.startsWith("hello"));//true
console.log(str.endsWith(".vue"));//true
console.log(str.includes("e"));//true
console.log(str.includes("hello"));//true

字串模板,``可以定義多行字串

//字串模板 ``可以定義多行字串
let ss = `<div>
            <span>hello world<span>
          </div>`;
console.log(ss);
        
function fun() {
    return "這是一個函式"
}

字串插入變數和表示式

// 2、字串插入變數和表示式。變數名寫在 ${} 中,${} 中可以放入 JavaScript 表示式。
let info = `我是${abc},今年${age + 10}了, 我想說: ${fun()}`;
console.log(info);

4.3 函式最佳化

//在ES6以前,我們無法給一個函式引數設定預設值,只能採用變通寫法:
       function add(a, b) {
            // 判斷b是否為空,為空就給預設值1
            b = b || 1;
            return a + b;
        }
        // 傳一個引數
        console.log(add(10));


        //現在可以這麼寫:直接給引數寫上預設值,沒傳就會自動使用預設值
        function add2(a, b = 1) {
            return a + b;
        }
        console.log(add2(20));


        //2)、不定引數
        function fun(...values) {
            console.log(values.length)
        }
        fun(1, 2)      //2
        fun(1, 2, 3, 4)  //4

        //3)、箭頭函式。lambda
        //以前宣告一個方法
        // var print = function (obj) {
        //     console.log(obj);
        // }
        var print = obj => console.log(obj);
        print("hello");

        var sum = function (a, b) {
            c = a + b;
            return a + c;
        }

        var sum2 = (a, b) => a + b;
        console.log(sum2(11, 12));

        var sum3 = (a, b) => {
            c = a + b;
            return a + c;
        }
        console.log(sum3(10, 20))


        const person = {
            name: "jack",
            age: 21,
            language: ['java', 'js', 'css']
        }

        function hello(person) {
            console.log("hello," + person.name)
        }

        //箭頭函式+解構
        var hello2 = ({name}) => console.log("hello," +name);
        hello2(person); 

4.4 物件最佳化

        const person = {
            name: "jack",
            age: 21,
            language: ['java', 'js', 'css']
        }

        console.log(Object.keys(person));//["name", "age", "language"]
        console.log(Object.values(person));//["jack", 21, Array(3)]
        console.log(Object.entries(person));//[Array(2), Array(2), Array(2)]

        const target  = { a: 1 };
        const source1 = { b: 2 };
        const source2 = { c: 3 };

        // 合併
        //{a:1,b:2,c:3}
        Object.assign(target, source1, source2);

        console.log(target);//{a: 1, b: 2, c: 3}

        //2)、宣告物件簡寫
        const age = 23
        const name = "張三"
        const person1 = { age: age, name: name }
        // 等價於
        const person2 = { age, name }//宣告物件簡寫
        console.log(person2);

        //3)、物件的函式屬性簡寫
        let person3 = {
            name: "jack",
            // 以前:
            eat: function (food) {
                console.log(this.name + "在吃" + food);
            },
            //箭頭函式this不能使用,要使用的話需要使用:物件.屬性
            eat2: food => console.log(person3.name + "在吃" + food),
            eat3(food) {
                console.log(this.name + "在吃" + food);
            }
        }

        person3.eat("香蕉");
        person3.eat2("蘋果")
        person3.eat3("橘子");

        //4)、物件擴充運算子

        // 1、複製物件(深複製)
        let p1 = { name: "Amy", age: 15 }
        let someone = { ...p1 }
        console.log(someone)  //{name: "Amy", age: 15}

        // 2、合併物件
        let age1 = { age: 15 }
        let name1 = { name: "Amy" }
        let p2 = {name:"zhangsan"}
        p2 = { ...age1, ...name1 } 
        console.log(p2)

4.5 map和reduce

        //陣列中新增了map和reduce方法。
        let arr = ['1', '20', '-5', '3'];
         
         //map():接收一個函式,將原陣列中的所有元素用這個函式處理後放入新陣列返回。
         //  arr = arr.map((item)=>{
         //     return item*2
         //  });
          arr = arr.map(item=> item*2);
 
         
 
          console.log(arr);
         //reduce() 為陣列中的每一個元素依次執行回撥函式,不包括陣列中被刪除或從未被賦值的元素,
         //[2, 40, -10, 6]
         //arr.reduce(callback,[initialValue])
         /**
         1、previousValue (上一次呼叫回撥返回的值,或者是提供的初始值(initialValue))
         2、currentValue (陣列中當前被處理的元素)
         3、index (當前元素在陣列中的索引)
         4、array (呼叫 reduce 的陣列)*/
         let result = arr.reduce((a,b)=>{
             console.log("上一次處理後:"+a);
             console.log("當前正在處理:"+b);
             return a + b;
         },100);
         console.log(result)

4.6 promise

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>

        //1、查出當前使用者資訊
        //2、按照當前使用者的id查出他的課程
        //3、按照當前課程id查出分數
        // $.ajax({
        //     url: "mock/user.json",
        //     success(data) {
        //         console.log("查詢使用者:", data);
        //         $.ajax({
        //             url: `mock/user_corse_${data.id}.json`,
        //             success(data) {
        //                 console.log("查詢到課程:", data);
        //                 $.ajax({
        //                     url: `mock/corse_score_${data.id}.json`,
        //                     success(data) {
        //                         console.log("查詢到分數:", data);
        //                     },
        //                     error(error) {
        //                         console.log("出現異常了:" + error);
        //                     }
        //                 });
        //             },
        //             error(error) {
        //                 console.log("出現異常了:" + error);
        //             }
        //         });
        //     },
        //     error(error) {
        //         console.log("出現異常了:" + error);
        //     }
        // });


        //1、Promise可以封裝非同步操作
        // let p = new Promise((resolve, reject) => { //傳入成功解析,失敗拒絕
        //     //1、非同步操作
        //     $.ajax({
        //         url: "mock/user.json",
        //         success: function (data) {
        //             console.log("查詢使用者成功:", data)
        //             resolve(data);
        //         },
        //         error: function (err) {
        //             reject(err);
        //         }
        //     });
        // });

        // p.then((obj) => { //成功以後做什麼
        //     return new Promise((resolve, reject) => {
        //         $.ajax({
        //             url: `mock/user_corse_${obj.id}.json`,
        //             success: function (data) {
        //                 console.log("查詢使用者課程成功:", data)
        //                 resolve(data);
        //             },
        //             error: function (err) {
        //                 reject(err)
        //             }
        //         });
        //     })
        // }).then((data) => { //成功以後幹什麼
        //     console.log("上一步的結果", data)
        //     $.ajax({
        //         url: `mock/corse_score_${data.id}.json`,
        //         success: function (data) {
        //             console.log("查詢課程得分成功:", data)
        //         },
        //         error: function (err) {
        //         }
        //     });
        // })

        function get(url, data) { //自己定義一個方法整合一下
            return new Promise((resolve, reject) => {
                $.ajax({
                    url: url,
                    data: data,
                    success: function (data) {
                        resolve(data);
                    },
                    error: function (err) {
                        reject(err)
                    }
                })
            });
        }

        get("mock/user.json")
            .then((data) => {
                console.log("使用者查詢成功~~~:", data)
                return get(`mock/user_corse_${data.id}.json`);
            })
            .then((data) => {
                console.log("課程查詢成功~~~:", data)
                return get(`mock/corse_score_${data.id}.json`);
            })
            .then((data)=>{
                console.log("課程成績查詢成功~~~:", data)
            })
            .catch((err)=>{ //失敗的話catch
                console.log("出現異常",err)
            });


    </script>
</body>
</html>

corse_score_10.json 得分

{
    "id": 100,
    "score": 90
}

user.json 使用者

{
    "id": 10,
    "name": "chinese"
}

user_corse_1.json 課程

{
    "id": 1,
    "name": "zhangsan",
    "password": "123456"
}

4.7 import和export

  • export用於規定模組的對外介面
  • import用於匯入其他模組提供的功能

user.js

var name = "jack"
var age = 21
function add(a,b){
    return a + b;
}
// 匯出變數和函式
export {name,age,add}

hello.js

// export const util = {
//     sum(a, b) {
//         return a + b;
//     }
// }

// 匯出後可以重新命名
export default {
    sum(a, b) {
        return a + b;
    }
}
// export {util}

//`export`不僅可以匯出物件,一切JS變數都可以匯出。比如:基本型別變數、函式、陣列、物件。

7.import和export.js

import abc from "./hello.js"
import {name,add} from "./user.js"

abc.sum(1,2);
console.log(name);
add(1,3);

4.2 Vue基本語法

MVVM思想

  • M:model 包括資料和一些基本操作
  • V:view 檢視,頁面渲染結果
  • VM:View-model,模型與檢視間的雙向操作(無需開發人員干涉)

v-bind 縮寫

<!-- 完整語法 -->
<a v-bind:href="url"></a>
<!-- 縮寫 -->
<a :href="url"></a>

v-on 縮寫

<!-- 完整語法 -->
<a v-on:click="doSomething"></a>
<!-- 縮寫 -->
<a @click="doSomething"></a>

官網:https://cn.vuejs.org/index.html

npm init -y
npm install vue
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <input type="text" v-model="num" />
      v-model實現雙向繫結。此處代表輸入框和vue裡的data繫結
      <button v-on:click="num++">點贊</button>
      v-on:click繫結事件,實現自增。
      <button v-on:click="cancel">取消</button>
      回撥自定義的方法。 此時字串裡代表的函式
      <h3>{{name}} ,你好 ,有{{num}}個人為他點贊</h3>
      先從vue中拿到值填充到dom,input再改變num值,vue例項更新,然後此處也更新
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <!-- <script src="./node_modules/vue/dist/vue.js"></script> -->

    <script>
      //1、vue宣告式渲染
      let vm = new Vue({
        el: "#app",
        data: {
          name: "彭小帥",
          num: 1,
        },
        methods: {
          cancel(){
            this.num--;
          },
          hello(){
            return "1";
          }
        },
      });
    </script>
  </body>
</html>

4.2.1 vue-devtools外掛安裝

(1)下載地址:https://github.com/vuejs/devtools

image-20220213212012729

(2)下載後解壓,cmd到解壓目錄,執行npm install

如果在E盤,cmd輸入e:,讓後cd到路徑

或者直接在devtools-main解壓目錄輸入cmd,回車

image-20220213215834234

image-20220213214856901

(3)執行npm run build,新版本使用npm run build報錯

image-20220213215303683

(4)安裝yarn

npm install -g yarn

image-20220213215427890

(5)使用前更換淘寶映象

yarn config set registry https://registry.npm.taobao.org --global
yarn config set disturl https://npm.taobao.org/dist --global
yarn config get registry (如果上面配置正確這個命令會輸出淘寶映象地址)

(6)配置外掛

yarn install

image-20220213221802320

中間失敗了一次,又執行了一次就成功了

image-20220213221854102

yarn run build

4.2.2 v-text、v-html、v-ref

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
        <p>前面的內容如果網速慢的話會先顯示括號,然後才替換成資料。
            v-html 和v-text能解決這個問題</p>
        {{msg}}  {{1+1}}  {{hello()}} 

        <p>用v-html取內容</p>
        <p><span v-html="msg"></span></p>
        
        <p>用v-text取內容</p>
        <p><span v-text="msg"></span></p>

    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>

      let vm = new Vue({
        el:"#app",
            data:{
                msg:"<h1>Hello</h1>",
                link:"http://www.baidu.com"
            },
            methods:{
                hello(){
                    return "World"
                }
            }
      });
    </script>
  </body>
</html>

4.2.3 v-bind

v-bind:,簡寫為:。表示把model繫結到view。可以設定src、title、class等

class 與 style 是 HTML 元素的屬性,用於設定元素的樣式,我們可以用 v-bind 來設定樣式屬性。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <a v-bind:href="link">跳轉</a>
       <!-- class,style  {class名:vue值}-->
       <span v-bind:class="{active:isActive,'text-danger':hasError}"
       :style="{color: color1,fontSize: size}">你好</span>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
      let vm = new Vue({
        el: "#app",
        data: {
          link: "http://www.baidu.com",
          isActive: true,
          hasError: true,
          color1: "red",
          size: "36px",
        },
        methods: {},
      });
    </script>
  </body>
</html>

4.2.4 v-model

v-model雙向繫結,v-bind只能從model到view。v-model能從view到mode

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      精通的語言:
      <input type="checkbox" v-model="language" value="Java" /> Java<br />
      <input type="checkbox" v-model="language" value="PHP" /> PHP<br />
      <input type="checkbox" v-model="language" value="Python" /> Python<br />
      選中了 {{language.join(",")}}
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
      let vm = new Vue({
        el: "#app",
        data: {
            language:[]
        }     
      });
    </script>
  </body>
</html>

4.2.5 v-on

事件監聽可以使用 v-on 指令

Vue.js 為 v-on 提供了事件修飾符來處理 DOM 事件細節,如:event.preventDefault() 或 event.stopPropagation()。

Vue.js 透過由點 . 表示的指令字尾來呼叫修飾符。

  • .stop - 阻止冒泡
  • .prevent - 阻止預設事件
  • .capture - 阻止捕獲
  • .self - 只監聽觸發該元素的事件
  • .once - 只觸發一次
  • .left - 左鍵事件
  • .right - 右鍵事件
  • .middle - 中間滾輪事件
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <!--事件中直接寫js片段-->
      <button v-on:click="num++">點贊</button>
      <!--事件指定一個回撥函式,必須是Vue例項中定義的函式-->
      <button @click="cancel">取消</button>
      <!--  -->
      <h1>有{{num}}個贊</h1>

      <!-- 事件修飾符 -->
      <div style="border: 1px solid red; padding: 20px" v-on:click.once="hello">
        大div
        <div style="border: 1px solid blue; padding: 20px" @click.stop="hello">
          小div <br />
          <a href="http://www.baidu.com" @click.prevent.stop="hello">去百度</a>
        </div>
      </div>

      <!-- 按鍵修飾符: -->
      <input
        type="text"
        v-model="num"
        v-on:keyup.up="num+=2"
        @keyup.down="num-=2"
        @click.ctrl="num=10"
      /><br />
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
      let vm = new Vue({
        el:"#app",
            data:{
                num: 1
            },
            methods:{
                cancel(){
                    this.num--;
                },
                hello(){
                    alert("點選了")
                }
            }
      });
    </script>
  </body>
</html>

4.2.6 v-for

可以遍歷 陣列[] 字典{} 。對於字典 v-for="(value, key, index) in object

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
        <ul>
            <!-- 4、遍歷的時候都加上:key來區分不同資料,提高vue渲染效率 -->
            <li v-for="(user,index) in users" :key="user.name" v-if="user.gender == '女'">
                <!-- 1、顯示user資訊:v-for="item in items" -->
               當前索引:{{index}} ==> {{user.name}}  ==>   
                  {{user.gender}} ==>{{user.age}} <br>
                <!-- 2、獲取陣列下標:v-for="(item,index) in items" -->
                <!-- 3、遍歷物件:
                        v-for="value in object"
                        v-for="(value,key) in object"
                        v-for="(value,key,index) in object" 
                -->
                物件資訊:
                <span v-for="(v,k,i) in user">{{k}}=={{v}}=={{i}};</span>
                <!-- 4、遍歷的時候都加上:key來區分不同資料,提高vue渲染效率 -->
            </li>

            
        </ul>

        <ul>
            <li v-for="(num,index) in nums" :key="index"></li>
        </ul>

    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
      let app = new Vue({
            el: "#app",
            data: {
                users: [
                { name: '柳巖', gender: '女', age: 21 },
                { name: '張三', gender: '男', age: 18 },
                { name: '范冰冰', gender: '女', age: 24 },
                { name: '劉亦菲', gender: '女', age: 18 },
                { name: '古力娜扎', gender: '女', age: 25 }
                ],
                nums: [1,2,3,4,4]
            },
        })
   </script>
  </body>
</html>

4.2.7 v-if和v-show

v-if元素會被引出,v-show只是隱藏

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <!-- 
        v-if,顧名思義,條件判斷。當得到結果為true時,所在的元素才會被渲染。
        v-show,當得到結果為true時,所在的元素才會被顯示。 
    -->
    <div id="app">
        <button v-on:click="show = !show">點我呀</button>
        <!-- 1、使用v-if顯示 -->
        <h1 v-if="show">if=看到我....</h1>
        <!-- 2、使用v-show顯示 -->
        <h1 v-show="show">show=看到我</h1>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
        
    <script>
        let app = new Vue({
            el: "#app",
            data: {
                show: true
            }
        })
    </script>

  </body>
</html>

4.2.8 v-else和v-else-if

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <!-- 
        v-if,顧名思義,條件判斷。當得到結果為true時,所在的元素才會被渲染。
        v-show,當得到結果為true時,所在的元素才會被顯示。 
    -->
    <div id="app">
        <button v-on:click="show = !show">點我呀</button>
        <!-- 1、使用v-if顯示 -->
        <h1 v-if="show">if=看到我....</h1>
        <!-- 2、使用v-show顯示 -->
        <h1 v-show="show">show=看到我</h1>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
        
    <script>
        let app = new Vue({
            el: "#app",
            data: {
                show: true
            }
        })
    </script>

  </body>
</html>

4.3 Vue計算屬性和偵聽器

4.3.1 計算屬性computed

什麼是計算屬性:屬性不是具體值,而是透過一個函式計算出來的,隨時變化

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <p>原始字串: {{ message }}</p>
      <p>計算後反轉字串: {{ reversedMessage }}</p>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          message: "Runoob!",
        },
        computed: {
          // 計算屬性的 getter
          reversedMessage: function () {
            // `this` 指向 vm 例項
            return this.message.split("").reverse().join("");
          },
        },
      });
    </script>
  </body>
</html>

4.3.2 監聽watch

監聽屬性 watch,我們可以透過 watch 來響應資料的變化。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <p style="font-size: 25px">計數器: {{ counter }}</p>
      <button @click="counter++" style="font-size: 25px">點我</button>
      <br />

      <!-- 某些結果是基於之前資料實時計算出來的,我們可以利用計算屬性。來完成 -->
      <ul>
        <li>
          西遊記; 價格:{{xyjPrice}},數量:<input
            type="number"
            v-model="xyjNum"
          />
        </li>
        <li>
          水滸傳; 價格:{{shzPrice}},數量:<input
            type="number"
            v-model="shzNum"
          />
        </li>
        <li>總價:{{totalPrice}}</li>
        {{msg}}
      </ul>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
      var vm = new Vue({
        el: "#app",
        data: {
          counter: 1,
          xyjPrice: 99.98,
          shzPrice: 98.0,
          xyjNum: 1,
          shzNum: 1,
          msg: "",
        },
        computed: {
          totalPrice() {
            return this.xyjPrice * this.xyjNum + this.shzPrice * this.shzNum;
          },
        },
        watch: {
          xyjNum: function (newVal, oldVal) {
            if (newVal >= 3) {
              this.msg = "庫存超出限制";
              this.xyjNum = 3;
            } else {
              this.msg = "";
            }
          },
        },
      });
      vm.$watch("counter", function (nval, oval) {
        // new old
        alert("計數器值的變化 :" + oval + " 變為 " + nval + "!");
      });
    </script>
  </body>
</html>

4.4過濾器filter

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
       <!-- 過濾器常用來處理文字格式化的操作。過濾器可以用在兩個地方:雙花括號插值和 v-bind 表示式 -->
    <div id="app">
        <ul>
            <li v-for="user in userList">
                {{user.id}} ==> {{user.name}} ==> {{user.gender == 1?"男":"女"}} ==>
                {{user.gender | genderFilter}} ==> {{user.gender | gFilter}}
            </li>
        </ul>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
        
      // 全域性過濾器
      Vue.filter("gFilter", function (val) {
            if (val == 1) {
                return "男~~~";
            } else {
                return "女~~~";
            }
        })

        let vm = new Vue({
            el: "#app",
            data: {
                userList: [
                    { id: 1, name: 'jacky', gender: 1 },
                    { id: 2, name: 'peter', gender: 0 }
                ]
            },
            filters: { // 區域性過濾器,只可以在當前vue例項中使用
                genderFilter(val) {
                    if (val == 1) {
                        return "男";
                    } else {
                        return "女";
                    }
                }
            }
        })

    </script>
  </body>
</html>

image-20220213235041764

4.5 元件化

  • 元件其實也是一個vue例項,因此它在定義時也會接收:data、methods、生命週期函式等
  • 不同的是元件不會與頁面的元素繫結(所以不寫el),否則就無法複用了,因此沒有el屬性。
  • 但是元件渲染需要html模板,所以增加了template屬性,值就是HTML模板
  • data必須是一個函式,不再是一個物件。
  • 全域性元件定義完畢,任何vue例項都可以直接在HTML中透過元件名稱來使用元件了
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <button v-on:click="count++">我被點選了 {{count}} 次</button>

      每個物件都是獨立統計的
      <counter></counter>
      <counter></counter>
      <counter></counter>
      <counter></counter>
      <counter></counter>

      <button-counter></button-counter>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
      //1、全域性宣告註冊一個元件 // counter標籤,代表button
      // 把頁面中<counter>標籤替換為指定的template,而template中的資料用data填充
      Vue.component("counter", {
        template: `<button v-on:click="count++">我被點選了 {{count}} 次</button>`,
        data() {
          // 如果 Vue 沒有這條規則,點選一個按鈕就可能會像如下程式碼一樣影響到其它所有例項:
          return {
            count: 1, // 資料
          };
        },
      });

      //2、區域性宣告一個元件
      const buttonCounter = {
        template: `<button v-on:click="count++">我被點選了 {{count}} 次~~~</button>`,
        data() {
          return {
            count: 1,
          };
        },
      };

      let vm = new Vue({
        el: "#app",
        data: {
          count: 1,
        },
        methods: {},
      });
    </script>
  </body>
</html>

4.6 生命週期鉤子函式

每個vue例項在被建立時都要經過一系列的初始化過程:建立例項,裝載模板、渲染模板等等。vue為生命週期中的每個狀態都設定了鉤子函式(監聽函)。每當vue實列處於不同的生命週期時,對應的函式就會被觸發呼叫。

  • beforeCreate

  • created

  • beforeMount

  • mounted

  • beforeUpdate

  • updated

  • beforeDestroy

  • destroyed

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <span id="num">{{num}}</span>
        <button @click="num++">贊!</button>
        <h2>{{name}},有{{num}}個人點贊</h2>
    </div>

    <script src="../node_modules/vue/dist/vue.js"></script>
    
    <script>
        let app = new Vue({
            el: "#app",
            data: {
                name: "張三",
                num: 100
            },
            methods: {
                show() {
                    return this.name;
                },
                add() {
                    this.num++;
                }
            },
            beforeCreate() {
                console.log("=========beforeCreate=============");
                console.log("資料模型未載入:" + this.name, this.num);
                console.log("方法未載入:" + this.show());
                console.log("html模板未載入:" + document.getElementById("num"));
            },
            created: function () {
                console.log("=========created=============");
                console.log("資料模型已載入:" + this.name, this.num);
                console.log("方法已載入:" + this.show());
                console.log("html模板已載入:" + document.getElementById("num"));
                console.log("html模板未渲染:" + document.getElementById("num").innerText);
            },
            beforeMount() {
                console.log("=========beforeMount=============");
                console.log("html模板未渲染:" + document.getElementById("num").innerText);
            },
            mounted() {
                console.log("=========mounted=============");
                console.log("html模板已渲染:" + document.getElementById("num").innerText);
            },
            beforeUpdate() {
                console.log("=========beforeUpdate=============");
                console.log("資料模型已更新:" + this.num);
                console.log("html模板未更新:" + document.getElementById("num").innerText);
            },
            updated() {
                console.log("=========updated=============");
                console.log("資料模型已更新:" + this.num);
                console.log("html模板已更新:" + document.getElementById("num").innerText);
            }
        });
    </script>
</body>

</html>

4.7 使用Vue腳手架快速開發

(1)全域性安裝webpack

npm install webpack -g

image-20220214003415738

(2)全域性安裝vue腳手架

npm install -g @vue/cli-init

image-20220214003538547

(3)初始化Vue專案

vue init webpack 專案名稱

image-20220214003751232

(4)啟動vue專案

npm run start

image-20220214004002384

4.8整合Element-UI

官網:https://element.eleme.cn/#/zh-CN

(1)安裝 element-ui

 npm i element-ui

image-20220214005153669

(2)引入Element-UI

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);

image-20220214005423073

(3)把elementUi的container佈局容器程式碼拷到App.vue中

image-20220214010329098

(4)修改App.vue中的 ,動態獲取檢視

image-20220214011921297

(5)路由配置

export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld
    },
    {
      path: '/hello',
      name: 'Hello',
      component: Hello
    },
    {
      path: '/table',
      name: 'MyTable',
      component: MyTable
    }
  ]
})

image-20220214012106757

(6)el-menu設定router,並新增跳轉路由

image-20220214012026031

5. 商品服務-三級分類

5.1 遞迴實現商品服務分類

CategoryController:

    /*
    * 查出所有分類 以及子分類,以樹形結構組裝起來
    * */
    @RequestMapping("/list/tree")
    public R listtree() {
        List<CategoryEntity> entityList = categoryService.listWithTree();
        return R.ok().put("data", entityList);
    }

CategoryServiceImpl:

@Override
public List<CategoryEntity> listWithTree() {

    //1、查出所有分類
    List<CategoryEntity> categoryEntities = baseMapper.selectList(null);

    // 2 組裝成父子的樹型結構
    // 2.1 找到所有一級分類
    List<CategoryEntity> level1Menus = categoryEntities.stream().filter((categoryEntity -> {
        return categoryEntity.getParentCid() == 0;
    })).map(menu -> {
        menu.setChildren(getChildren(menu, categoryEntities));
        return menu;
    }).sorted((menu1, menu2) -> {
        return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
    }).collect(Collectors.toList());

    return level1Menus;
}

/*
 * 獲取某一個選單的子選單
 * 在all裡找root的子選單
 * */

private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all) {
    List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
        return categoryEntity.getParentCid() == 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;
}

訪問:http://localhost:11000/product/category/list/tree

image-20220214160011207

5.2 配置閘道器路由與路徑重寫

(1)執行人人開源程式

image-20220214160511157

(2)增加商品系統選單

image-20220214160710002

給商品系統增加商品維護子選單

選單路由對應前端工程的路徑

image-20220214160900531

結果:

image-20220214161044924

(3)在renren-fast-vue增加商品維護的程式碼,路徑匹配配置的選單路由

image-20220214161328682

(4)前端修改閘道器伺服器的地址

image-20220214162203675

(5)將renren-fast註冊到nacos中心

cloud:
  acos:
     discovery:
        server-addr: 127.0.0.1:8848
application:
  name: renren-fast

image-20220214162510778

開啟服務的註冊發現

@EnableDiscoveryClient

image-20220214162614475

引入:gulimall-common

<dependency>
<groupId>com.peng.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
   </dependency>

(6)配置路由

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

匯入spring-cloud-starter-loadbalancer依賴

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

image-20220214172348011

(7)重新執行,啟動gulimall-gateway和renren-fast工程:

image-20220214164516579

(8)檢視獲取驗證碼介面,透過閘道器正常訪問

image-20220214172459200

5.3 閘道器統一配置跨域

(1)gulimall-gateway:使用UrlBasedCorsConfigurationSource和CorsConfiguration

@Configuration // gateway
public class GulimallCorsConfiguration {

    @Bean // 新增過濾器
    public CorsWebFilter corsWebFilter(){
        // 基於url跨域,選擇reactive包下的
        UrlBasedCorsConfigurationSource source=new UrlBasedCorsConfigurationSource();
        // 跨域配置資訊
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        // 允許跨域的頭
        corsConfiguration.addAllowedHeader("*");
        // 允許跨域的請求方式
        corsConfiguration.addAllowedMethod("*");
        // 允許跨域的請求來源
        corsConfiguration.addAllowedOrigin("*");
        // 是否允許攜帶cookie跨域
        corsConfiguration.setAllowCredentials(true);

        // 任意url都要進行跨域配置
        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);
    }
}

image-20220214173327335

(2)註釋renren-fast配置的跨域配置

image-20220214173557046

(3)跨域配置完成,登入成功

image-20220214173804961

5.4 樹形展示三級分類資料

(1)使用nacos遠端配置

新建product名稱空間

image-20220214181401616

新建product配置,配置按服務隔離

datasource.yml

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.56.10:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis.yml

mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1
      logic-not-delete-value: 0

other.yml

server:
  port: 11005

效果圖

image-20220214181450891

gulimall-product主程式新增@EnableDiscoveryClient

image-20220214182210468

(3)啟動執行,確保gulimall-product,gulimall-gateway,renren-fast註冊到nacos中

image-20220214182711314

(4)gulimall-product 配置閘道器路由

admin_route和product_route都是路徑匹配,需要將product_route放前面負責可能會匹配到admin_route

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

image-20220214182331250

(5)前端呼叫介面

5.5 三級分類刪除頁面效果

前端程式碼不過多展示,主要是去官方文件檢視

<el-tree
      :data="menus"
      :props="defaultProps"
      @node-click="handleNodeClick"
      :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)"
            >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>

5.6 邏輯刪除

(1)MyBatis配置邏輯刪除

我這裡是用的nacos遠端配置

image-20220214220630272

logic-delete-value: 1代表1是刪除

logic-not-delete-value: 0代表0是未刪除

mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1
      logic-not-delete-value: 0

image-20220214220714212

(2)實體類的邏輯刪除欄位加上TableLogic,這裡@TableLogic(value="1")代表1是未刪除,0是已刪除,因為資料庫跟全域性的配置可能是反的,所以可以單獨配置

image-20220214220941214

錯誤:com.google.common

com.google.common.collect.Sets$SetView.iterator()Lcom/google/common/collect/UnmodifiableIterator;

匯入:

<dependency>
   <groupId>com.google.guava</groupId>
   <artifactId>guava</artifactId>
   <version>20.0</version>
   </dependency>

錯誤:配置完路由後,前端訪問503

需要匯入依賴:

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

6.商品服務-品牌管理

6.1使用逆向工程前後端程式碼

(1)增加品牌管理選單

image-20220215202438654

(2)逆向生成的前端程式碼複製到vue工程product目錄下

image-20220215202746691

(3)取消Eslint語法檢查

image-20220215195646152

(4)取消許可權檢查

image-20220215204605575

(4)重新啟動前端工程

6.2 最佳化快速顯示開關

前端程式碼:

加上el-switch開關,並在@change方法中傳送請求

<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>

updateBrandStatus:

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, showStatus:showStatus?1:0 }, false)
      }).then(({ data }) => {
        this.$message({
          type: "success",
          message: "狀態更新成功"
        });
      });
},

java後端程式碼(自動生產的):

/**
 * 修改
 */
@RequestMapping("/update")
//@RequiresPermissions("product:category:update")
public R update(@RequestBody CategoryEntity category){
    categoryService.updateById(category);
    return R.ok();
}

6.3 阿里雲物件儲存開通與使用

(1)開啟自己的阿里雲工作臺

image-20220215205228972

(2)建立Bucket

image-20220215205613901

(3)檔案管理上傳圖片

image-20220215210237165

(4)點選詳情,複製路徑

image-20220215210339185

(5)瀏覽器開啟時候能正常下載

6.4 阿里雲物件儲存-OSS測試

6.4.1 阿里雲配置

地址:https://help.aliyun.com/document_detail/32013.html

(1)新增AccessKey

image-20220215214658550

(2)使用子使用者

image-20220215214930967

(3)建立訪問使用者

image-20220215214741375

(4)驗證後獲取AccessKey ID和AccessKey Secret

image-20220215214834349

(5)賬號分配許可權

image-20220215215603451

6.4.2 java程式碼測試

(1)匯入阿里雲依賴

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.10.2</version>
</dependency>

(2)EndPoint地域節點

oss-cn-beijing.aliyuncs.com

image-20220215215746871

(3)AccessKey ID和AccessKey Secret

image-20220215220746382

(4)Bucket

image-20220215220947464

(2)測試程式碼

@Test
public  void testUpload(){
    // Endpoint以華東1(杭州)為例,其它Region請按實際情況填寫。
    //oss-cn-beijing.aliyuncs.com
    String endpoint = "https://oss-cn-beijing.aliyuncs.com";
    // 阿里雲賬號AccessKey擁有所有API的訪問許可權,風險很高。強烈建議您建立並使用RAM使用者進行API訪問或日常運維,請登入RAM控制檯建立RAM使用者。
    String accessKeyId = "LTAI5t6WczYReBvbRjY1cscr";
        String accessKeySecret = "ROidZCyNL0mMexts35tDxLI8VYZbMF";
    // 填寫Bucket名稱,例如examplebucket。
    String bucketName = "peng-aliyun";
    // 填寫Object完整路徑,例如exampledir/exampleobject.txt。Object完整路徑中不能包含Bucket名稱。
    //上傳後的名字
    String objectName = "1.jpg";
    // 填寫本地檔案的完整路徑,例如D:\\localpath\\examplefile.txt。
    // 如果未指定本地路徑,則預設從示例程式所屬專案對應本地路徑中上傳檔案流。
    String filePath= "C:\\Users\\Peng\\Desktop\\temporary\\testpic\\1.jpg";
    // 建立OSSClient例項。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    try {
        InputStream inputStream = new FileInputStream(filePath);
        // 建立PutObject請求。
        ossClient.putObject(bucketName, objectName, inputStream);
    } catch (OSSException oe) {
        System.out.println("Caught an OSSException, which means your request made it to OSS, "
                + "but was rejected with an error response for some reason.");
        System.out.println("Error Message:" + oe.getErrorMessage());
        System.out.println("Error Code:" + oe.getErrorCode());
        System.out.println("Request ID:" + oe.getRequestId());
        System.out.println("Host ID:" + oe.getHostId());
    } catch (ClientException ce) {
        System.out.println("Caught an ClientException, which means the client encountered "
                + "a serious internal problem while trying to communicate with OSS, "
                + "such as not being able to access the network.");
        System.out.println("Error Message:" + ce.getMessage());
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        if (ossClient != null) {
            ossClient.shutdown();
        }
    }
    System.out.printf("上傳完成");
}

6.4.3 Aliyun Spring Boot OSS 上傳

地址:https://github.com/alibaba/aliyun-spring-boot/tree/master/aliyun-spring-boot-samples/aliyun-oss-spring-boot-sample

(1)gulimall-common匯入依賴,

只匯入aliyun-oss-spring-boot-starter,版本過高程式執行不起來,需要匯入aliyun-java-sdk-core

aliyun-spring-boot-dependencies需要aliyun-oss-spring-boot-starter一起匯入,不然會報錯

        <!--阿里雲端儲存-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>aliyun-oss-spring-boot-starter</artifactId>
        </dependency>
        <!--匯入新的依賴-->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
        </dependency>
        
        
        <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>aliyun-spring-boot-dependencies</artifactId>
                <version>1.0.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

(2)阿里雲端儲存配置

alibaba:
  cloud:
    access-key: LTAI5t6WczYReBvbRjY1cscr
    secret-key: ROidZCyNL0mMexts35tDxLI8VYZbMF
    oss:
      endpoint: https://oss-cn-beijing.aliyuncs.com

image-20220215232743833

(3)測試程式碼:

    @Autowired
    OSSClient ossClient;
    @Test
    public  void testAliyunUpload(){
        //下載
        //ossClient.getObject(new GetObjectRequest("peng-aliyun", "2,.jpg"), new File("C:\\Users\\Peng\\Desktop\\temporary\\testpic\\2.jpg"));
        //上傳
        ossClient.putObject("peng-aliyun", "1.jpg", new File("C:\\Users\\Peng\\Desktop\\temporary\\testpic\\1.jpg"));
        ossClient.shutdown();
    }

image-20220215232828105

6.5 阿里雲物件儲存-服務端簽名後直傳

(1)建立第三方spring服務模組

image-20220215233321017

(2)選中spring Web和OpenFeign

image-20220215233355627

(3)將gulimall-common阿里雲端儲存相關服務匯入到gulimall-third-party

image-20220215233759603

(4)把gulimall-third-party註冊到nacos服務中

配置nacos相關配置

spring.application.name=gulimall-third-party
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=b719fb01-6656-4b1f-872e-b6ddbf170214

## 分組 預設DEFAULT_GROUP
spring.cloud.nacos.config.group=dev

spring.cloud.nacos.config.extension-configs[0].data-id=oss.yml
spring.cloud.nacos.config.extension-configs[0].group=dev
spring.cloud.nacos.config.extension-configs[0].refresh=true

image-20220215235001563

增加gulimall-third-party名稱空間和oss.yml,名稱空間為dev

image-20220215234524522

oss.yml配置檔案

alibaba:
  cloud:
    access-key: LTAI5t6WczYReBvbRjY1cscr
    secret-key: ROidZCyNL0mMexts35tDxLI8VYZbMF
    oss:
      endpoint: https://oss-cn-beijing.aliyuncs.com

(5)增加application.yml

spring:
  cloud:
    acos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: gulimall-third-party

server:
  port: 30000

image-20220215235725753

(6)引入com.peng.gulimall排除掉mybatis,暫時用不上

<dependency>
    <groupId>com.peng.gulimall</groupId>
    <artifactId>gulimall-common</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <exclusions>
        <exclusion>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </exclusion>
    </exclusions>
</dependency>

(7)開啟服務發現

image-20220215235801725

(8)新增other.yml作為其他配置

server:
  port: 30000

other.yml

image-20220216002232050

(9)服務端簽名後直傳文件

地址:https://help.aliyun.com/document_detail/31926.html

image-20220216003903836

https://help.aliyun.com/document_detail/91868.htm?spm=a2c4g.11186623.0.0.16073967YFZamz#concept-ahk-rfz-2fb

image-20220216004038234

(10)OssController

package com.peng.gulimall.thirdparty.controller;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
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 ("${alibaba.cloud.oss.endpoint}")
    String endpoint ;

    @Value("${alibaba.cloud.oss.bucket}")
    String bucket ;

    @Value("${alibaba.cloud.access-key}")
    String accessId ;
    @Value("${alibaba.cloud..secret-key}")
    String accessKey ;

    @RequestMapping("/oss/policy")
    public Map<String, String> policy(){

        String host = "https://" + bucket + "." + endpoint; // host的格式為 bucketname.endpoint

        String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        String dir = format; // 使用者上傳檔案時指定的字首。

        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) {
            // Assert.fail(e.getMessage());
            System.out.println(e.getMessage());
        } finally {
            ossClient.shutdown();
        }
        return respMap;
    }

}

(11)配置路由

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

(12)訪問:http://localhost:88/api/thirdparty/oss/policy

image-20220216013018287

6.6 OSS前後端聯調

(1)跨域配置

  • 登入OSS管理控制檯
  • 單擊Bucket列表,之後單擊目標Bucket名稱。
  • 單擊*許可權管理* > *跨域設定*,在跨域設定區域單擊設定**。

image-20220216014623835

(2)前端配置Bucket域名

image-20220216015748916

(3)前端引入並使用上傳元件

image-20220216020345769

(4)檢視阿里雲,目錄和檔案都成功建立

image-20220216020441284

6.7 Vue表單校驗&自定義校驗

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"
          }
        ]
      }

6.8 JSR303檢驗

6.8.1 JSR303資料校驗

(1)匯入依賴

<!--jsr3引數校驗器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

(2)使用註解進行校驗

  • @NotNull 該屬性不能為null

  • @NotEmpty 該欄位不能為null或""

  • @NotBlank 不能為空,不能僅為一個空格

(3)@Valid

這裡內建異常的意思是發生異常時返回的json不是我們的R物件,而是mvc的內建類

  @RequestMapping("/save")
  //@RequiresPermissions("product:brand:save")
  public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);

      return R.ok();
  }

(4)自定義錯誤訊息

/**
* 品牌名
*/
@NotBlank(message = "品牌名必須非空")
private String name;

(5)ValidationMessages_zh_CN.properties

image-20220216190258731

(6)Entity校驗認證

package com.peng.gulimall.product.entity;

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

import java.io.Serializable;
import java.util.Date;


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

import javax.validation.constraints.*;

/**
 * 品牌
 * 
 * @author peng
 * @email pengpeng6135@163.com
 * @date 2022-02-12 01:03:04
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 品牌id
	 */
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 */
	@NotBlank(message = "品牌名必須非空")
	private String name;
	/**
	 * 品牌logo地址
	 */
	@NotEmpty()
	@URL(message = "logo必須是一個合法的url地址")
	private String logo;
	/**
	 * 介紹
	 */
	private String descript;
	/**
	 * 顯示狀態[0-不顯示;1-顯示]
	 */
	private Integer showStatus;
	/**
	 * 檢索首字母
	 */
	@NotEmpty()
	@Pattern(regexp = "^[a-zA-Z]$",message = "檢索首字母必須是一個字母")
	private String firstLetter;
	/**
	 * 排序
	 */
	@NotEmpty()
	@Min(value = 0,message = "排序必須大於等於0")
	private Integer sort;

}

(7)區域性異常處理BindingResult

    /**
     * 儲存
     */
    @RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result) {

        if (result.hasErrors()) {
            Map<String, String> map = new HashMap<>();
            //1、獲取檢驗的錯誤結果
            result.getFieldErrors().forEach((item) -> {
                //FieldError 獲得錯誤提示
                String message = item.getDefaultMessage();
                //獲得錯誤屬性的名字
                String field = item.getField();
                map.put(field, message);
            });
            return R.error(400, "提交的資料不合法").put("data", map);
        } else {
            brandService.save(brand);
        }
        return R.ok();
    }

6.8.2 統一異常處理

(1)自定義錯誤碼狀態

統一在gulimall-common中,別的服務也要引用

package com.peng.common.exception;

import com.peng.common.constant.ProductConstant;

/***
 * 錯誤碼和錯誤資訊定義類
 * 1. 錯誤碼定義規則為5為數字
 * 2. 前兩位表示業務場景,最後三位表示錯誤碼。例如:100001。10:通用 001:系統未知異常
 * 3. 維護錯誤碼後需要維護錯誤描述,將他們定義為列舉形式
 * 錯誤碼列表:
 *  10: 通用
 *      001:引數格式校驗
 *  11: 商品
 *  12: 訂單
 *  13: 購物車
 *  14: 物流
 */
public enum BizCodeEnum {
    UNKNOWN_EXCEPTION(10000, "系統未知異常"),
    VAILD_EXCEPTION(10001, "引數格式校驗失敗"),
    SMS_CODE_EXCEPTION(10002, "驗證碼獲取頻率太高,稍後再試"),
    TO_MANY_REQUEST(10003, "請求流量過大"),
    SMS_SEND_CODE_EXCEPTION(10403, "簡訊傳送失敗"),
    USER_EXIST_EXCEPTION(15001, "使用者已經存在"),
    PHONE_EXIST_EXCEPTION(15002, "手機號已經存在"),
    LOGINACTT_PASSWORD_ERROR(15003, "賬號或密碼錯誤"),
    SOCIALUSER_LOGIN_ERROR(15004, "社交賬號登入失敗"),
    NOT_STOCK_EXCEPTION(21000, "商品庫存不足"),
    PRODUCT_UP_EXCEPTION(11000,"商品上架異常");

    private int code;

    private String msg;

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

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

image-20220216191936139

(2)統一異常處理GulimallExceptionControllerAdvice

package com.peng.gulimall.product.exception;

import com.peng.common.exception.BizCodeEnum;
import com.peng.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
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和@ControllerAdvice的關係類似於@RestController
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")//管理的controller
public class GulimallExceptionControllerAdvice {

    @ExceptionHandler(value = Exception.class) // 也可以返回ModelAndView
    public R handleValidException(MethodArgumentNotValidException exception){

        Map<String,String> map=new HashMap<>();
        // 獲取資料校驗的錯誤結果
        BindingResult bindingResult = exception.getBindingResult();
        // 處理錯誤
        bindingResult.getFieldErrors().forEach(fieldError -> {
            String message = fieldError.getDefaultMessage();
            String field = fieldError.getField();
            map.put(field,message);
        });

        log.error("資料校驗出現問題{},異常型別{}",exception.getMessage(),exception.getClass());

        return R.error(400,"資料校驗出現問題").put("data",map);
    }

    @ExceptionHandler(value = Throwable.class)//異常的範圍更大
    public R handleException(Throwable throwable) {
        log.error("未知異常{},異常型別{}",
                throwable.getMessage(),
                throwable.getClass());

        return R.error(BizCodeEnum.UNKNOWN_EXCEPTION.getCode(),
                BizCodeEnum.UNKNOWN_EXCEPTION.getMsg());
    }
}

image-20220216192058960

6.8.3JSR303分組檢驗

(1)新增三個驗證介面

image-20220216193035945

(2)註解驗證的時候加上分組

例如品牌id:新增的時候必須加上id,新增的時候不需要

image-20220216193143510

(3)controller方法必須使用Validated指定分組,負責不生效

image-20220216193438387

6.8.4 自定義校驗註解

(1)建立ListValue,繼承Constraint,並指定校驗器

port java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * <p>Title: ListValue</p>
 * Description:JSR303自定義註解 必須有前三個方法
 * date:2020/6/1 23:25
 */
@Documented
// 指定校驗器   這裡可以指定多個不同的校驗器
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
    // 1 使用該屬性去Validation.properties中取
    String message() default "{com.atguigu.common.valid.ListValue.message}";

    // 2
    Class<?>[] groups() default { };

    // 3
    Class<? extends Payload>[] payload() default { };

    int[] vals() default { };
}

(2)ListValueConstraintValidator:獲取傳入的引數,並在isValid進行驗證

package com.peng.common.valid;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;

public class ListValueConstraintValidator
        implements ConstraintValidator<ListValue,Integer> {//泛型引數<校驗註解,標註欄位型別>

    /**
     * set 裡面就是使用註解時規定的值, 例如: @ListValue(vals = {0,1})  set= {0,1}
     */
    private Set<Integer> set = new HashSet<>();

    //初始化方法
    @Override
    public void initialize(ListValue constraintAnnotation) {
        // 獲取java後端寫好的限制
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }
    }

    /**
     * 判斷是否校驗成功
     * @param value 需要校驗的值
     *              判斷這個值再set裡面沒
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        // 每次請求傳過來的值是否在java後端限制的值裡
        return set.contains(value);
    }
}

(3)呼叫

ListValue只允許showStatus是0或1

/**
 * 顯示狀態[0-不顯示;1-顯示]
 */
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals = {0,1}, groups = {AddGroup.class, UpdateGroup.class, UpdateStatusGroup.class})
private Integer showStatus;

7.規格引數&銷售屬性 概念

  • SPU:standard product unit(標準化產品單元):是商品資訊聚合的最小單位,是一組可複用、易檢索的標準化資訊的集合,該集合描述了一個產品的特性。

  • 如iphoneX是SPU
    SKU:stock keeping unit(庫存量單位):庫存進出計量的基本單元,可以是件/盒/托盤等單位。SKU是對於大型連鎖超市DC配送中心物流管理的一個必要的方法。現在已經被引申為產品統一編號的簡稱,每種產品對應有唯一的SKU號。

  • 如iphoneX 64G 黑色 是SKU
    基礎屬性:同一個SPU擁有的特性叫基本屬性。如機身長度,這個是手機共用的屬性。而每款手機的屬性值不同

  • 也可以叫規格引數
    銷售屬性:能決定庫存量的叫銷售屬性。如顏色

8 屬性分組

8.1 前端元件抽取&父子元件互動

透過$emit向父元件傳送事件,

image-20220216205521677

引入註冊完成後呼叫,注意方法名要和$emit傳入的一致

image-20220216205749781

8.2 分類查詢介面

controller

傳入的catelogId需要透過@PathVariable註解獲取

@RequestMapping("/list/{catelogId}")
public R list(@RequestParam Map<String, Object> params, @PathVariable("catelogId") Long catelogId){
    PageUtils page =  attrGroupService.queryPage(params, catelogId);
    return R.ok().put("page", page);
}

AttrServiceImpl

透過QueryWrapper拼接查詢條件

@Override
public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
    String key = (String) params.get("key");
    QueryWrapper<AttrGroupEntity> wrapper = new QueryWrapper<AttrGroupEntity>();
    //拼接條件查詢
    if (!StringUtils.isEmpty(key)) {
        wrapper.and((obj) -> {
            obj.eq("attr_group_id", key).or().like("attr_group_name", key);
        });
    }

    //查詢所有
    if (catelogId == 0) {
        IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params), wrapper);
        return new PageUtils(page);
    } else {
        wrapper.eq("catelog_id", catelogId);
        IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params), wrapper);
        return new PageUtils(page);
    }
}

8.3分組新增&級聯選擇器

(1)三級分類沒有childdren時候不顯示,透過設定JsonInclude註解

/**
 * 子分類
 * 不是資料表裡的屬性
 * 將當前選單的子分類都儲存到裡面
 * 
 * */
@TableField(exist =false)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<CategoryEntity> children;

image-20220216220016968

8.4 分組修改&級聯選擇器回顯

(1)屬性分組實體增加欄位,用於顯示完整的類別路徑

/**
 * 三級分類修改的時候回顯路徑
 */
@TableField(exist = false)
private Long[] catelogPath;

(2)後端透過

 @Override
    public Long[] findCateLogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        paths = findParentPath(catelogId,paths);
        // 收集的時候是順序 前端是逆序顯示的 所以用集合工具類給它逆序一下
        Collections.reverse(paths);
        return  paths.toArray(new Long[paths.size()]);
    }

    private  List<Long> findParentPath(Long catlogId, List<Long> paths) {
        // 1、收集當前節點id
        paths.add(catlogId);
        CategoryEntity categoryEntity = this.getById(catlogId);
        if (categoryEntity.getParentCid() != 0) {
            findParentPath(categoryEntity.getParentCid(), paths);
        }
        return paths;
    }

8.5 品牌管理

(1)mybatis-plus分頁外掛

地址:https://baomidou.com/pages/97710a/#paginationinnerinterceptor

<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

(2)MybatisConfig

package com.peng.gulimall.product.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@EnableTransactionManagement
@MapperScan("com.peng.gulimall.product.dao")
@Configuration
public class MybatisConfig {

    //    PaginationInterceptor
    @Bean
    public PaginationInnerInterceptor paginationInterceptor() {
        PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();
        // 設定請求的頁面大於最大頁後操作, true調回到首頁,false 繼續請求  預設false
        paginationInterceptor.setOverflow(true);
        // 設定最大單頁限制數量,預設 500 條,-1 不受限制
        paginationInterceptor.setMaxLimit(1000L);
        return paginationInterceptor;
    }

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

//    @Bean
//    public ConfigurationCustomizer configurationCustomizer() {
//        return configuration -> configuration.setUseDeprecatedExecutor(false);
//    }
}

(3)分頁條件查詢品牌

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

        String key = (String) params.get("key");
        QueryWrapper<BrandEntity> queryWrapper = new QueryWrapper<>();
        if (!StringUtil.isNullOrEmpty(key)) {
            queryWrapper.eq("brand_id", key).or().like("name", key);
        }

        IPage<BrandEntity> page = this.page(new Query<BrandEntity>().getPage(params), queryWrapper);

        return new PageUtils(page);
    }

9 平臺屬性

9.1 規格引數新增與VO

BeanUtils.copyProperties:vo物件和entity物件轉換

@Override
public void saveAttr(AttrVo attrVo) {
    AttrEntity attrEntity = new AttrEntity();
    //vo物件和entity物件轉換
    BeanUtils.copyProperties(attrVo, attrEntity);
    //1、儲存基本資料
    this.save(attrEntity);
    //2、儲存關聯關係
    if (attrVo.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode() && attrVo.getAttrGroupId() != null) {
        AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
        relationEntity.setAttrGroupId(attrVo.getAttrGroupId());
        relationEntity.setAttrId(attrEntity.getAttrId());
        relationEntity.setAttrSort(0);
        relationDao.insert(relationEntity);
    }

}

9.2 規格引數列表

AttrController

@GetMapping("/{attrType}/list/{catelogId}")
public R baseAttrList(@RequestParam Map<String, Object> params ,@PathVariable("catelogId") Long catelogId, @PathVariable("attrType") String attrType){

    PageUtils page = attrService.queryBaseAttrPage(params, catelogId, attrType);
    return R.ok().put("page", page);
}

AttrServiceImpl

    @Resource
    private AttrAttrgroupRelationDao relationDao;
    @Resource
    private AttrGroupDao attrGroupDao;
    @Resource
    private CategoryDao categoryDao;

  
  @Override
    public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId, String attrType) {
        // 傳入的attrType是"base"或其他,但是資料庫存的是 "0"銷售 / "1"基本
        // 屬性都在pms_attr表中混合著
        QueryWrapper<AttrEntity> wrapper =
                new QueryWrapper<AttrEntity>().eq("attr_type", "base".equalsIgnoreCase(attrType)
                        ?ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()
                        :ProductConstant.AttrEnum.ATTR_TYPE_SALE.getCode());

        // 如果引數帶有分類id,則按分類查詢
        if (catelogId != 0L ) {
            wrapper.eq("catelog_id", catelogId);
        }
        // 支援模糊查詢,用id或者name查
        String key = (String) params.get("key");
        if (!StringUtils.isEmpty(key)) {
            wrapper.and((w) -> {
                w.eq("attr_id", key).or().like("attr_name", key);
            });
        }
        // 正式查詢滿足條件的屬性
        IPage<AttrEntity> page = this.page(
                new Query<AttrEntity>().getPage(params),
                wrapper
        );
        List<AttrEntity> records = page.getRecords();
        PageUtils pageUtils = new PageUtils(page);

        // 查到屬性後還要結合分類名字、分組名字(分類->屬性->分組) 封裝為AttrRespVo物件
        List<AttrRespVo> attrRespVos = records.stream().map((attrEntity) -> {
            AttrRespVo attrRespVo = new AttrRespVo();
            BeanUtils.copyProperties(attrEntity, attrRespVo);

            // 1.設定分類和分組的名字  先獲取中間表物件  給attrRespVo 封裝分組名字
            if("base".equalsIgnoreCase(attrType)){ // 如果是規格引數才查詢,或者說銷售屬性沒有屬性分組,只有分類
                // 根據屬性id查詢關聯表,得到其屬性分組
                AttrAttrgroupRelationEntity entity = relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));
                if (entity != null && entity.getAttrGroupId() != null) {
                    AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(entity);
                    // 設定屬性分組的名字
                    attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
                }
            }

            // 2.查詢分類id 給attrRespVo 封裝三級分類名字
            CategoryEntity categoryEntity = categoryDao.selectById(attrEntity.getCatelogId());
            if (categoryEntity != null) {
                attrRespVo.setCatelogName(categoryEntity.getName());
            }
            return attrRespVo;
        }).collect(Collectors.toList());
        pageUtils.setList(attrRespVos);
        return pageUtils;
    }

9.3 規格引數修改

@Override
public void updateAttr(AttrVo attrVo) {
    AttrEntity attrEntity = new AttrEntity();
    BeanUtils.copyProperties(attrVo, attrEntity);
    this.updateById(attrEntity);

    // 基本型別才進行修改
    if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) {
        // 修改分組關聯
        AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();

        relationEntity.setAttrGroupId(attrVo.getAttrGroupId());
        relationEntity.setAttrId(attrVo.getAttrId());
        // 查詢 attr_id 在 pms_attr_attrgroup_relation 表中是否已經存在 不存在返回0 表示這是新增 反之返回1 為修改 [這裡的修改可以修復之前沒有設定上的屬性]
        Long count = relationDao.selectCount(new UpdateWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrVo.getAttrId()));
        if(count > 0){
            relationDao.update(relationEntity, new UpdateWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrVo.getAttrId()));
        }else {
            relationDao.insert(relationEntity);
        }
    }
}

9.4 銷售屬性維護

主要步驟

  • 1.base是基礎屬性,sale是銷售屬性,根據attrType篩選AttrEntity(屬性)
  • 2.如果查詢的是基礎屬性需要設定分類名稱

base是基礎屬性,sale是銷售屬性,根據attrType篩選AttrEntity(屬性)

image-20240702042807431

基礎屬性需要設定分類名稱

image-20240702042926335

9.5查詢分組未關聯的屬性&刪除關聯

主要步驟

  • 1.獲取當前分組的分類
  • 2.獲取分類下所有屬性分組
  • 3.獲取分類下所有屬性分組的屬性
  • 4.篩選已經繫結的分組和分組屬性

AttrAttrgroupRelationDao

package com.peng.gulimall.product.dao;

import com.peng.gulimall.product.entity.AttrAttrgroupRelationEntity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 屬性&屬性分組關聯
 * 
 * @author peng
 * @email pengpeng6135@163.com
 * @date 2022-02-12 01:03:04
 */
@Mapper
public interface AttrAttrgroupRelationDao extends BaseMapper<AttrAttrgroupRelationEntity> {

    void deleteBatchRelation(@Param("entities") List<AttrAttrgroupRelationEntity> entities);
}

AttrAttrgroupRelationDao.xml

<!-- 傳送批次刪除語句 -->
<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>

9.6 查詢分組未關聯的屬性

@Override
public PageUtils getNoRelationAttr(Map<String, Object> params, Long attrgroupId) {
    //1、當前分組只能關聯自己所屬的分類裡面的所有屬性
    AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupId);
    Long catelogId = attrGroupEntity.getCatelogId();
    // 2、當前分組只能別的分組沒有引用的屬性                                                                         並且這個分組的id不是我當前正在查的id
    //2.1)、當前分類下的其他分組
    List<AttrGroupEntity> group = attrGroupDao.selectList(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));
    // 得到當前分類下面的所有分組id
    List<Long> collect = group.stream().map(item -> {
        return item.getAttrGroupId();
    }).collect(Collectors.toList());

    //2.2)、查詢這些分組關聯的屬性
    List<AttrAttrgroupRelationEntity> groupId = relationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>().in("attr_group_id", collect));
    // 再次獲取跟這些分組有關的屬性id的集合
    List<Long> attrIds = groupId.stream().map(item -> {
        return item.getAttrId();
    }).collect(Collectors.toList());

    //2.3)、從當前分類的所有屬性中移除這些屬性;[因這些分組已經存在被選了 就不用再顯示了]
    QueryWrapper<AttrEntity> wrapper = new QueryWrapper<AttrEntity>().eq("catelog_id", catelogId).eq("attr_type", ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode());
    if(attrIds != null && attrIds.size() > 0){
        wrapper.notIn("attr_id", attrIds);
    }
    // 當搜尋框中有key並且不為空的時候 進行模糊查詢
    String key = (String) params.get("key");
    if(!StringUtils.isEmpty(key)){
        wrapper.and((w)->{
            w.eq("attr_id",key).or().like("attr_name",key);
        });
    }
    // 將最後返回的結果進行封裝
    IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), wrapper);

    PageUtils pageUtils = new PageUtils(page);
    return pageUtils;
}

9.7 新增分組與屬性關聯

規格引數(基礎屬性)新增和修改時後端沒有獲取值型別這個欄位

image-20240702215807919

後端的EntityVo給商品屬性加上值型別這個欄位

image-20240702215610067

AttrAttrgroupRelationServiceImpl

    @Override
    public void saveBatch(List<AttrGroupRelationVo> vos) {
        // 對拷資料 然後批次儲存
        List<AttrAttrgroupRelationEntity> entities = vos.stream().map(item -> {
            AttrAttrgroupRelationEntity entity = new AttrAttrgroupRelationEntity();
            BeanUtils.copyProperties(item, entity);
            entity.setAttrSort(0);
            return entity;
        }).collect(Collectors.toList());
        this.saveBatch(entities);
    }

10 新增商品

10.1除錯會員等級相關介面

主要步驟:

  • 複製會員等級頁面的前端程式碼
  • 後端自動化工程生成程式碼(前面已完成)
  • 配置gulimall-gateway閘道器服務,給會員服務新增閘道器配置
  • 新增會員等級的測試資料

(1)gulimall-member開啟服務與發現等註解

image-20220501234807936

(2)gulimall-member配置路由

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

image-20220501234947041

(3)執行

image-20220502000233121

10.2獲取分類關聯的品牌

選擇分類時,顯示分類的所有品牌

主要步驟:

  • 根據分類Id(catId)查詢CategoryBrandRelationEntity分類和品牌對映表,獲取所有的分類和品牌對映關係

  • 迴圈分類和品牌對映關係,查詢每個品牌,返回品牌列表集合

  • 流式程式設計品牌列表轉換vo返回給前端

image-20240702222350396

根據分類Id(catId)查詢CategoryBrandRelationEntity分類和品牌對映表,獲取所有的分類和品牌對映關係

迴圈分類和品牌對映關係,查詢每個品牌,返回品牌列表集合

image-20240702223140838

流式程式設計品牌列表轉換vo返回給前端

image-20240702223209972

CategoryBrandRelationController

    @GetMapping("/brands/list")
    public R relationBrandsList(@RequestParam(value = "catId",required = true) Long catId){
        List<BrandEntity> vos = categoryBrandRelationService.getBrandsByCatId(catId);
        List<BrandVo> collect = vos.stream().map(item -> {
            BrandVo vo = new BrandVo();
            vo.setBrandId(item.getBrandId());
            vo.setBrandName(item.getName());
            return vo;
        }).collect(Collectors.toList());

        return R.ok().put("data", collect);
    }

CategoryBrandRelationServiceImpl

@Override
public List<BrandEntity> getBrandsByCatId(Long catId) {
    List<CategoryBrandRelationEntity> catelogId = categoryBrandRelationDao.selectList(new QueryWrapper<CategoryBrandRelationEntity>().eq("catelog_id", catId));
    // 根據品牌id查詢詳細資訊
    List<BrandEntity> collect = catelogId.stream().map(item -> {
        Long brandId = item.getBrandId();
        BrandEntity entity = brandService.getById(brandId);
        return entity;
    }).collect(Collectors.toList());

    return collect;
}

10.3獲取分類下的所有分組及屬性

獲取分類的規格引數(基本屬性)

主要步驟

  • 根據商品分類id(catelogId)查詢改商品下的所有屬性分組
  • 遍歷所有屬性分組,根據屬性分組和規格引數(基本屬性)的對映表查詢所有的規格引數(基本屬性)
  • 返回改分組下的所有屬性

image-20240702224607372

根據商品分類id(catelogId)查詢改商品下的所有屬性分組

遍歷所有屬性分組,根據屬性分組和規格引數(基本屬性)的對映表查詢所有的規格引數(基本屬性)

image-20240702225353329

根據屬性熟屬性分組attrgroupId查詢所有規格引數(基礎屬性)

image-20240702225428744

AttrGroupController

@GetMapping("/{catelogId}/withattr")
public R getAttrGroupWithAttrs(@PathVariable("catelogId") Long catelogId){
    // 1.查詢當前分類下的所有屬性分組
    List<AttrGroupWithAttrsVo> vos = attrGroupService.getAttrGroupWithAttrByCatelogId(catelogId);
    // 2.查詢每個分組的所有資訊
    return R.ok().put("data", vos);
}

AttrGroupServiceImpl

@Override
public List<AttrGroupWithAttrsVo> getAttrGroupWithAttrByCatelogId(Long catelogId) {
    // 1.查詢這個品牌id下所有分組
    List<AttrGroupEntity> attrGroupEntities = this.list(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));

    // 2.查詢所有屬性
    List<AttrGroupWithAttrsVo> collect = attrGroupEntities.stream().map(group ->{
        // 先對拷分組資料
        AttrGroupWithAttrsVo attrVo = new AttrGroupWithAttrsVo();
        BeanUtils.copyProperties(group, attrVo);
        // 按照分組id查詢所有關聯屬性並封裝到vo
        List<AttrEntity> attrs = attrService.getRelationAttr(attrVo.getAttrGroupId());
        attrVo.setAttrs(attrs);
        return attrVo;
    }).collect(Collectors.toList());
    return collect;
}

AttrServiceImpl

@Override
public List<AttrEntity> getRelationAttr(Long attrgroupId) {
    List<AttrAttrgroupRelationEntity> entities = relationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_group_id", attrgroupId));
    List<Long> attrIds = entities.stream().map((attr) -> attr.getAttrId()).collect(Collectors.toList());
    // 根據這個屬性查詢到的id可能是空的
    if(attrIds == null || attrIds.size() == 0){
        return null;
    }
    return this.listByIds(attrIds);
}

10.4商品新增Vo抽取

基本資訊

image-20240702234535359

規格引數

image-20240702234512703

銷售屬性

image-20240702234500746

sku資訊

image-20240702234209056

雅丹黑 白沙銀 南糯紫
12GB+256GB
12GB+512GB
新品手機 
全網通
清涼季 暑期好物 超值鉅惠

華為Mate60 新品手機 雅丹黑 12GB+256GB 全網通
華為Mate60 新品手機 雅丹黑 12GB+256GB 全網通
清涼季 暑期好物 超值鉅惠

華為Mate60 新品手機 雅丹黑 12GB+512GB 全網通
華為Mate60 新品手機 雅丹黑 12GB+512GB 全網通
清涼季 暑期好物 超值鉅惠

華為Mate60 新品手機 南糯紫 12GB+256GB 全網通
華為Mate60 新品手機 南糯紫 12GB+256GB 全網通
清涼季 暑期好物 超值鉅惠

華為Mate60 新品手機 南糯紫 12GB+512GB 全網通
華為Mate60 新品手機 南糯紫 12GB+512GB 全網通
清涼季 暑期好物 超值鉅惠

華為Mate60 新品手機 白沙銀 12GB+256GB 全網通
華為Mate60 新品手機 白沙銀 12GB+256GB 全網通
清涼季 暑期好物 超值鉅惠

華為Mate60 新品手機 白沙銀 12GB+512GB 全網通
華為Mate60 新品手機 白沙銀 12GB+512GB 全網通
清涼季 暑期好物 超值鉅惠

設定優惠、會員

image-20240702235447579

點選下一步,儲存商品資訊,貼上商品儲存的json

image-20240702235554337

生成json實體

https://www.bejson.com/json2javapojo/new/#google_vignette

image-20240702234354794

10.5商品新增業務流程分析

主要步驟:

  • 1、儲存spu的基本資訊 gulimall_pms - pms_spu_info
  • 2、儲存spu的描述圖片 gulimall_pms - pms_spu_info_desc
  • 3、儲存spu的圖片集 gulimall_pms - pms_spu_images
  • 4、儲存spu的規格引數 gulimall_pms - pms_product_attr_value
  • 5、遠端呼叫優惠卷服務gulimall_coupon,儲存spu的積分資訊 gulimall_sms - sms_spu_bounds
  • 6、儲存spu對應的所有sku資訊
    • a、sku的基本資訊 gulimall_pms - pms_sku_info
    • b、sku的圖片資訊 gulimall_pms - pms_sku_images
    • c、sku的銷售屬性 gulimall_pms - pms_sku_sale_attr_value
    • d、遠端呼叫優惠卷服務gulimall_coupon,儲存sku的優惠、滿減等資訊 gulimall_sms - sms_sku_laddersms_sku_full_reductionsms_member_price

10.6儲存SPU基本資訊

主要步驟:

  • 1、儲存spu的基本資訊 gulimall_pms - pms_spu_info
  • 2、儲存spu的描述圖片 gulimall_pms - pms_spu_info_dessc
  • 3、儲存spu的圖片集 gulimall_pms - pms_spu_images
  • 4、儲存spu的規格引數 gulimall_pms - pms_product_attr_value
  • 5、遠端呼叫優惠卷服務gulimall_coupon,儲存spu的積分資訊 gulimall_sms - sms_spu_bounds
        //1、儲存spu基本資訊 pms_spu_info
        SpuInfoEntity infoEntity = new SpuInfoEntity();
        BeanUtils.copyProperties(vo,infoEntity);
        infoEntity.setCreateTime(new Date());
        infoEntity.setUpdateTime(new Date());
        this.saveBaseSpuInfo(infoEntity);

        //2、儲存Spu的描述圖片 pms_spu_info_desc
        List<String> decript = vo.getDecript();
        SpuInfoDescEntity descEntity = new SpuInfoDescEntity();
        descEntity.setSpuId(infoEntity.getId());
        descEntity.setDecript(String.join(",",decript));
        spuInfoDescService.saveSpuInfoDesc(descEntity);

        //3、儲存spu的圖片集 pms_spu_images
        List<String> images = vo.getImages();
        imagesService.saveImages(infoEntity.getId(),images);


        //4、儲存spu的規格引數;pms_product_attr_value
        List<BaseAttrs> baseAttrs = vo.getBaseAttrs();
        List<ProductAttrValueEntity> collect = baseAttrs.stream().map(attr -> {
            ProductAttrValueEntity valueEntity = new ProductAttrValueEntity();
            valueEntity.setAttrId(attr.getAttrId());
            AttrEntity id = attrService.getById(attr.getAttrId());
            valueEntity.setAttrName(id.getAttrName());
            valueEntity.setAttrValue(attr.getAttrValues());
            valueEntity.setQuickShow(attr.getShowDesc());
            valueEntity.setSpuId(infoEntity.getId());

            return valueEntity;
        }).collect(Collectors.toList());
        attrValueService.saveProductAttr(collect);


        //5、儲存spu的積分資訊;gulimall_sms->sms_spu_bounds
        Bounds bounds = vo.getBounds();
        SpuBoundTo spuBoundTo = new SpuBoundTo();
        BeanUtils.copyProperties(bounds,spuBoundTo);
        spuBoundTo.setSpuId(infoEntity.getId());
        R r = couponFeignService.saveSpuBounds(spuBoundTo);
        if(r.getCode() != 0){
            log.error("遠端儲存spu積分資訊失敗");
        }

10.7儲存SKU基本資訊

儲存spu對應的所有sku資訊,主要步驟:

  • a、sku的基本資訊 gulimall_pms - pms_sku_info
  • b、sku的圖片資訊 gulimall_pms - pms_sku_images
  • c、sku的銷售屬性 gulimall_pms - pms_sku_sale_attr_value
        //5、儲存當前spu對應的所有sku資訊;

        List<Skus> skus = vo.getSkus();
        if(skus!=null && skus.size()>0){
            skus.forEach(item->{
                String defaultImg = "";
                for (Images image : item.getImages()) {
                    if(image.getDefaultImg() == 1){
                        defaultImg = image.getImgUrl();
                    }
                }
                //    private String skuName;
                //    private BigDecimal price;
                //    private String skuTitle;
                //    private String skuSubtitle;
                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);
                //5.1)、sku的基本資訊;pms_sku_info
                skuInfoService.saveSkuInfo(skuInfoEntity);

                Long skuId = skuInfoEntity.getSkuId();

                List<SkuImagesEntity> imagesEntities = item.getImages().stream().map(img -> {
                    SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
                    skuImagesEntity.setSkuId(skuId);
                    skuImagesEntity.setImgUrl(img.getImgUrl());
                    skuImagesEntity.setDefaultImg(img.getDefaultImg());
                    return skuImagesEntity;
                }).filter(entity->{
                    //返回true就是需要,false就是剔除
                    return !StringUtils.isEmpty(entity.getImgUrl());
                }).collect(Collectors.toList());
                //5.2)、sku的圖片資訊;pms_sku_image
                skuImagesService.saveBatch(imagesEntities);
                //TODO 沒有圖片路徑的無需儲存

                List<Attr> attr = item.getAttr();
                List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attr.stream().map(a -> {
                    SkuSaleAttrValueEntity attrValueEntity = new SkuSaleAttrValueEntity();
                    BeanUtils.copyProperties(a, attrValueEntity);
                    attrValueEntity.setSkuId(skuId);

                    return attrValueEntity;
                }).collect(Collectors.toList());
                //5.3)、sku的銷售屬性資訊:pms_sku_sale_attr_value
                skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);

                // //5.4)、sku的優惠、滿減等資訊;gulimall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_member_price
                SkuReductionTo skuReductionTo = new SkuReductionTo();
                BeanUtils.copyProperties(item,skuReductionTo);
                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優惠資訊失敗");
                    }
                }
            });
        }

10.8呼叫遠端服務儲存優惠等資訊

主要步驟:

  • common中建立to使用者服務和服務之間傳遞引數

  • 遠端呼叫優惠卷服務gulimall_coupon,儲存spu的積分資訊 gulimall_sms - sms_spu_bounds

  • 遠端呼叫優惠卷服務gulimall_coupon,儲存sku的優惠、滿減等資訊 gulimall_sms - sms_sku_laddersms_sku_full_reductionsms_sku_full_reductionsms_member_price

common中建立to使用者服務和服務之間傳遞引數

image-20240703014427238

gulimall_product建立com.peng.product.feign.CouponFeignService服務遠端呼叫優惠卷服務gulimall_coupon,預設儲存積分的介面自動化工程已經幫我們生成好了

image-20240703013352986

優惠卷服務gulimall_coupon建立介面/coupon/skufullreduction/saveinfo儲存優惠資訊

image-20240703013559568

儲存sku階梯價格資訊(折扣)、sku滿減資訊、商品會員價格

image-20240703014025648

呼叫優惠卷服務gulimall_coupon的介面/coupon/spubounds/save儲存spu的積分資訊

image-20240703014721798

呼叫優惠卷服務gulimall_coupon的介面/coupon/skufullreduction/saveinfo儲存sku的優惠、滿減等資訊

image-20240703014853380

10.9商品儲存debug完成

配置服務佔用記憶體

-Xmx 100m

image-20240703023531210

設定資料庫當前會話的事務隔離級別是讀未提交READ UNCOMMITTED,方便除錯的時候檢視資料

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
  • SET SESSION: 指定更改僅影響當前會話。會話結束後,事務隔離級別將恢復預設值或全域性設定。

  • TRANSACTION ISOLATION LEVEL: 設定事務的隔離級別。

  • READ UNCOMMITTED: 指定事務隔離級別為 READ UNCOMMITTED,允許讀取未提交的資料(髒讀)。

image-20240703025106811

Spu描述圖片儲存的時候報錯,需要設定spuId欄位為INPUT(輸入)

image-20240703025048127

主鍵非空不自增,所以需要支援自己輸入

image-20240703025427141

除錯的時候邊除錯邊檢視資料是否正常提交

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

select * from mall_pms.pms_spu_info;

select * from mall_pms.pms_spu_info_desc;

select * from mall_pms.pms_spu_images;

select * from mall_pms.pms_product_attr_value;

select * from mall_sms.sms_spu_bounds;

select * from mall_pms.pms_sku_info;

select * from mall_pms.pms_sku_images;

select * from mall_pms.pms_sku_sale_attr_value;

select * from mall_sms.sms_sku_ladder;

select * from mall_sms.sms_sku_full_reduction;

select * from mall_sms.sms_member_price;


TRUNCATE TABLE mall_pms.pms_spu_info;
TRUNCATE TABLE mall_pms.pms_spu_info_desc;
TRUNCATE TABLE mall_pms.pms_spu_images;
TRUNCATE TABLE mall_pms.pms_product_attr_value;
TRUNCATE TABLE mall_sms.sms_spu_bounds;

TRUNCATE TABLE mall_pms.pms_sku_info;
TRUNCATE TABLE mall_pms.pms_sku_images;
TRUNCATE TABLE mall_pms.pms_sku_sale_attr_value;
TRUNCATE TABLE mall_sms.sms_sku_ladder;
TRUNCATE TABLE mall_sms.sms_sku_full_reduction;
TRUNCATE TABLE mall_sms.sms_member_price;

image-20240703045225356

10.10商品儲存其他問題處理

上傳SKU圖片沒有路徑地址就不新增資料

image-20240703050808186

滿量打折和滿價打折大於0才傳送請求,新增相應優惠資料

image-20240703050956041

在優惠卷服務裡新增的時候也加上判斷滿量打折和滿價打折大於0才新增

image-20240703051209147

會員價格大於0的時候才新增商品會員資料

image-20240703051312628

11.商品管理

開發階段每次需要輸入驗證碼,先取消一下這個功能,提高開發效率

image-20240703200453666

前段介面給文字框繫結預設值

image-20240703200600402

後端程式碼註釋掉驗證碼功能

image-20240703200640487

每次登入點選一下就可以了

image-20240703200701692

11.1SPU檢索

主要步驟

spu管理查詢功能:

  • 根據分類、品牌、狀態、id和名字進行條件查詢
  • id和名字進行檢索的時候注意條件拼接

image-20240703200808737

idspu_name是或者關係,和其他條件是並且關係

image-20240703201702999

完整程式碼

image-20240703201806791

查詢出來的日期顯示異常

image-20240703201938062

配置application.yaml

spring:
  jackson:
    date-format: yyyy-mm-dd HH:mm:ss

image-20240703202020733

已格式化時間

image-20240703202054729

11.2SKU檢索

主要步驟

商品管理查詢功能:

  • 根據分類、品牌、價格、id和名稱進行檢索
  • 分類Id、品牌Id查詢時不能等於0
  • 價格可以等於0(>0),但是需要大於0
  • 需要對價格進行錯誤容錯,如果不能轉換為BigDecimal就不拼接條件

image-20240703202905784

完整程式碼

@Override
    public PageUtils queryPageByCondition(Map<String, Object> params) {
        QueryWrapper<SkuInfoEntity> queryWrapper = new QueryWrapper<>();
        /**
         * key:
         * catelogId: 0
         * brandId: 0
         * min: 0
         * max: 0
         */
        String key = (String) params.get("key");
        if(!StringUtils.isEmpty(key)){
            queryWrapper.and((wrapper)->{
               wrapper.eq("sku_id",key).or().like("sku_name",key);
            });
        }

        String catelogId = (String) params.get("catelogId");
        if(!StringUtils.isEmpty(catelogId)&&!"0".equalsIgnoreCase(catelogId)){

            queryWrapper.eq("catalog_id",catelogId);
        }

        String brandId = (String) params.get("brandId");
        if(!StringUtils.isEmpty(brandId)&&!"0".equalsIgnoreCase(catelogId)){
            queryWrapper.eq("brand_id",brandId);
        }

        String min = (String) params.get("min");
        if(!StringUtils.isEmpty(min)){
            queryWrapper.ge("price",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("price",max);
                }
            }catch (Exception e){

            }

        }


        IPage<SkuInfoEntity> page = this.page(
                new Query<SkuInfoEntity>().getPage(params),
                queryWrapper
        );

        return new PageUtils(page);
    }

測試

image-20240703203832457

12.倉庫管理

12.1整合ware服務&獲取倉庫列表

主要步驟:

  • 配置倉儲服務執行記憶體
  • 倉儲服務gulimall-ware配置服務註冊、日誌級別
  • 閘道器服務配置gulimall-gateway配置倉儲服務gulimall-ware路由
  • 根據倉庫id、倉庫名、倉庫地址、區域編碼查詢倉庫列表

image-20240703210441089

倉儲服務設定執行記憶體

-Xmx100m

image-20240703205304893

application.yaml配置註冊服務中心和日誌級別

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.188.180:8848 # nacos地址

logging:
  level:
    com.peng: debug

image-20240703204855546

配置倉儲服務路由

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

image-20240703205026592

啟動專案,倉儲服務gulimall-ware已成功註冊

image-20240703205346888

根據倉庫id、倉庫名、倉庫地址、區域編碼查詢倉庫列表

image-20240703205429043

測試

image-20240703210414401

12.2查詢庫存&建立採購需求

主要步驟:

  • 1.根據倉庫idskuId查詢商品庫存
  • 2.商品管理建立商品庫存
  • 3.採購需求、採購單建立商品庫存,合併採購需求為採購單
  • 4.根據倉庫、狀態、關鍵字查詢採購需求

image-20240703220212169

根據倉庫idskuId查詢商品庫存

image-20240703220333259

可以根據商品管理->庫存管理新增庫存

image-20240703220444816

採購需求、採購單建立商品庫存,合併採購需求為採購單

image-20240703222113953

根據倉庫、狀態、關鍵字查詢採購需求

@Override
    public PageUtils queryPage(Map<String, Object> params) {

        QueryWrapper<PurchaseDetailEntity> queryWrapper = new QueryWrapper<PurchaseDetailEntity>();

        String key = (String) params.get("key");
        if(!StringUtils.isEmpty(key)){
            //purchase_id  sku_id
            queryWrapper.and(w->{
                w.eq("purchase_id",key).or().eq("sku_id",key);
            });
        }

        String status = (String) params.get("status");
        if(!StringUtils.isEmpty(status)){
            //purchase_id  sku_id
            queryWrapper.eq("status",status);
        }

        String wareId = (String) params.get("wareId");
        if(!StringUtils.isEmpty(wareId)){
            //purchase_id  sku_id
            queryWrapper.eq("ware_id",wareId);
        }

        IPage<PurchaseDetailEntity> page = this.page(
                new Query<PurchaseDetailEntity>().getPage(params),
                queryWrapper
        );

        return new PageUtils(page);
    }

12.3合併採購需求

主要步驟:

  • 1.查詢未領取的採購單
  • 2.管理員列表新增採購人員,採購單分配採購人員
  • 3.合併採購需求建立採購單,沒有purchaseId建立新的採購單

查詢未領取的採購單程式碼

image-20240703224031787

管理員列表新增採購人員

image-20240703224158234

建立一個採購單

image-20240703224524498

給建立的採購單分配一個採購人員

image-20240703224551363

合併採購需求建立採購單,沒有purchaseId建立新的採購單

@Transactional
@Override
    public void mergePurchase(MergeVo mergeVo) {
        Long purchaseId = mergeVo.getPurchaseId();
        if(purchaseId == null){
            //1、新建一個
            PurchaseEntity purchaseEntity = new PurchaseEntity();

            purchaseEntity.setStatus(WareConstant.PurchaseStatusEnum.CREATED.getCode());
            purchaseEntity.setCreateTime(new Date());
            purchaseEntity.setUpdateTime(new Date());
            this.save(purchaseEntity);
            purchaseId = purchaseEntity.getId();
        }

        //TODO 確認採購單狀態是0,1才可以合併

        List<Long> items = mergeVo.getItems();
        Long finalPurchaseId = purchaseId;
        List<PurchaseDetailEntity> collect = items.stream().map(i -> {
            PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();

            detailEntity.setId(i);
            detailEntity.setPurchaseId(finalPurchaseId);
            detailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.ASSIGNED.getCode());
            return detailEntity;
        }).collect(Collectors.toList());


        detailService.updateBatchById(collect);

        PurchaseEntity purchaseEntity = new PurchaseEntity();
        purchaseEntity.setId(purchaseId);
        purchaseEntity.setUpdateTime(new Date());
        this.updateById(purchaseEntity);
    }

合併成功

image-20240703225616378

沒有選擇採購單會重新生成採購單

image-20240703225601230

12.4領取採購單

主要步驟:

  • 1.根據id查詢所有的採購單,並且篩選出新建和已分配狀態
  • 2.改變採購單的狀態為已領取
  • 3.改變採購項的狀態為為正在採購
@Override
    public void received(List<Long> ids) {
        // 1、確認當前採購單是新建或者已分配狀態
        List<PurchaseEntity> collect = ids.stream().map(id -> {
            PurchaseEntity byId = this.getById(id);
            return byId;
        }).filter(item -> {
            if (item.getStatus() == WareConstant.PurchaseStatusEnum.CREATED.getCode() ||
                    item.getStatus() == WareConstant.PurchaseStatusEnum.ASSIGNED.getCode()) {
                return true;
            }
            return false;
        }).map(item -> {
            item.setStatus(WareConstant.PurchaseStatusEnum.RECEIVE.getCode());
            item.setUpdateTime(new Date());
            return item;
        }).collect(Collectors.toList());

        // 2、改變採購單的狀態
        this.updateBatchById(collect);

        // 3、改變採購項的狀態
        collect.forEach((item) -> {
            List<PurchaseDetailEntity> entities = detailService.listDetailByPurchaseId(item.getId());
            List<PurchaseDetailEntity> detailEntities = entities.stream().map(entity -> {
                PurchaseDetailEntity entity1 = new PurchaseDetailEntity();
                entity1.setId(entity.getId());
                entity1.setStatus(WareConstant.PurchaseDetailStatusEnum.BUYING.getCode());
                return entity1;
            }).collect(Collectors.toList());
            detailService.updateBatchById(detailEntities);
        });
    }

介面測試

image-20240704000820528

輸入引數1,然後執行

image-20240704000847621

採購單和對應的採購需求狀態都成功改變

image-20240704000925502

合併的時候需要判斷採購單是新建和已分配狀態

 // TODO 確認採購單狀態是0,1才可以合併
        PurchaseEntity purchaseEntity = this.getById(purchaseId);
        if (purchaseEntity.getStatus() != WareConstant.PurchaseStatusEnum.CREATED.getCode()
                || purchaseEntity.getStatus() != WareConstant.PurchaseStatusEnum.ASSIGNED.getCode()) {
            return;
        }

image-20240704001624837

12.5完成採購

主要步驟:

  • 1.更新採購單的狀態
    • 採購項採購失敗,採購單的狀態需要更新為有異常
    • 採購項採購成功,採購單的狀態需要更新已完成
  • 2.更新採購項的狀態
    • 採購項採購失敗,採購項的狀態更新為採購失敗,採購單的狀態需要更新為有異常
    • 採購項採購成功,採購項的狀態更新為已完成
    • 採購項採購成功,需要更新庫存
  • 3.採購成功的進行入庫

更新採購項的狀態

  • 採購項採購失敗,採購項的狀態更新為採購失敗,採購單的狀態需要更新為有異常
  • 採購項採購成功,採購項的狀態更新為已完成
  • 採購項採購成功,需要更新庫存
    @Transactional
    @Override
    public void done(PurchaseDoneVo doneVo) {
    Long id = doneVo.getId();
        //2、改變採購項的狀態
        Boolean flag = true;
        List<PurchaseItemDoneVo> items = doneVo.getItems();
        List<PurchaseDetailEntity> updates = new ArrayList<>();
        for (PurchaseItemDoneVo item : items) {
            PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();
            if(item.getStatus() == WareConstant.PurchaseDetailStatusEnum.HASERROR.getCode()){
                flag = false;
                detailEntity.setStatus(item.getStatus());
            }else{
                detailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.FINISH.getCode());
                ////3、將成功採購的進行入庫
                PurchaseDetailEntity entity = detailService.getById(item.getItemId());
                wareSkuService.addStock(entity.getSkuId(),entity.getWareId(),entity.getSkuNum());

            }
            detailEntity.setId(item.getItemId());
            updates.add(detailEntity);
        }
        detailService.updateBatchById(updates);

更新採購單的狀態

  • 採購項採購失敗,採購單的狀態需要更新為有異常
  • 採購項採購成功,採購單的狀態需要更新已完成
        //1、改變採購單狀態
        PurchaseEntity purchaseEntity = new PurchaseEntity();
        purchaseEntity.setId(id);
        purchaseEntity.setStatus(flag?WareConstant.PurchaseStatusEnum.FINISH.getCode():WareConstant.PurchaseStatusEnum.HASERROR.getCode());
        purchaseEntity.setUpdateTime(new Date());
        this.updateById(purchaseEntity);    
}

採購成功的進行入庫

  • 根據skuId(商品skuId)和ware_id(倉庫Id)查詢商品庫存

  • 庫存中沒有該商品,需要重新新增

    • 遠端呼叫商品服務gulimall-product獲取sku商品名稱
  • 庫存中有該商品,需要修改庫存:庫存 = 現有庫存 + 增加庫存,條件是商品skuIdware_id

    • UPDATE `wms_ware_sku` SET stock=stock+#{skuNum} WHERE sku_id=#{skuId} AND ware_id=#{wareId}
      
 @Override
    public void addStock(Long skuId, Long wareId, Integer skuNum) {
        //1、判斷如果還沒有這個庫存記錄新增
        List<WareSkuEntity> entities = wareSkuDao.selectList(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));
        if(entities == null || entities.size() == 0){
            WareSkuEntity skuEntity = new WareSkuEntity();
            skuEntity.setSkuId(skuId);
            skuEntity.setStock(skuNum);
            skuEntity.setWareId(wareId);
            skuEntity.setStockLocked(0);
            //TODO 遠端查詢sku的名字,如果失敗,整個事務無需回滾
            //1、自己catch異常
            //TODO 還可以用什麼辦法讓異常出現以後不回滾?高階
            try {
                R info = productFeignService.info(skuId);
                Map<String,Object> data = (Map<String, Object>) info.get("skuInfo");

                if(info.getCode() == 0){
                    skuEntity.setSkuName((String) data.get("skuName"));
                }
            }catch (Exception e){

            }
            wareSkuDao.insert(skuEntity);
        }else{
            wareSkuDao.addStock(skuId,wareId,skuNum);
        }

    }

倉儲服務gulimall-ware配置遠端呼叫商品服務gulimall-product

  • 讓所有請求過閘道器:
    • @FeignClient("gulimall-gateway"):給gulimall-gateway所在的機器發請求
    • /api/product/skuinfo/info/{skuId}:帶上api,閘道器服務自己解析
  • 直接讓後臺指定服務處理:
    • @FeignClient("gulimall-product"):給gulimall-product所在的機器發請求
    • /product/skuinfo/info/{skuId}:直接請求gulimall-product介面服務地址

image-20240704024514114

開啟遠端呼叫服務

@EnableFeignClients(basePackages = "com.peng.ware.feign")

image-20240704024705669

gulimall-ware服務配置MybatisPlus分頁外掛

image-20240704025456116

接著測試採購完成的介面

{
  "id": 1,
  "items": [
    {"itemId":1,"status": 3, "reason": ""},
    {"itemId":2,"status": 4, "reason": "無貨"}
  ]
}

image-20240704025826016

我們有一個採購項採購失敗了,所以採購單狀態是有異常,採購項一個是已完成一個是採購失敗

image-20240704025942476

採購流程

建立3個採購需求,不選擇採購單自動建立採購單id為2的採購單

採購單狀態為已分配,採購需求狀態為已分配

image-20240704032245219

領取採購單id為2的採購單

採購單狀態為已領取,採購需求狀態為正在採購

image-20240704032347087

完成採購

採購單狀態為已完成,採購需求狀態為已完成

如果有一個採購需求為採購失敗,那採購單狀態為有異常

image-20240704032548732

11商品管理

11.3SPU規格維護

spuId查詢所有spu基礎屬性

 @Override
    public List<ProductAttrValueEntity> baseAttrlistforspu(Long spuId) {
        List<ProductAttrValueEntity> entities = this.baseMapper.selectList(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));
        return entities;
    }

不需要更新屬性,全部刪掉屬性,然後新增

@Transactional
    @Override
    public void updateSpuAttr(Long spuId, List<ProductAttrValueEntity> entities) {
        //1、刪除這個spuId之前對應的所有屬性
        this.baseMapper.delete(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id",spuId));
        List<ProductAttrValueEntity> collect = entities.stream().map(item -> {
            item.setSpuId(spuId);
            return item;
        }).collect(Collectors.toList());
        this.saveBatch(collect);
    }

13.分散式基礎總結

清醒,知趣,明得失,知進退