谷粒商城(无CURD代码)

GUIGU-GULIMALL项目:

写在开头:

1 Linux环境搭建:

1.1 使用visualBox安装centos/7

  • visualBox进行安装需要cpu开启虚拟化,在开机启动的时候设置主板,CPU configuration,然后点击Intel Vitualization Technology。重启电脑

  • 普通安装linux虚拟机太麻烦,可以利用vagrant可以帮助我们快速地创建一个虚拟机。主要装了vitualbox,vagrant可以帮助我们快速创建出一个虚拟机。他有一个镜像仓库。

  • https://www.vagrantup.com/ 下载vagrant安装,安装后重启系统。cmd中输入vagrant有版本代表成功了。

  • 输入vagrant init centos/7,即可初始化一个centos7系统。(注意这个命令在哪个目录下执行的,他的Vagrantfile就生成在哪里)

1.2 vagrant up启动虚拟机环境。

  • 启动后出现default folder:/cygdrive/c/User/… =>/vagrant。然后ctrl+c退出

  • 前面的页面中有ssh账号信息。vagrant ssh 就会连上虚拟机。可以使用exit退出

  • 下次使用也可以直接vagrant up直接启动,他下面有一个Vagrantfile,不过我们也可以配置环境变量。

  • 启动后再次vagrant ssh连上即可

  • 网络方式是网络地址转换NAT(端口转发),如果其他主机要访问虚拟机,必须由windows端口如3333断发给虚拟机端口如3306。这样每在linux里安一个软件都要进行端口映射,不方便,(也可以在virualBox里挨个设置)。我们想要给虚拟机一个固定的ip地址,windows和虚拟机可以互相ping通。

  • visualBox的网络模式可以参考https://mp.weixin.qq.com/s?__biz=MzI5MDg4ODEzOA==&mid=2247488277&idx=1&sn=012c33bec2984a61850b30b1bb270812&scene=21#wechat_redirect

方式1:在虚拟机中配置静态ip。

方式2:更改Vagrantfile更改虚拟机ip,修改其中的 config.vm.network "private_network",ip:"192.168.56.10",这个ip需要 在windows的ipconfig中查到vitualbox的虚拟网卡ip,然后更改下最后一个数字就行(不能是1,1是我们的主机)。配置完后 vagrant reload重启虚拟机。在虚拟机中ip addr就可以查看到地址了。互相ping也能ping通。

关掉防火墙,VirualBox中第一个网卡设置NAT,第二个网卡设置仅主机

  • 如果ping不了baidu

    • cd /etc/sysconfig/network-scripts

    • ls 一般有ifcfg-eth0 1

    • ip addr 看哪个网格是192.168.56网段,然后vim他

    • vim ifcfg-eth1 加入

GATEWAY=192.168.56.1 DNS1=114.114.114.114 DNS2=8.8.8.8

service network restart

  • 默认只允许ssh登录方式,为了后来操作方便,文件上传等,我们可以配置允许账号密码登录

vim /etc/ssh/sshd_config
修改
PasswordAuthentication yes
重启
service sshd restart
账号root
密码vagrant

1.3 配置源

##### 备份原yum源
​
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
​
##### 使用新yum源
​
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.163.com/.help/CentOS7-Base-163.repo
​
##### 生成缓存
​
yum makecache

1.4 虚拟机安装docker

https://docs.docker.com/engine/install/centos/

#卸载系统之前的docker 
sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine
                  
                  
sudo yum install -y yum-utils
​
# 配置镜像
sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
    
sudo yum install docker-ce docker-ce-cli containerd.io
​
sudo systemctl start docker
# 设置开机自启动
sudo systemctl enable docker
​
docker -v
sudo docker images
​
# 配置镜像加速
​

https://cr.console.aliyun.com/cn-qingdao/instances/mirrors

1.5 根据页面命令执行完命令

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://chqac97z.mirror.aliyuncs.com"]
}
EOF
​
sudo systemctl daemon-reload
sudo systemctl restart docker

 

1.6 安装mysql-docker

  • 用docker安装上mysql,去docker仓库里搜索mysql

    sudo docker pull mysql:5.7
    ​
    ##### --name指定容器名字 -v目录挂载 -p指定端口映射  -e设置mysql参数 -d后台运行
    ​
    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
        
    su root 密码为vagrant,这样就可以不写sudo了
    ​
    [root@localhost vagrant]# docker ps
    CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES
    6a685a33103f        mysql:5.7           "docker-entrypoint.s…"   32 seconds ago      Up 30 seconds       0.0.0.0:3306->3306/tcp, 33060/tcp   mysql
    ​
  • 设置mysql:

##### 进入已启动的容器
​
docker exec -it mysql bin/bash
​
##### 退出进入的容器
​
exit;
​
因为有目录映射,所以我们可以直接在镜像外执行
vi /mydata/mysql/conf/my.conf 
​
[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
​
保存(注意评论区该配置不对,不是collection而是collation)
​
docker restart mysql

1.7安装Redis

  • 如果直接挂载的话docker会以为挂载的是一个目录,所以我们先创建一个文件然后再挂载,在虚拟机中。

##### 在虚拟机中
​
mkdir -p /mydata/redis/conf
touch /mydata/redis/conf/redis.conf
​
docker pull 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
    
# 直接进去redis客户端。
docker exec -it redis redis-cli

 

  • 默认是不持久化的。在配置文件中输入appendonly yes,就可以aof持久化了。修改完docker restart redis,docker -it redis redis-cli

vim /mydata/redis/conf/redis.conf
​
##### 插入下面内容
​
appendonly yes
保存
​
docker restart redis

 

  • 设置redis容器在docker启动的时候启动

docker update redis --restart=always

 

2. IDEA、VScode开发环境:

2.1 maven

在settings中配置阿里云镜像,配置jdk1.8。这个基本都配置过,不贴了

IDEA安装插件lombok,mybatisX。IDEA设置里配置好maven

2.2 vsCode设置

下载vsCode用于前端管理系统。在vsCode里安装插件。

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

2.3 安装git(或者github):

  • 下载git客户端,右键桌面Git GUI/bash Here。去bash,

# 配置用户名
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公钥---随便填个标题---复制
xx  
# 测试
ssh -T git@gitee.com
测试成功,就可以无密给码云推送仓库了

3 项目搭建:

3.1 码云创建仓库:

  • 在码云新建仓库,仓库名gulimall,选择语言java,在.gitignore选中maven(就会忽略掉maven一些个人无需上传的配置文件),许可证选Apache-2.0,开发模型选生成/开发模型,开发时在dev分支,发布时在master分支,创建。

  • 在IDEA中New–Project from version control–git–复制刚才项目的地址

  • IDEA然后New Module–Spring Initializer–com.atguigu.gulimall , Artifact填 gulimall-product。Next—选择web(web开发),springcloud routing里选中openFeign(rpc调用)。

  • 依次创建出以下服务模块

商品服务product 存储服务ware 订单服务order 优惠券服务coupon 用户服务member

  • 共同点:

    • 导入web和openFeign

    • group:com.atguigu.gulimall

    • Artifact:gulimall-XXX

    • 每一个服务,包名com.atguigu.gulimall.XXX{product/order/ware/coupon/member}

    • 模块名:gulimall-XXX

  • 然后右下角显示了springboot的service选项,选择他

  • 从某个项目粘贴个pom.xml粘贴到项目目录,修改他

  • <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    ​
        <groupId>com.atguigu.gulimall</groupId>
        <artifactId>gulimall</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>gulimall</name>
        <description>聚合服务</description>
    ​
        <packaging>pom</packaging>
    ​
        <modules>
            <module>gulimall-coupon</module>
            <module>gulimall-member</module>
            <module>gulimall-order</module>
            <module>gulimall-product</module>
            <module>gulimall-ware</module>
        </modules>
    ​
    </project>
  • 修改总项目的.gitignore,把小项目里的垃圾文件在提交的时候忽略掉,比如HELP.md

    • **/mvnw
      **/mvnw.cmd
      **/.mvn
      **/target/
      .idea
      **/.gitignore
    • 在version control/local Changes,点击刷新看Unversioned Files,可以看到变化。

    • 全选最后剩下21个文件,选择右键、Add to VCS。

    • 在IDEA中安装插件:gitee,重启IDEA。

    • 在D额fault changelist右键点击commit,去掉右面的勾选Perform code analysis、CHECK TODO,然后点击COMMIT,有个下拉列表,点击commit and push才会提交到云端。此时就可以在浏览器中看到了。commit只是保存更新到本地.push才是提交到gitee.

3.2 数据库:

3.2.1 数据库地址

  • https://gitee.com/HanFerm/gulimall/tree/master/sql文件
  • pms库里面少了pms_category_brand_relation表

3.2.2 docker配置数据库:

  • sudo docker ps
    sudo docker ps -a
    # 这两个命令的差别就是后者会显示  【已创建但没有启动的容器】
    ​
    # 我们接下来设置我们要用的容器每次都是自动启动
    sudo docker update redis --restart=always
    sudo docker update mysql --restart=always
    # 如果不配置上面的内容的话,我们也可以选择手动启动
    sudo docker start mysql
    sudo docker start redis
    # 如果要进入已启动的容器
    sudo docker exec -it mysql /bin/bash
    # /bin/bash就是进入一般的命令行,如果改成redis就是进入了redis
  • 用navicat或者sqlyog或者其他可视化数据库操作软件建表:

    • gulimall-oms
      gulimall-pms
      gulimall-sms
      gulimall-ums
      gulimall-wms
      ​
    • 然后打开对应的sql在对应的数据库中执行。依次执行。(注意sql文件里没有建库语句)

3.3 人人开源:

3.3.1 人人前后端:

  • 在码云上搜索人人开源,我们使用renren-fast(后端)、renren-fast-vue(前端)项目。

  • git下载项目:

    • git clone https://gitee.com/renrenio/renren-fast.git
      git clone https://gitee.com/renrenio/renren-fast-vue.git
  • 下载后删除文件夹中的.git文件,同时分别导入到idea和VScode中。

  • 在IDEA项目里的pom.xml添加一个renrnen-fast

    • <modules>
          <module>gulimall-coupon</module>
          <module>gulimall-member</module>
          <module>gulimall-order</module>
          <module>gulimall-product</module>
          <module>gulimall-ware</module>
          <module>renren-fast</module>
      </modules>
      ​
  • 然后打开renren-fast/db/mysql.sql,复制全部,在sqlyog中创建库guli-admin,粘贴刚才的内容执行。然后修改项目里renren-fast中的application.yml,修改application-dev.yml中的数库库的url,通常把localhost修改为192.168.56.10即可。然后该对后面那个数据库

    • url: jdbc:mysql://192.168.56.10:3306/guli_admin?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
      username: root
      password: root
    • 然后运行该java项目下的RenrenApplication

      浏览器输入http://localhost:8080/renren-fast/ 得到{“msg”:“invalid token”,“code”:401}就代表无误

  • 前端启动vue项目,后端启动SpringBoot项目。略

3.3.2 人人逆向工程:

  • 逆向工程搭建:

    • git clone https://gitee.com/renrenio/renren-generator.git
    • 下载到桌面后,同样把里面的.git文件删除,然后移动到我们IDEA项目目录中,同样配置好根项目pom.xml(将renren-generator添加进去)

    • <modules>
          <module>gulimall-coupon</module>
          <module>gulimall-member</module>
          <module>gulimall-order</module>
          <module>gulimall-product</module>
          <module>gulimall-ware</module>
          <module>renren-fast</module>
          <module>renren-generator</module>
      </modules>
    • 在maven中刷新一下,让项目名变粗体,稍等下面进度条完成。修改application.yml

    • url: jdbc:mysql://192.168.56.10:3306/gulimall-pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
      username: root
      password: root
    • 然后修改generator.properties:

    • # 主目录
      mainPath=com.atguigu
      #包名
      package=com.atguigu.gulimall
      #模块名
      moduleName=product
      #作者
      author=hh
      #email
      email=55333@qq.com
      #表前缀(类名不会包含表前缀) # 我们的pms数据库中的表的前缀都pms
      # 如果写了表前缀,每一张表对于的javaBean就不会添加前缀了
      tablePrefix=pms_
    • !!! 在代码生成器的的resources目录下的template文件夹下的controller.java.vm中注释掉下面的代码

    • //import org.apache.shiro.authz.annotation.RequiresPermissions;
      //@RequiresPermissions("${moduleName}:${pathName}:list")
      
      第二行举个例,实际要注释掉每个方法上面的@RequiresPermissions
          原因:此项目不用
    • 运行逆向工程,依次生成product、ware、member、order、coupon板块。

  • 抽取common板块:

    • 逆向生成的所有的module中都需要引入例如mybatis,lombok等等的相同的依赖,所以我们可以新建一个common板块,用去配置所有的依赖和一些常用的其他的类。

      • 新建common module:略

      • 再common module中的pom文件中添加:

      • <!-- 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包。发送http请求 -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.13</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
      • 创建完common后我们可以将每一个微服务中都引入common,作为common的子项目

      • <dependency>
            <groupId>com.atguigu.gulimall</groupId>
            <artifactId>gulimall-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
  • 配置逆向生成的代码运行所需环境

    • 复制renren-fast----utils包下的Query和PageUtilsRConstant复制到common项目的java/com.atguigu.common.utils下。

    • 引入mybatis-plus和servlet-api

      <!-- 数据库驱动 https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.17</version>
      </dependency>
      <!--tomcat里一般都带-->
      <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>servlet-api</artifactId>
          <version>2.5</version>
          <scope>provided</scope>
      </dependency>
    • 删掉common里xss/xssfiler和XssHttpServletRequestWrapper.在product项目的resources目录下新建application.yml

    • spring:
        datasource:
          username: root
          password: root
          url: jdbc:mysql://192.168.56.10:3306/gulimall-pms
          driver-class-name: com.mysql.jdbc.Driver
      ​
      # MapperScan
      # sql映射文件位置
      mybatis-plus:
        mapper-locations: classpath:/mapper/**/*.xml
        global-config:
          db-config:
            id-type: auto
    • 在主类上加入MapperScan("你的dao包所在的位置")

    • 测试

      • @SpringBootTest
        class gulimallProductApplicationTests {
            @Resource
            BrandService brandService;
        ​
            @Test
            void contextLoads() {
                BrandEntity brandEntity = new BrandEntity();
                brandEntity.setDescript("手机");
                brandEntity.setName("小米");
                brandService.save(brandEntity);
                System.out.println("保存成功");
            }
        }
      • 数据库中如果新增了这条数据则测试成功,这个逆向生成的代码可以用了。

  • 继续生成其余的module,构建gulimall整个项目。

  • 分别设置端口号:

    • gulimall-coupon     7000
      gulimall-geteway    88
      gulimall-member     8000
      gulimall-order      9000
      gulimall-product    11000
      gulimall-ware       12000
      renren-fast         8083
      renren-generator    80

 

4 SpringCloud Alibaba

4.1 简介:

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

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

  • 伴随着springcloud中的Eureka、Hystrix等组件的停止维护,spring cloud alibaba 中的nacos、sentinel逐渐成为替代方案。

  • Nacos可以作为微服务架构中的注册中心(spring cloud的其他方案:eurekba、consul、zookeeper)以及配置中心(spring cloud config)来使用。

  • Sentinel可以作为服务保护替代方案,替代springcloud中的Hystrix,实现服务限流、降级、熔断等功能。

4.2本项目中的技术运用:

  • 本项目采用nacos作为注册及配置中心,网关采用getway,采用Spring cloud提供的openFeign进行远程调用服务。

  • 在common中引入依赖:

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

4.3 Nacos:

4.3.1 简介:

4.3.2 配置nacos地址:

  • 在product项目中的核心配置文件中配置nacos地址:

  • spring:
      datasource:
        username: root
        password: root
        url: jdbc:mysql://192.168.56.10:3306/mall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.jdbc.Driver
    
      cloud:
        nacos:
          discovery:
            server-addr: 127.0.0.1:8848
  • 在common中引入依赖,方便其他服务使用:

  • <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
  • 在启动类上加上@EnableDiscoveryClient注解开启服务的注册及发现功能。

  • 再在服务核心配置文件中配置当前服务中心名和当前模块的名字:

  • application:
      name: gulimall-coupon
  • 后面依次给member、coupon配置nacos。

4.4 远程调用与注册中心:

4.4.1 使用远程调用:

  • 如果会员服务想调用优惠卷服务查询所有的优惠券,那么应该在会员服务里引入openfeign依赖,那么他就有了远程调用其他服务的能力。:

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

4.4.2 测试远程调用:

  • 在CouponController中写入以下内容:

  • @RequestMapping("coupon/coupon")
    public class CouponController {
        @Autowired
        private CouponService couponService;
    
        @RequestMapping("/member/list")
        public R membercoupons(){    //全系统的所有返回都返回R
            // 应该去数据库查用户对于的优惠券,但这个我们简化了,不去数据库查了,构造了一个优惠券给他返回
            CouponEntity couponEntity = new CouponEntity();
            couponEntity.setCouponName("满100-10");//优惠券的名字
            return R.ok().put("coupons",Arrays.asList(couponEntity));
        }
  • 在member的配置类上加注解@EnableDiscoveryClient,告诉member是一个远程调用客户端,member要调用东西的:

  • /*
    * 想要远程调用的步骤:
    * 1 引入openfeign
    * 2 编写一个接口,接口告诉springcloud这个接口需要调用远程服务
    * 	2.1 在接口里声明@FeignClient("gulimall-coupon")他是一个远程调用客户端且要调用coupon服务
    * 	2.2 要调用coupon服务的/coupon/coupon/member/list方法
    * 3 开启远程调用功能 @EnableFeignClients,要指定远程调用功能放的基础包
    * */
    @EnableFeignClients(basePackages="com.atguigu.gulimall.member.feign")//扫描接口方法注解
    @EnableDiscoveryClient// 注册到nacos
    @SpringBootApplication
    public class gulimallMemberApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(gulimallMemberApplication.class, args);
    	}
    }
  • 在MeemberController中写测试请求:

  • @RestController
    @RequestMapping("member/member")
    public class MemberController {
        @Autowired
        private MemberService memberService;
    
        @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"));
        }
  • 请求你的url,测试有没有返回一个R对象。

4.5 nacos作为配置中心:

4.5.1 添加配置中心依赖:

  • <dependency>
         <groupId>com.alibaba.cloud</groupId>
         <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
     </dependency>
  • 在coupons项目中创建/src/main/resources/bootstrap.properties ,这个文件是springboot里规定的,他优先级别application.properties高.所以先使用nacos里配置的配置。

  • # 改名字,对应nacos里的配置文件名
    spring.application.name=gulimall-coupon
    spring.cloud.nacos.config.server-addr=127.0.0.1:8848

4.5.2 nacos管理中心配置:

  • 默认路径:localhost:8848

  • 在配置列表中添加:gulimall-coupon.properties

  • coupon.user.name=Tx
    coupon.user.age=20
  • 发布并重启coupon(生产中加入@RefreshScope即可),http://localhost:7000/coupon/coupon/test

  • 在controller中加入注解@RefreshScope可以在不重启服务的情况下在nacos中配置应用到服务中。

4.5.3配置中心的进阶使用:

在nacos浏览器中还可以配置:

  • 命名空间:用作配置隔离。(一般每个微服务一个命名空间)默认public。默认新增的配置都在public空间下

  • 开发、测试、开发可以用命名空间分割。properties每个空间有一份。也可以为每个微服务配置一个命名空间,微服务互相隔离

  • 在bootstrap.properties里配置(测试完去掉,学习不需要)

    # 可以选择对应的命名空间 # 写上对应环境的命名空间ID
    spring.cloud.nacos.config.namespace=b176a68a-6800-4648-833b-be10be8bab00
  • 配置集:一组相关或不相关配置项的集合。

  • 配置集ID:类似于配置文件名,即Data ID

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

  • # 更改配置分组
    spring.cloud.nacos.config.group=DEFAULT_GROUP

4.6 网关getway:

4.6.1 简介:

4.6.2 核心:

  • Route: The basic building block of the gateway. It is defined by an ID, a destination URI, a collection of predicates断言, and a collection of filters. A route is matched if the aggregate predicate is true.发一个请求给网关,网关要将请求路由到指定的服务。路由有id,目的地uri,断言的集合,匹配了断言就能到达指定位置

  • Predicate断言: This is a Java 8 Function Predicate. The input type is a Spring Framework ServerWebExchange. This lets you match on anything from the HTTP request, such as headers or parameters.就是java里的断言函数,匹配请求里的任何信息,包括请求头等。根据请求头路由哪个服务

  • Filter: These are instances of Spring Framework GatewayFilter that have been constructed with a specific factory. Here, you can modify requests and responses before or after sending the downstream request.过滤器请求和响应都可以被修改。

  • 客户端发请求给服务端。中间有网关。先交给映射器,如果能处理就交给handler处理,然后交给一系列filer,然后给指定的服务,再返回回来给客户端。

4.6.3 使用网关服务:

  • 创建:略

  • 添加common依赖:略

  • nacos中新建getway命名空间,并新建guilmall-gateway.yml.yml.

  • 开启服务注册及发现:

    • 服务发现:主类中添加@EnableDiscoveryClient注解

    • 服务注册:配置nacos注册中心地址applicaion.properties

    • spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
      spring.application.name=gulimall-gateway
      server.port=88
    • bootstrap.properties 填写nacos配置中心地址:

    • spring.application.name=gulimall-gateway
      spring.cloud.nacos.config.server-addr=127.0.0.1:8848
      spring.cloud.nacos.config.namespace=自己建的命名空间的地址
    • 配置本服务在nacos中的服务名:

    • spring:
          application:
              name: gulimall-gateway
  • 设置网关路由转发:

    • spring:
        cloud:
          gateway:
            routes:
              - id: product_route
                uri: lb://gulimall-product
                predicates:
                  - Path=/api/product/**
                filters:
                  - RewritePath=/api/(?<segment>/?.*),/$\{segment}
      ​
              - id: third-party-route
                uri: lb://mall-third-service
                predicates:
                  - Path=/api/thirdparty/**
                filters:
                  - RewritePath=/api/thirdparty/(?<segment>/?.*),/$\{segment}
      ​
      ​
              - id: admin_route
                uri: lb://renren-fast
                predicates:
                  - Path=/api/**
                filters:
                  - RewritePath=/api/(?<segment>/?.*),/renren-fast/$\{segment}
                  
              - id: test_route
                uri: https://www.baidu.com
                predicates:
                  - Query=url,baidu
      ​
      ​
    • 测试:访问localhost:8080/hello?url=baidu

 

5 三级分类

  • 评论区有带数据的sql文件

5.1 element-ui的使用

  • element-ui提供了tree树形组件,他的数据是以data属性显示的。而他的子菜单是由data里的children属性决定的。

  • 树形组件的使用可以参考element-ui里面实例。

5.2 新建菜单:

  • 在系统管理-》菜单管理中新建商品系统目录,路径:product/category

  • 同样在这里新建分类维护菜单,分类维护是商品系统的子菜单。路径:product/category

5.3 关于登陆页面验证码不显示的问题:

5.3.1 问题分析:

  • 打开后台:我们会发现,他要请求localhost:8001/renren-fast/product/category/list/tree这个url。

  • 但实际上人人工程的端口号以及在之前修改过的8083,自然我们拿不到验证码的图片

5.3.2 解决方法:

  • 搭建网关,通过网关路由到8083端口。

5.3.3 具体实现:

  • 修改vue工程里的请求路径:static/config/index.js

  • window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';
  • 将renren-fast注册到nacos中,再通过getway中的路径重写访问目的端口号。

    • renren-fast注册到nacos:

      <dependency>
          <!-- 里面有nacos注册中心 -->
          <groupId>com.atguigu.gulimall</groupId>
          <artifactId>gulimall-common</artifactId>
          <version>0.0.1-SNAPSHOT</version>
      </dependency> 
      ​
    • renren-fast项目中添加:

    • spring:
        application:
          name: renren-fast  # 意思是把renren-fast项目也注册到nacos中(后面不再强调了),这样网关才能转发给
        cloud:
          nacos:
            discovery:
              server-addr: 127.0.0.1:8848 # nacos
              
              
      server: 
        port: 88  # 别和gateway冲突了
      ​
    • fast启动类上加上注解@EnableDiscoveryClient

  • getway中路径重写:

    •     - id: admin_route
            uri: lb://renren-fast # 路由给renren-fast
            predicates:  # 什么情况下路由给它
              - Path=/api/** # 默认前端项目都带上api前缀,就是我们前面题的localhost:88/api
            filters:
              - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}  # 把/api/* 改变成 /renren-fast/*
      # fast找
  • 后出现跨域问题:见下:

5.4 跨域问题:

5.4.1 解决方案一:

  • img

5.4.2 解决方案二

  • 服务端配置允许跨域

  • 在网关中定义“GulimallCorsConfiguration”类,该类用来做过滤,允许所有的请求跨域。

  • package com.atguigu.gulimall.gateway.config;
    
    @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);
        }
    }
  • 修改renren-fast项目,注释掉“io.renren.config.CorsConfig”类。

  • 再次访问就可以显示验证码了。

5.5 继续实现三级分类:

5.5.1 网关转发product服务

  • 在显示分类信息时,利用抓包工具可以看到请求的路径是不存在的,这是因为网关在进行转发时识别到了/api并将这次请求转发到了renren-fast模块,而我们应该是要请求product模块里面的内容,因此会显示404错误。

  • 解决方法:在getway中继续配置详细的路径映射:

  •     - id: product_route
          uri: lb://gulimall-product # 注册中心的服务
          predicates:
            - Path=/api/product/**
          filters:
            - RewritePath=/api/(?<segment>/?.*),/$\{segment}
      
  • 此处有个知识点:精确的路由一点更要放在模糊路由的前面,getway中匹配路由是按照你代码的顺序自上而下的,命中路由后就不会再继续寻找下去。

5.5.2category.vue(主要的代码)

  • 批量删除
    • 	//批量删除
      BatchDelete(){
            let catIds=[];
            let names=[]
            let checkedNodes=this.$refs.menutree.getCheckedNodes()
            console.log("被选中的元素",checkedNodes)
            //遍历拿到每一个结点的id,根据id进行删除
            for(let i=0;i<checkedNodes.length;i++)
            {
              catIds.push(checkedNodes[i].catId)
              names.push(checkedNodes[i].name)
            }
            //向后台提交批量删除请求
            this.$confirm(`确认批量删除当前选中的【${names}】节点?`, '提示', {
              confirmButtonText: '确定',
              cancelButtonText: '取消',
              type: 'warning'
            }).then(() => {
              this.$http({
                url: this.$http.adornUrl('/product/category/delete'),
                method: 'post',
                data: this.$http.adornData(catIds, false)
              }).then(({data}) => {
                this.$message({
                  type: 'success',
                  message: '删除成功!'
                })
                this.getMenus()
                //this.expandKey = [node.parent.data.catId]
              })
            }).catch(() => {
              this.$message({
                type: 'info',
                message: '已取消删除'
              })
            })
      
          }
  • 拖拽节点并更新节点的数据
    • handleDrop(draggingNode, dropNode, dropType, ev) {
         console.log("handleDrop: ", draggingNode, dropNode, dropType);
         
         //1 当前节点最新的父节点
         let pCid = 0;
         let siblings = null;
         if (dropType == "before" || dropType == "after") {
           pCid =
             dropNode.parent.data.catId == undefined
               ? 0
               : dropNode.parent.data.catId;
           siblings = dropNode.parent.childNodes;
         } else {
           pCid = dropNode.data.catId;
           siblings = dropNode.childNodes;
         }
         this.pCid.push(pCid);
         //2 当前拖拽节点的最新顺序
         for (let i = 0; i < siblings.length; i++) {
           if (siblings[i].data.catId == draggingNode.data.catId) {
             // 如果遍历的是当前正在拖拽的节点
             let catLevel = draggingNode.level;
             if (siblings[i].level != draggingNode.level) {
               // 当前节点的层级发生变化
               catLevel = siblings[i].level;
               // 修改他子节点的层级
               this.updateChildNodeLevlel(siblings[i]);
             }
             this.updateNodes.push({
               catId: siblings[i].data.catId,
               sort: i,
               parentCid: pCid,
               catLevel: catLevel,
             });
           } else {
             this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });
           }
         }
         //3 当前拖拽节点的最新层级
         console.log("updateNodes", this.updateNodes);
         },
       updateChildNodeLevlel(node) {
         if (node.childNodes.length > 0) {
           for (let i = 0; i < node.childNodes.length; i++) {
             var cNode = node.childNodes[i].data;
             this.updateNodes.push({
               catId: cNode.catId,
               catLevel: node.childNodes[i].level,
             });
             this.updateChildNodeLevlel(node.childNodes[i]);
           }
         }
       },
       allowDrop(draggingNode,dropNode,type){
         //被拖动节点的总层数
         console.log("allowDrop",draggingNode,dropNode,type)
         
         this.countNodeLevel(draggingNode)
         //当前拖动的节点+父节点所在的深度不大于三就可以拖动
         //console.log(this.maxLevel)
         //console.log(draggingNode.data.catLevel)
         let deep=Math.abs(this.maxLevel-draggingNode.level+1)
         console.log("深度:",deep)
         
         if(type=="inner")
         return (deep+dropNode.level)<=3
         else
         return (deep+dropNode.parent.level)<=3
         
       },
         
       countNodeLevel(node)
       {
         //找到所有的子节点,求出最大深度
         if(node.childNodes != null &&node.childNodes.length>0)
         {
           for(let i=0;i<node.childNodes.length;i++)
           {
             if(node.childNodes[i].catLevel>this.maxLevel)
             {
               this.maxLevel=node.childNodes[i].cLevel
             }
             this.countNodeLevel(node.childNodes[i]);
           }
         }
       }
         

6 品牌管理

6.1 逆向工程生成

  • 使用逆向工程生成前后端代码:略

6.2 配置阿里云OSS对象存储:

6.2.1 开通阿里云服务:

6.2.2 引入依赖:

  • 在product服务的pom文件中引入阿里云sdk:

  •     <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.15.0</version>
            <scope>test</scope>
        </dependency>

6.2.3 配置上传文件:

  • String endpoint = "";
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = "";
    String accessKeySecret = "";
    ​
    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    ​
    // 创建PutObjectRequest对象。
    // 填写Bucket名称、Object完整路径和本地文件的完整路径。Object完整路径中不能包含Bucket名称。
    // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
    PutObjectRequest putObjectRequest = new PutObjectRequest("你选择的bucket", "文件名", new File("文件路径"));
    ​
    // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
    // ObjectMetadata metadata = new ObjectMetadata();
    // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
    // metadata.setObjectAcl(CannedAccessControlList.Private);
    // putObjectRequest.setMetadata(metadata);
    ​
    // 上传文件。
    ossClient.putObject(putObjectRequest);
    ​
    // 关闭OSSClient。
    ossClient.shutdown(); 

6.2.4 使用子账户管理OSS对象存储:

  • image-20220814224459049

  • 步骤:略(跟着视频来挺简单的)

6.2.5 测试代码

  • @Test
    public void testUpload() {
        String endpoint = "xxx";
        String accessKeyId = "xxx";
        String accessKeySecret = "xxx";
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        PutObjectRequest putObjectRequest = new PutObjectRequest("gulimall-kaisarh", "login.png", new File("C:\\Users\\Administrator\\Pictures\\login.png"));
        ossClient.putObject(putObjectRequest);
        ossClient.shutdown();
        System.out.println("上传完成");
    }

6.3 SpringCloud Alibaba-OSS实现对象存储:

6.3.1 引入SpringCloud Alibaba-OSS

  • 由于很多服务都可能使用文件上传,因此直接在gulimall-common中引入:

  • --引入spring-alibaba-oss-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
    </dependency>
  • 配置阿里云oss账号信息:

  • spring: 
     cloud:
      alicloud: 
       oss:
        endpoint: oss-cn-shanghai.aliyuncs.com
       access-key: xxxxxx
       secret-key: xxxxxx
  • 测试使用OssClient 上传

  • @Resources
    OSSClient ossClient;
    
    @Test
    public void testUpload() throws FileNotFoundException {
        InputStream inputStream = new FileInputStream("C:\\Users\\Administrator\\Pictures\\removeAll.png");
        ossClient.putObject("gulimall-kaisarh", "removeAll.png", inputStream);
        ossClient.shutdown();
        System.out.println("上传完成");
    }

 

6.4 校验

6.4.1:表单校验自定义校验器

  • 前端表单校验:自定义一个校验器,

  • 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"
              }
            ]
  • callback()参数为空表示校验通过的情况。

6.4.2 JSR303规范及后端校验:

  • JSR303是一套JavaBean参数校验的标准,定义了很多常用的校验注解。可以直接将这些注解加在我们JavaBean的属性上面就可以在需要校验的时候进行校验了。

  • 使用方法:

    • 给 bean 添加校验注解 constraints 并定义自己的错误提示

    • controller 给参数添加 @Valid 注解

    • 给校验的bean后面紧跟一个BindingResult, 就可以获取到校验的结果

  • 分组校验

    • 先定义分组,再再校验注解上加参数groups={组名.class}.

  • 例如:

    •     /**
           * 品牌id
           */
          //@Null(message = "修改时品牌id必须为空",groups = {UpdateGroup.class})
          //@NotNull(message = "新增时品牌时不能指定id",groups = {AddGroup.class})
          @TableId
          private Long brandId;
      ​
  • 统一异常处理

    • 兼容并处理返回的异常:

    • @Slf4j
      @RestControllerAdvice
      public class GulimallExceptionControllerAdvice {
      ​
          @ExceptionHandler(value= MethodArgumentNotValidException.class)
          public R handleValidException(MethodArgumentNotValidException e) {
              log.error("数据校验出现问题{},异常类型{}",e.getMessage(),e.getClass());
              BindingResult bindingResult = e.getBindingResult();
              Map<String, String> map = new HashMap<>();
              bindingResult.getFieldErrors().forEach((fieldError) -> {
                  String message = fieldError.getDefaultMessage();
                  String filed = fieldError.getField();
                  map.put(filed, message);
              });
              return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(), BizCodeEnume.VAILD_EXCEPTION.getMessage()).put("data", map);
          }
      ​
          //能够兼容所有的异常
          @ExceptionHandler(value = Throwable.class)
          public  R handleException(Throwable throwable)
          {
              return R.error(BizCodeEnume.UNKNOWN_EXCEPTION.getCode(),BizCodeEnume.UNKNOWN_EXCEPTION.getMessage());
          }
      }

6.5 SPU及SKU:

6.5.1 简介:

  • SPU:Standard Product Unit(标准化产品单元)

    • 是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。

    • 如iphoneX是SPU

  • SKU:Stock Keeping Unit(库存量单位)

    • 即库存进出计量的基本单元,可以是以件,盒,托盘等为单位。SKU这是对于大型连锁超市DC(配送中心)物流管理的一个必要的方法。现在已经被引申为产品同意编号的简称,每种产品均对应有唯一的SKU号。

    • 如iphoneX 64G 黑色 是SKU

  • SPU决定规格参数。SKU决定销售属性。

6.6 基本属性及销售属性:

6.6.1 简介:

  • 基本属性:同一个SPU拥有的特性叫基本属性。如机身长度,这个是手机共用的属性。而每款手机的属性值不同。商品介绍、规格与 包装都属于基本属性,是SPU的特性。规格参数属于基本属性。

  • 销售属性:决定SKU的属性,如颜色、

6.7 前端抽取组件、父子组件的交互:

6.7.1 抽取组件:

  • 所谓抽取组件就是将一个常用的组件封装成一个vue组件,需要使用时在其他的页面中直接调用这个组件即可。

  • 例如:category.vue:

  • <template>
      <div>
        <el-input placeholder="输入关键字进行过滤" v-model="filterText"></el-input>
        <el-tree
          :data="menus"
          :props="defaultProps"
          node-key="catId"
          ref="menuTree"
          @node-click="nodeclick"
          :filter-node-method="filterNode"
          :highlight-current = "true"
        ></el-tree>
      </div>
    </template>
    <script>
        略
    </script>
  • 在其他组件中使用这个组件就先在script中引入,再components中声明,后再使用。

6.7.2 父子组件的交互:

  • 在子组件中调用this.$emit("父组件中的方法名",参数);

  • 在父组件在声明子组件传递的方法,并调用自己的方法。

  • 例如:

  • 子组件:

    • <template>
        <div>
          <el-input placeholder="输入关键字进行过滤" v-model="filterText"></el-input>
          <el-tree
            :data="menus"
            :props="defaultProps"
            node-key="catId"
            ref="menuTree"
            @node-click="nodeclick"
            :filter-node-method="filterNode"
            :highlight-current = "true"
          ></el-tree>
        </div>
      </template>
          
                
       //实现      
      nodeclick(data, node, component) {
      console.log("子组件category的节点被点击", data, node, component);
      //向父组件发送事件;
      this.$emit("tree-node-click", data, node, component);
      }
  • 父组件:

    •     <el-col :span="6">
            <category @tree-node-click="treenodeclick"></category>
          </el-col>
              
          //感知树节点被点击
          treenodeclick(data, node, component) {
            if (node.level == 3) {
              this.catId = data.catId;
              this.getDataList(); //重新查询
            }
          }

6.7.3 关于pubsub的问题:

  • 安装:先试试npm如果不行再使用cnpm

  • 有些地方不需要使用this.pubsub来调用,可以改成pubsub直接调用(‘publish‘ of undefined异常解决办法)。

6.8 各种对象:

  • PO持久对象:Po就是对数据库中某个表中的一条记录,多个记录可以是用Po集合,po不包含对数据库的操作

  • DO领域对象:从现实世界中抽取出来的邮箱或无形的业务实体。

  • TO数据传输对象:不同应用程序之间传输的对象。

  • DTO数传输对象:泛指用于展示层与业务之间的传输对象

  • VO值对象:用于业务层之间的数据传递,但是应该是抽象出的业务对象,可与表对应也可以不用,根据业务取药。 视图对象,接收页面传来的数据,也可封装为完成业务逻辑,界面所需要的数据。

  • BO业务对象:把业务逻辑封装为一个对象。

  • POJO剪短无规则java对象:传统意义的java对象。

  • DAO数据访问对象:持久化层提供接口,用于访问数据库。

6.9 枚举类型的使用:

6.9.1 自定义枚举类型:

  • 创建枚举类型类,

    • public class ProductConstant {
          public enum AttrEnum {
              //可以理解为实现声明了两种类型
              ATTR_TYPE_BASE(1, "基本属性"),
              ATTR_TYPE_SALE(0, "销售属性");
              private int code;
              private String msg;
              AttrEnum(int code, String msg) {
                  this.code = code;
                  this.msg = msg;
              }
              public int getCode() {
                  return code;
              }
              public String getMsg() {
                  return msg;
              }
          }
      }
  • 使用:

    • 类名。枚举类型名。事先声明的类型。方法

6.10 vo的使用:

6.10.1 简介:

  • 在web项目中,vo一般多用于封装一个包含了不同的类中的属性的情况,或者处理对象包含了一个类及类外其他的属性的情况。

    • import lombok.Data;
      @Data
      public class AttrVo {
          private Long attrId;
          private String attrName;
          private Integer searchType;
          private String icon;
          private String valueSelect;
          private Integer attrType;
          private Long enable;
          private Long catelogId;
          private Integer showDesc;
          private Long attrGroupId;
      }
    • 在这个vo中,showDesc,searchType等等都是Attr类中所不具有的属性,封装在一个对象中便于对数据的处理。

  • 可以利用BeanUtils对对象的属性值进行快速拷贝,前提是数据源与目标两者的属性名相同。vo中用这个很方便(vo中的大多数属性都是从实体类中拷贝过来的)。

    • BeanUtils.copyProperties(源,目标)

6.11远程调用其他的服务:

  • 在需要调用其他服务的服务里新建feign包:包下新建一个接口。

    • @FeignClient("gulimall-coupon")
      public interface CouponFeignService {
          @PostMapping("coupon/spubounds/save")
          R saveSpuBounds(SpuBoundTo spuBoundTo);
      ​
          @PostMapping("coupon/skufullreduction/saveinfo")
          R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
      }
    • 注解@FeignClient指明要调用的服务是哪个。

    • 注意:

      • 请求路径一定要写全(从controller那一层开始写)

      • 只用声明调用某个服务里的哪个方法就行。

  • 在需要调用其他微服务的启动类上添加注解

    • @EnableFeignClients("com.atguigu.gulimall.product.feign")
  • 在被调用的服务启动类上开启服务发现功能

    • @EnableDiscoveryClient
  • 关于FeignClient的两种写法:

    • 1.让所有的请求经过网关
      @FeignClient("gulimall-getway")
      @RequestMapping("/api/product/skuinfo/info/{skuId}")
        
      2.不经过网关直接访问product的服务
       @FeignClient("gulimall-product")
      @RequestMapping("/product/skuinfo/info/{skuId}")

7 仓储服务:

  • 防止事务回滚:

    • try{}catch():如果失败则抛出异常,自己解决

    • 高级篇:消息队列实现一致性事务

  • @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);
          }
      
      }
  •  
posted @ 2022-08-17 20:13  张满月。  阅读(134)  评论(0编辑  收藏  举报