穀粒商城
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
(3)vagrant init centos/7:初始化
https://app.vagrantup.com/boxes/search
(4)vagrant up:啟動虛擬機器
第一次啟動需要去下載centos/7
說明啟動成功
(5)vagrant ssh 開啟命令列
1.3 網路配置
(1)找到配置檔案,修改配置
C:\Users\Peng\Vagrantfile
(2)ipconfig 檢查本機VirtualBox地址,修改配置的網段需要一致,這裡都是56
(3)vagrant reload 重啟虛擬機器
(4)測試本機與虛擬機器能否互相ping通
檢視ip命令
winows:ipconfig
centos:ip addr
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)外掛
lombok
mybatisx
(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)建立專案倉庫
(2)使用idea Clone專案
(3)建立微服務模組
- com.peng.gulimall
- gulimall-product
- com.peng.gulimall.product
選擇springWeb和OpenFeign
(4)專案結構
- 商品服務product
- 儲存服務ware
- 訂單服務order
- 優惠券服務coupon
- 使用者服務member
(5)建立聚合服務
<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
(7)重新整理後納入版本控制
(8)idea安裝碼雲
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
$ 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
2.快速開發
2.1人人開源搭建後臺管理系統
(1)clone
地址:https://gitee.com/renrenio
$ git clone git@gitee.com:renrenio/renren-fast-vue.git
$ git clone git@gitee.com:renrenio/renren-fast.git
(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>
(3)執行mysql
(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
(5)執行
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
啟動
npm run dev
訪問http://localhost:8001/#/login
輸出使用者名稱admin密碼admin 驗證碼,登入成功,前後端聯調成功
2.3逆向工程搭建&使用
(1)下載程式碼人人開原始碼生成器
$ git clone git@gitee.com:renrenio/renren-generator.git
(2)renren-generator載入到專案中
(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
(4)修改程式碼生成配置
# 主目錄
mainPath=com.peng
#包名
package=com.peng.gulimall
moduleName=product
#作者
author=peng
#Email
email=pengpeng6135@163.com
#表字首(類名不會包含表字首)
tablePrefix=pms_
(5)修改contoller模板
註釋import org.apache.shiro.authz.annotation.RequiresPermissions名稱空間
註釋Controller.java.vm模板中所有的@RequiresPermissions("${moduleName}😒{pathName}:list")
(6)新建公共模組
新建模組
選擇maven專案
新建gulimall-common
配置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>
gulimall-common新增到pom.xml
(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
(9)生成程式碼
(10)生成的程式碼複製到專案中去
2.4測試基本基本的CRUD功能
(1)整合MyBatis-Plus
<!-- mybatisPLUS -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
(2)@MapperScan配置注入的資料訪問層
(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
(4)測試
2.5 所有微服務的CURD
- 商品服務product
- 儲存服務ware
- 訂單服務order
- 優惠券服務coupon
- 使用者服務member
如果UndoLog的有關服務報錯,可以都先註釋掉,暫時用不上
2.5.1 coupon 優惠卷服務
renren-generator工程,修改generator.properties
#主目錄
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資料庫資訊
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
<dependency>
<groupId>com.peng.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
gulimall-coupon工程,修改application.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
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
訪問http://localhost:8888/#generator.html
生成完的程式碼賦值到gulimall-coupon,以下是專案結構
配置mapper對映,並啟動
@MapperScan("com.peng.gulimall.coupon.dao")
測試gulimall-coupon,訪問http://localhost:7001/coupon/coupon/list
2.5.2 member 使用者服務
(1)renren-generator修改generator.properties
#模組名
moduleName=member
#表字首(類名不會包含表字首) # 我們的ums資料庫中的表的字首都ums
如果寫了表字首,每一張表對於的javaBean就不會新增字首了
tablePrefix=ums_
(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
(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_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對映
(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>
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
(2)解壓執行
預設是cluster,可以改成standalone,或者如下命令列執行,
單機執行
startup.cmd -m standalone
(3)訪問:http://127.0.0.1:8848/nacos/index.html#/login
使用者名稱密碼都是nacos
(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
(6)gulimall-member使用 @EnableDiscoveryClient
註解開啟服務註冊與發現功能
@EnableDiscoveryClient
(7)gulimall-coupon的application.yml增加nacos配置
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: gulimall-coupon
(8)gulimall-coupon使用 @EnableDiscoveryClient
註解開啟服務註冊與發現功能
@EnableDiscoveryClient
(9)執行GulimallCouponApplication 和GulimallMemberApplication
(10)登入nacos檢視,服務已經註冊進來
注意:com.alibaba.cloud版本和nacos版本問題
com.alibaba.cloud是2.2.7.RELEASE版本,nacos如果是1.多版本就會註冊不進去,服務起不來
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>
(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));
}
(2)開啟遠端呼叫功能 @EnableFeignClients,要指定遠端呼叫功能放的基礎包
@EnableFeignClients(basePackages="com.peng.gulimall.member.feign")//掃描介面方法註解
(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();
}
(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"));
}
(5)訪問:http://localhost:8005/member/member/coupons
出現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>
在需要遠端呼叫的服務加上loadbalancer
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
不識別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>
(2)增加bootstrap.properties配置,這個檔案是springboot裡規定的,他優先順序別application.properties高
# 改名字,對應nacos裡的配置檔名
spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
(3)增加application.properties配置
coupon.user.name = application.properties
coupon.user.age = 18
(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);
}
(5)測試:http://localhost:7005/coupon/coupon/test
這裡就可以訪問本地application.properties的配置檔案
(6)nacos增加配置併發布
3.5 Spring Cloud alibaba Nacos 配置中心-名稱空間與配置分組
3.5.1 名稱空間
nacos新增名稱空間
prop配置
coupon.user.name = nacos.prop
coupon.user.age = 30
# 可以選擇對應的名稱空間 # 寫上對應環境的名稱空間ID
spring.cloud.nacos.config.namespace=d408db52-7aa9-4348-81b4-cc5d39f91c55
測試訪問:http://localhost:7005/coupon/coupon/test
-
名稱空間:用作配置隔離。(一般每個微服務一個名稱空間)
-
預設public。預設新增的配置都在public空間下
-
開發、測試、開發可以用名稱空間分割。properties每個空間有一份。也可以為每個微服務配置一個名稱空間,微服務互相隔離
-
在bootstrap.properties裡配置
spring.cloud.nacos.config.namespace=d408db52-7aa9-4348-81b4-cc5d39f91c55
-
-
配置集:一組相關或不相關配置項的集合。
-
配置集ID:類似於配置檔名,即Data ID
-
配置分組:預設所有的配置集都屬於
DEFAULT_GROUP
。雙十一,618的優惠策略改分組即可spring.cloud.nacos.config.group=DEFAULT_GROUP
3.5.2 配置分組
(1)為coupon新增一個名稱空間
(2)gulimall-coupon只讀取自己的配置(基於微服務隔離)
(3)可以設定不同的分組
coupon.user.name = coupon.11
coupon.user.age = 60
(4)測試:http://localhost:7005/coupon/coupon/test
(5)新增dev(開發環境)和prod(生產環境)分組,預設為dev分組
dev配置:
coupon.user.name = coupon.dev
coupon.user.age = 50
prod配置:
coupon.user.name = coupon.prod
coupon.user.age = 70
測試訪問:http://localhost:7005/coupon/coupon/test
3.6 Spring Cloud alibaba Nacos 配置中心-載入多配製集
(1)註釋application.yml
(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
(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
(4)測試執行,nacos配置讀取正常
訪問:http://localhost:7005/coupon/coupon/test
訪問:http://localhost:7005/coupon/coupon/list
3.7 Spring Cloud Gateway
地址:https://spring.io/projects/spring-cloud-gateway
三大核心概念:
- Route(路由):
- Predicate(斷言):
- Filter(過濾器):
(1)spring嚮導建立閘道器,選擇閘道器
(2)nacos增加gateway配置
增加gateway名稱空間
gulimall-gateway.properties
spring:
application:
name: gulimall-gateway
(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
(4)application.properties配置
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.application.name=gulimall-gateway
server.port=88
(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
(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
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
(2)下載後解壓,cmd到解壓目錄,執行npm install
如果在E盤,cmd輸入e:,讓後cd到路徑
或者直接在devtools-main解壓目錄輸入cmd,回車
(3)執行npm run build,新版本使用npm run build報錯
(4)安裝yarn
npm install -g yarn
(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
中間失敗了一次,又執行了一次就成功了
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>
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
(2)全域性安裝vue腳手架
npm install -g @vue/cli-init
(3)初始化Vue專案
vue init webpack 專案名稱
(4)啟動vue專案
npm run start
4.8整合Element-UI
官網:https://element.eleme.cn/#/zh-CN
(1)安裝 element-ui
npm i element-ui
(2)引入Element-UI
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
(3)把elementUi的container佈局容器程式碼拷到App.vue中
(4)修改App.vue中的
(5)路由配置
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
},
{
path: '/hello',
name: 'Hello',
component: Hello
},
{
path: '/table',
name: 'MyTable',
component: MyTable
}
]
})
(6)el-menu設定router,並新增跳轉路由
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
5.2 配置閘道器路由與路徑重寫
(1)執行人人開源程式
(2)增加商品系統選單
給商品系統增加商品維護子選單
選單路由對應前端工程的路徑
結果:
(3)在renren-fast-vue增加商品維護的程式碼,路徑匹配配置的選單路由
(4)前端修改閘道器伺服器的地址
(5)將renren-fast註冊到nacos中心
cloud:
acos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: renren-fast
開啟服務的註冊發現
@EnableDiscoveryClient
引入: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>
(7)重新執行,啟動gulimall-gateway和renren-fast工程:
(8)檢視獲取驗證碼介面,透過閘道器正常訪問
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);
}
}
(2)註釋renren-fast配置的跨域配置
(3)跨域配置完成,登入成功
5.4 樹形展示三級分類資料
(1)使用nacos遠端配置
新建product名稱空間
新建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
效果圖
gulimall-product主程式新增@EnableDiscoveryClient
(3)啟動執行,確保gulimall-product,gulimall-gateway,renren-fast註冊到nacos中
(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}
(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遠端配置
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
(2)實體類的邏輯刪除欄位加上TableLogic,這裡@TableLogic(value="1")代表1是未刪除,0是已刪除,因為資料庫跟全域性的配置可能是反的,所以可以單獨配置
錯誤: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)增加品牌管理選單
(2)逆向生成的前端程式碼複製到vue工程product目錄下
(3)取消Eslint語法檢查
(4)取消許可權檢查
(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)開啟自己的阿里雲工作臺
(2)建立Bucket
(3)檔案管理上傳圖片
(4)點選詳情,複製路徑
(5)瀏覽器開啟時候能正常下載
6.4 阿里雲物件儲存-OSS測試
6.4.1 阿里雲配置
地址:https://help.aliyun.com/document_detail/32013.html
(1)新增AccessKey
(2)使用子使用者
(3)建立訪問使用者
(4)驗證後獲取AccessKey ID和AccessKey Secret
(5)賬號分配許可權
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
(3)AccessKey ID和AccessKey Secret
(4)Bucket
(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
(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();
}
6.5 阿里雲物件儲存-服務端簽名後直傳
(1)建立第三方spring服務模組
(2)選中spring Web和OpenFeign
(3)將gulimall-common阿里雲端儲存相關服務匯入到gulimall-third-party
(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
增加gulimall-third-party名稱空間和oss.yml,名稱空間為dev
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
(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)開啟服務發現
(8)新增other.yml作為其他配置
server:
port: 30000
other.yml
(9)服務端簽名後直傳文件
地址:https://help.aliyun.com/document_detail/31926.html
https://help.aliyun.com/document_detail/91868.htm?spm=a2c4g.11186623.0.0.16073967YFZamz#concept-ahk-rfz-2fb
(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
6.6 OSS前後端聯調
(1)跨域配置
- 登入OSS管理控制檯。
- 單擊Bucket列表,之後單擊目標Bucket名稱。
- 單擊*許可權管理* > *跨域設定*,在跨域設定區域單擊設定**。
(2)前端配置Bucket域名
(3)前端引入並使用上傳元件
(4)檢視阿里雲,目錄和檔案都成功建立
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
(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;
}
}
(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());
}
}
6.8.3JSR303分組檢驗
(1)新增三個驗證介面
(2)註解驗證的時候加上分組
例如品牌id:新增的時候必須加上id,新增的時候不需要
(3)controller方法必須使用Validated指定分組,負責不生效
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向父元件傳送事件,
引入註冊完成後呼叫,注意方法名要和$emit傳入的一致
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;
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
(屬性)
基礎屬性需要設定分類名稱
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 新增分組與屬性關聯
規格引數(基礎屬性)新增和修改時後端沒有獲取值型別這個欄位
後端的Entity
和Vo
給商品屬性加上值型別這個欄位
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開啟服務與發現等註解
(2)gulimall-member配置路由
- id: member_route
uri: lb://gulimall-member
predicates:
- Path=/api/member/**
filters:
- RewritePath=/api/(?<segment>/?.*),/$\{segment}
(3)執行
10.2獲取分類關聯的品牌
選擇分類時,顯示分類的所有品牌
主要步驟:
-
根據分類Id(
catId
)查詢CategoryBrandRelationEntity
分類和品牌對映表,獲取所有的分類和品牌對映關係 -
迴圈分類和品牌對映關係,查詢每個品牌,返回品牌列表集合
-
流式程式設計品牌列表轉換vo返回給前端
根據分類Id(catId
)查詢CategoryBrandRelationEntity
分類和品牌對映表,獲取所有的分類和品牌對映關係
迴圈分類和品牌對映關係,查詢每個品牌,返回品牌列表集合
流式程式設計品牌列表轉換vo返回給前端
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
)查詢改商品下的所有屬性分組 - 遍歷所有屬性分組,根據屬性分組和規格引數(基本屬性)的對映表查詢所有的規格引數(基本屬性)
- 返回改分組下的所有屬性
根據商品分類id(catelogId
)查詢改商品下的所有屬性分組
遍歷所有屬性分組,根據屬性分組和規格引數(基本屬性)的對映表查詢所有的規格引數(基本屬性)
根據屬性熟屬性分組attrgroupId
查詢所有規格引數(基礎屬性)
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抽取
基本資訊
規格引數
銷售屬性
sku
資訊
雅丹黑 白沙銀 南糯紫
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 全網通
清涼季 暑期好物 超值鉅惠
設定優惠、會員
點選下一步,儲存商品資訊
,貼上商品儲存的json
生成json
實體
https://www.bejson.com/json2javapojo/new/#google_vignette
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_ladder
、sms_sku_full_reduction
、sms_member_price
- a、
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_ladder
、sms_sku_full_reduction
、sms_sku_full_reduction
、sms_member_price
在common
中建立to使用者服務和服務之間傳遞引數
在gulimall_product
建立com.peng.product.feign.CouponFeignService
服務遠端呼叫優惠卷服務gulimall_coupon
,預設儲存積分的介面自動化工程已經幫我們生成好了
優惠卷服務gulimall_coupon
建立介面/coupon/skufullreduction/saveinfo
儲存優惠資訊
儲存sku
階梯價格資訊(折扣)、sku
滿減資訊、商品會員價格
呼叫優惠卷服務gulimall_coupon
的介面/coupon/spubounds/save
儲存spu
的積分資訊
呼叫優惠卷服務gulimall_coupon
的介面/coupon/skufullreduction/saveinfo
儲存sku
的優惠、滿減等資訊
10.9商品儲存debug完成
配置服務佔用記憶體
-Xmx 100m
設定資料庫當前會話的事務隔離級別是讀未提交READ UNCOMMITTED
,方便除錯的時候檢視資料
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-
SET SESSION
: 指定更改僅影響當前會話。會話結束後,事務隔離級別將恢復預設值或全域性設定。 -
TRANSACTION ISOLATION LEVEL
: 設定事務的隔離級別。 -
READ UNCOMMITTED
: 指定事務隔離級別為READ UNCOMMITTED
,允許讀取未提交的資料(髒讀)。
Spu
描述圖片儲存的時候報錯,需要設定spuId
欄位為INPUT
(輸入)
主鍵非空不自增,所以需要支援自己輸入
除錯的時候邊除錯邊檢視資料是否正常提交
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;
10.10商品儲存其他問題處理
上傳SKU圖片沒有路徑地址就不新增資料
滿量打折和滿價打折大於0才傳送請求,新增相應優惠資料
在優惠卷服務裡新增的時候也加上判斷滿量打折和滿價打折大於0才新增
會員價格大於0的時候才新增商品會員資料
11.商品管理
開發階段每次需要輸入驗證碼,先取消一下這個功能,提高開發效率
前段介面給文字框繫結預設值
後端程式碼註釋掉驗證碼功能
每次登入點選一下就可以了
11.1SPU檢索
主要步驟
spu
管理查詢功能:
- 根據分類、品牌、狀態、id和名字進行條件查詢
- id和名字進行檢索的時候注意條件拼接
id
和spu_name
是或者關係,和其他條件是並且關係
完整程式碼
查詢出來的日期顯示異常
配置application.yaml
spring:
jackson:
date-format: yyyy-mm-dd HH:mm:ss
已格式化時間
11.2SKU檢索
主要步驟
商品管理查詢功能:
- 根據分類、品牌、價格、id和名稱進行檢索
- 分類Id、品牌Id查詢時不能等於0
- 價格可以等於0(>0),但是需要大於0
- 需要對價格進行錯誤容錯,如果不能轉換為
BigDecimal
就不拼接條件
完整程式碼
@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);
}
測試
12.倉庫管理
12.1整合ware服務&獲取倉庫列表
主要步驟:
- 配置倉儲服務執行記憶體
- 倉儲服務
gulimall-ware
配置服務註冊、日誌級別 - 閘道器服務配置
gulimall-gateway
配置倉儲服務gulimall-ware
路由 - 根據倉庫id、倉庫名、倉庫地址、區域編碼查詢倉庫列表
倉儲服務設定執行記憶體
-Xmx100m
application.yaml
配置註冊服務中心和日誌級別
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.188.180:8848 # nacos地址
logging:
level:
com.peng: debug
配置倉儲服務路由
- id: member_route
uri: lb://gulimall-member
predicates:
- Path=/api/member/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
啟動專案,倉儲服務gulimall-ware
已成功註冊
根據倉庫id、倉庫名、倉庫地址、區域編碼查詢倉庫列表
測試
12.2查詢庫存&建立採購需求
主要步驟:
- 1.根據
倉庫id
、skuId
查詢商品庫存 - 2.商品管理建立商品庫存
- 3.採購需求、採購單建立商品庫存,合併採購需求為採購單
- 4.根據倉庫、狀態、關鍵字查詢採購需求
根據倉庫id
、skuId
查詢商品庫存
可以根據商品管理->庫存管理新增庫存
採購需求、採購單建立商品庫存,合併採購需求為採購單
根據倉庫、狀態、關鍵字查詢採購需求
@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
建立新的採購單
查詢未領取的採購單程式碼
管理員列表新增採購人員
建立一個採購單
給建立的採購單分配一個採購人員
合併採購需求建立採購單,沒有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);
}
合併成功
沒有選擇採購單會重新生成採購單
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);
});
}
介面測試
輸入引數1,然後執行
採購單和對應的採購需求狀態都成功改變
合併的時候需要判斷採購單是新建和已分配狀態
// TODO 確認採購單狀態是0,1才可以合併
PurchaseEntity purchaseEntity = this.getById(purchaseId);
if (purchaseEntity.getStatus() != WareConstant.PurchaseStatusEnum.CREATED.getCode()
|| purchaseEntity.getStatus() != WareConstant.PurchaseStatusEnum.ASSIGNED.getCode()) {
return;
}
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
商品名稱
- 遠端呼叫商品服務
-
庫存中有該商品,需要修改庫存:庫存 = 現有庫存 + 增加庫存,條件是商品
skuId
和ware_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
介面服務地址
開啟遠端呼叫服務
@EnableFeignClients(basePackages = "com.peng.ware.feign")
gulimall-ware
服務配置MybatisPlus
分頁外掛
接著測試採購完成的介面
{
"id": 1,
"items": [
{"itemId":1,"status": 3, "reason": ""},
{"itemId":2,"status": 4, "reason": "無貨"}
]
}
我們有一個採購項採購失敗了,所以採購單狀態是有異常,採購項一個是已完成一個是採購失敗
採購流程
建立3個採購需求,不選擇採購單自動建立採購單id為2的採購單
採購單狀態為已分配,採購需求狀態為已分配
領取採購單id為2的採購單
採購單狀態為已領取,採購需求狀態為正在採購
完成採購
採購單狀態為已完成,採購需求狀態為已完成
如果有一個採購需求為採購失敗,那採購單狀態為有異常
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.分散式基礎總結
清醒,知趣,明得失,知進退