谷粒商城-基础篇
谷粒商城
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
$ 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 注册中心
(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 配置中心
(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 上传
(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
(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.分布式基础总结
清醒,知趣,明得失,知进退