商城项目-分布式基础-01

279 阅读6分钟

笔记

文档

easydoc.xyz/s/78237135/…

blog.csdn.net/hancoder/ar…

项目介绍

1.项目架构

image-20201024173433290

2.项目划分

image-20201024175340213

环境搭建

1、docker安装

#卸载系统之前的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

# 配置镜像加速
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://xdub84vk.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

2、docker安装mysql

# --name指定容器名字 -v目录挂载 -p指定端口映射  -e设置mysql参数 -d后台运行
sudo docker run -p 3306:3306 --name mysql \
-v /root/app/mysql/log:/var/log/mysql \
-v /root/app/mysql/data:/var/lib/mysql \
-v /root/app/mysql/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7


# 进入容器内部
docker exec -it 471 bash
whereis mysql
#mysql: /usr/bin/mysql /usr/lib/mysql /etc/mysql /usr/share/mysql


# 修改配置文件
vim /root/app/mysql/conf/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

# 重启
docker restart mysql
# 查看配置文件是否生效
docker exec -it 471 bash
root@471b51c04bd0:/# cat /etc/mysql/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

3、docker安装redis

# 创建挂载目录
mkdir -p /root/app/redis/conf
touch /root/app/redis/conf/redis.conf

# 启动
docker run -p 6379:6379 --name redis \
-v /root/app/redis/data:/data \
-v /root/app/redis/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf

# 进入容器内
docker exec -it redis redis-cli

#设置持久化 设置aof类型的持久化方式
vim /root/app/redis/conf/redis.conf
appendonly yes

配置自动重启

[root@like conf]# docker update redis --restart=always
redis
[root@like conf]# docker update mysql --restart=always
mysql

4、创建后端项目

1.共同点
	jar包:web,openfeign
	包名:com.lk.mall.xxx
	
2.父项目
<?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.like.mall</groupId>
    <artifactId>mall</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mall</name>
    <description>商城父项目</description>
    <packaging>pom</packaging>

    <modules>
        <module>mall-coupon</module>
        <module>mall-member</module>
        <module>mall-order</module>
        <module>mall-product</module>
        <module>mall-ware</module>
    </modules>

</project>

image-20201024204508138

5、创建数据库

image-20201024205908619

6、人人开源项目

后端

gitee.com/renrenio/re…

前端

gitee.com/renrenio/re…

下载完成后启动

gitee.com/renrenio/re…

7、生成代码

生成各个库的代码

image-20201025132146646

分布式-微服务

在common中添加依赖

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

1、服务发现-nacos

添加依赖

<!--nacos服务发现-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

添加配置

spring:
  # 微服务
  cloud:
    nacos:
      # 注册地址
      discovery:
        server-addr:  47.112.150.204:8848
  application:
    name: mall-ware

启动nacos

cd /root/app/nacos/bin
sh startup.sh -m standalone	

image-20201025144444822

2、服务远程调用-openfeign

==测试:member 远程调用coupon中的方法==

jar 包

<!--服务调用-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency> 

步骤:

  1. 编写==被调用服务的方法==
@RestController
@RequestMapping("coupon/coupon")
public class CouponController {
    @Autowired
    private CouponService couponService;

    /**
 	* 测试 member 远程调用方法
 	* @return r
 	*/
   public R memberCoupons() {
        return R.ok().put("coupons","这里是coupons");
    }
  1. 在调用端新建feign包
package com.like.mall.member.feign;

import com.like.mall.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * @author like
 * @since 2020-10-25 14:54
 * 远程调用 Coupon 服务中的方法
 */
@FeignClient("mall-coupon") // 要调用远程服务
public interface CouponFeignService {

    /**
     * 远程调用coupon 中的方法,调用地址为coupon服务中的完整调用地址
     */
    @GetMapping("coupon/coupon/member/list")
    public R memberCoupons();
}
  1. 在调用端的启动类上添加注解
@SpringBootApplication
@EnableDiscoveryClient // 开启服务发现
@EnableFeignClients(basePackages = "com.like.mall.member.feign") // 开启远程调用功能
public class MallMemberApplication {

    public static void main(String[] args) {
        SpringApplication.run(MallMemberApplication.class, args);
    }

}

4.调用远程服务

@RestController
@RequestMapping("member/member")
public class MemberController {
    @Autowired
    private MemberService memberService;

    // 远程调用服务
    @Autowired
    private CouponFeignService couponFeignService;

    // 调运远程方法
    @RequestMapping("/coupon")
    public R testCoupon() {
        R r = couponFeignService.memberCoupons();
        return r;
    }
}

5.测试

image-20201025151935968

3、配置中心-nacos

添加依赖

<!--nacos配置中心-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
  1. 新建bootstrap.properties文件
# 改名字,对应nacos里的配置文件名
spring.application.name=mall-coupon
# nacos配置中心地址
spring.cloud.nacos.config.server-addr=47.112.150.204:8848

my.name=like
  1. 新建测试方法
@RestController
@RequestMapping("coupon/coupon")
public class CouponController {
    @Autowired
    private CouponService couponService;
    // 从配置文件中获取
    @Value("${my.name}")
    private String myName;

    @GetMapping("/test/config")
    public R testConfigToNacos() {
        return R
                .ok()
                .put("user", myName);
    }
}

3.测试

image-20201025154017545

4.在nacos中新建配置

mall-coupon.properties

image-20201025154326882

  1. 在controller中添加注解
@RestController
@RequestMapping("coupon/coupon")
@RefreshScope // 动态刷新config
public class CouponController {

6、修改

image-20201025155108154

image-20201025155122283

指定多个配置文件

  1. 新建一个命名空间
  2. 新建3个配置文件
  3. 注释掉application.yml中的配置信息

image-20201025160704847

image-20201025160654358

image-20201025160755402

  1. 查看coupro中是否加载了相关内容

4、网关- gateway

所有的请求先经过网关,比如常用的路转发,权限校验,限流控制等。==现在用gateway取代了zuul==

新建项目

1.依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>com.like</groupId>
        <artifactId>mall-common</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.SR3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

2.启动类

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
public class MallGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(MallGatewayApplication.class, args);
    }

}

3.配置类

server:
  port: 88
spring:
  # 微服务
  cloud:
    # nacos
    nacos:
      # 服务发现
      discovery:
        server-addr: 47.112.150.204:8848
    # 网关
    gateway:
      routes:
        - id: test_go_baidu
          uri: https://www.baidu.com
          predicates:  # 断言规则
            - Query=url,baidu         # 携带参数url且值为baidu

        - id: test_go_qq
          uri: https://www.qq.com
          predicates:  # 断言规则
            - Query=url,qq           # 携带参数url且值为qq
  application:
    name: mall-gateway
# 改名字,对应nacos里的配置文件名
spring.application.name=mall-gateway
# nacos配置中心地址
spring.cloud.nacos.config.server-addr=47.112.150.204:8848
spring.cloud.nacos.config.namespace=8a868529-4b9e-4a77-833f-151532298809

商品服务

a.分类维护

image-20201101102120635

1.查询分类,组装层tree

查询数据库中的所有分类信息,组装成如图下列效果

image-20201025171746015

思路:

  1. 查找一标签:parentId = 0
  2. 递归查找该标签的子标签:需要查找子标签的id = parentId

代码实现:

// 添加属性  CategoryEntity

/**
* 在表中不存在,专门用来存放子节点
*/
@TableField(exist = false)
private List<CategoryEntity> children;


// controller 方法
@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;

    /**
     * 查询分类中的所有信息,并组成的tree
     */
    @RequestMapping("/list/tree")
    public R list(@RequestParam Map<String, Object> params){
        List<CategoryEntity> entities = categoryService.listWithTree();

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




// service 方法
@Override
public List<CategoryEntity> listWithTree() {
    // 1.查出所有分类
    List<CategoryEntity> entities = baseMapper.selectList(null);
    // 2.组装成父子结构
    return entities
        .stream()
        .filter(c -> c.getParentCid() == 0)   // 查询到所有的一级分类,parent id = 0
        .map(c -> {
            c.setChildren(getChildren(c, entities)); // 查找当前标签的子标签
            return c;
        })
        .sorted((c1, c2) -> (c1.getSort() == null ? 0 : c1.getSort()) - (c2.getSort() == null ? 0 : c2.getSort())) // 排序
        .collect(Collectors.toList()); // 收集

}

/**
* 查找当前标签的子标签
*
* @param root 需要查找的标签
* @param all  所有标签
* @return 返回root的子标签集合
*/
private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all) {
    return all
        .stream()
        .filter(categoryEntity -> categoryEntity.getParentCid() == root.getCatId())  // 条件:当all中的标签的父id是root的id
        .map(c -> {
            c.setChildren(getChildren(c, all)); // 递归查找,当前标签的子标签,比如说当前是2级标签,就查找他的子标签->3级标签
            return c;
        })
        .sorted((c1, c2) -> (c1.getSort() == null ? 0 : c1.getSort()) - (c2.getSort() == null ? 0 : c2.getSort())) // 排序
        .collect(Collectors.toList()); // 收集
}

2.把服务注册到gateway上

修改gateway服务的配置文件

server:
  port: 88
spring:
  # 微服务
  cloud:
    # nacos
    nacos:
      # 服务发现
      discovery:
        server-addr: 47.112.150.204:8848
    # 网关
    gateway:
      routes:
        - id: product_route
          uri: lb://mall-product
          predicates:
            - Path=/api/product/**
          filters:
            - RewritePath=/api/(?<segment>.*),/$\{segment}

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

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

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

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


  application:
    name: mall-gateway

修改前端项目的请求地址

image-20201025202653344

这样直接访问88端口,通过特定域名访问特定服务

3.解决跨域

  1. gateway中添加配置类
  2. 注释掉renren-fast中关于跨域的配置
@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();

        // 1.配置跨域
        corsConfiguration.addAllowedHeader("*");                // 请求头
        corsConfiguration.addAllowedMethod("*");               // 请求方法
        corsConfiguration.addAllowedOrigin("*");              // 请求来源
        corsConfiguration.setAllowCredentials(true);         // 是否允许携带cookie

        // 允许所有请求
        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);
    }
}

4.前端页面搭建

人人开发平台配置页面:

image-20201026124519382

页面具体内容:
mall\mall-sys-vue\src\views\modules\product\category.vue
提交信息:
前端页面-商品系统-分页维护:页面搭建,添加添加和删除按钮逻辑(如果是一级标签就不显示删除只显示添加,如果还有子标签就不显示删除只显示添加)

5.后端删除和添加功能

删除

逻辑删除,使用mybatsi的逻辑删除功能

  1. product服务添加配置信息

  1. 给CategoryEntity实体类上的属性添加注解

    可以定义删除逻辑,和全局配置是反的

image-20201026132851003

  1. 方法
// controller
/**
 * 删除
 */
@PostMapping("/delete")
public R delete(@RequestBody Long[] catIds) {
    // 1.删除标签:检查是否被引用
    categoryService.removeMenu(Arrays.asList(catIds));
    return R.ok();
}


// service
@Override
public void removeMenu(List<Long> asList) {
    // TODO: 2020/10/26 1.删除标签:检查是否被引用
    // 现在使用逻辑删除
    baseMapper.deleteBatchIds(asList);
}

添加

使用默认的save方法

image-20201026214915071

编辑

使用默认的update方法

6.tree拖拽功能后端数据收集

目标节点的catLevel + deep小于3

需要考虑两种类型节点的catLevel,一种关系是:

如果是==同一个节点==下的==子节点的前后移动==,则不需要修改catLevel。

如果拖动到==与父节点平级的节点关系中==,则要将该拖动的节点的catLevel,设置为兄弟节点的Level,先考虑parentCid还是catLevel?

另外还有一种是前后拖动的情况,哪个范围最大?

肯定是拖动类型关系最大,

如果是前后拖动,则拖动后需要看待拖动节点的层级和设置拖动节点的parentId,

如果拖动节点和目标节点的层级相同,则任务是同级拖动,只需要修改节点的先后顺序,否则任务是跨级拖动,需要修改层级和重新设置parentId

同级拖动:

  1. 判断节点和目标节点的catLevel是否相同相同则认为是同级拖动,
    1. 如果此时==移动后目标节点的parentId和待移动节点的相同==,移动类型是前后移动,只需要==修改sort==
    2. 否则需要修改catLevel和parentId和sort

前后移动:

  1. 同级移动的标准是catLevel是否相同
  2. ==同级别==的前后移动分为parentId是否相同
    1. parentId相同只需要修改sort即可
    2. 不同则需要修改parentId和sort
  3. ==不同级别==的前后移动
    1. 需要修改parentId,catLevel,sort

inner类型的移动:

  1. 无论是同级inner,跨级inner,都需要修改parentId,catLevel,sort

哪种情况需要更新子节点

  1. 拖拽的节点是否含有子节点
    1. 如果有子节点则需要更新子节点的catLevel,
    2. 如果待移动节点和目标节点额catLevel不同,则是跨级移动。如果是移动到父节点中,则需要设置catLevel,parentId和sort。
      1. 如果是移动到父节点中,则需要设置catLevel,parentId,sort

b.品牌管理

image-20201101102127978

使用逆向工程生成的代码

image-20201028195029814

image-20201028202510341

c.文件存储-阿里云OSS

服务端签名后直传

  1. 用户发送上传policy请求到服务器
  2. 服务器返回上传policy和签名给用户
  3. 用户直接上传数据给oss

新建模块:

  1. 导入依赖
<dependencies>
        <!--		公共包-->
        <dependency>
            <groupId>com.like</groupId>
            <artifactId>mall-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <!--        mybatis-->
                    <groupId>com.baomidou</groupId>
                    <artifactId>mybatis-plus-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--		oss sdk-->
        <!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alicloud-oss -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
            <version>2.2.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
  1. 定义配置文件
spring:
  # 微服务
  cloud:
    # oss 上传文件信息
    alicloud:
      access-key: 
      secret-key: 
      oss:
        endpoint: oss-cn-shenzhen.aliyuncs.com
        bucket: mall-like
    nacos:
      server-addr: 
  application:
    name: mall-party

server:
  port: 13000
  1. 编写控制层方法
@RestController
public class OssController {

    @Autowired
    OSS ossClient;

    @Value("${spring.cloud.alicloud.oss.endpoint}")
    String endpoint;
    @Value("${spring.cloud.alicloud.oss.bucket}")
    String bucket;
    @Value("${spring.cloud.alicloud.access-key}")
    String accessId;
    @Value("${spring.cloud.alicloud.secret-key}")
    String accessKey;

    /**
     * 服务器签名
     *
     * @return 签名信息
     * @throws Exception 异常
     */
    @RequestMapping("/oss/policy")
    public Map<String, String> policy() throws Exception {
        //https://mall-like.oss-cn-shenzhen.aliyuncs.com/QQ%E6%88%AA%E5%9B%BE20201014092430.png
        String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
        // callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
        String callbackUrl = "http://88.88.88.88:8888";
        String nowDate = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        String dir = nowDate + "/"; // 用户上传文件时指定的前缀。
        Map<String, String> respMap = null;
        try {
            long expireTime = 30;
            long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
            Date expiration = new Date(expireEndTime);
            // PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
            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)); // 过期时间
            // respMap.put("expire", formatISO8601Date(expiration));


        } catch (Exception e) {
            // Assert.fail(e.getMessage());
            System.out.println(e.getMessage());
        } finally {
            ossClient.shutdown();
        }
        return respMap;
    }
}
  1. 启动类加服务发现注解
  2. 测试

image-20201029202222371

d.品牌管理添加图片上传功能

1.图片上传组件

image-20201030131053087

// singleUpload
<template> 
  <div>
    <el-upload
      action="http://mall-like.oss-cn-shenzhen.aliyuncs.com"
      :data="dataObj"
      list-type="picture"
      :multiple="false" :show-file-list="showFileList"
      :file-list="fileList"
      :before-upload="beforeUpload"
      :on-remove="handleRemove"
      :on-success="handleUploadSuccess"
      :on-preview="handlePreview">
      <el-button size="small" type="primary">点击上传</el-button>
      <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过10MB</div>
    </el-upload>
    <el-dialog :visible.sync="dialogVisible">
      <img width="100%" :src="fileList[0].url" alt="">
    </el-dialog>
  </div>
</template>
<script>
   import {policy} from './policy'
   import { getUUID } from '@/utils'

  export default {
    name: 'singleUpload',
    // 用于父组件传入信息
    props: {
      value: String
    },
    computed: {
      imageUrl() {
        return this.value;
      },
      imageName() {
        if (this.value != null && this.value !== '') {
          return this.value.substr(this.value.lastIndexOf("/") + 1);
        } else {
          return null;
        }
      },
      fileList() {
        return [{
          name: this.imageName,
          url: this.imageUrl
        }]
      },
      showFileList: {
        get: function () {
          return this.value !== null && this.value !== ''&& this.value!==undefined;
        },
        set: function (newValue) {
        }
      }
    },
    data() {
      return {
        dataObj: {
          policy: '',
          signature: '',
          key: '',
          ossaccessKeyId: '',
          dir: '',
          host: '',
          // callback:'',
        },
        dialogVisible: false
      };
    },
    methods: {
      emitInput(val) {
        this.$emit('input', val)
      },
      handleRemove(file, fileList) {
        this.emitInput('');
      },
      handlePreview(file) {
        this.dialogVisible = true;
      },
      beforeUpload(file) {
        let _self = this;
        return new Promise((resolve, reject) => {
          policy().then(response => {
            _self.dataObj.policy = response.data.policy;
            _self.dataObj.signature = response.data.signature;
            _self.dataObj.ossaccessKeyId = response.data.accessid;
            _self.dataObj.key = response.data.dir +getUUID()+'_${filename}';
            _self.dataObj.dir = response.data.dir;
            _self.dataObj.host = response.data.host;
            resolve(true)
          }).catch(err => {
            reject(false)
          })
        })
      },
      handleUploadSuccess(res, file) {
        console.log("上传成功...")
        this.showFileList = true;
        this.fileList.pop();
        this.fileList.push({name: file.name, url: this.dataObj.host + '/' + this.dataObj.key.replace("${filename}",file.name) });
        this.emitInput(this.fileList[0].url);
      }
    }
  }
</script>
<style>

</style>

2.发送请求脚本

import http from '@/utils/httpRequest.js'
export function policy() {
   return  new Promise((resolve,reject)=>{
     // 获取oss服务器签署信息
        http({
            url: http.adornUrl("/party/oss/policy"),
            method: "get",
            params: http.adornParams({})
        }).then(({ data }) => {
            resolve(data);
        })
    });
}

3.brand-add-or-update

添加上传组件

image-20201030131157582

4.brand

显示图片

image-20201030131222220

5.添加前端表单校验

image-20201030132037258

e.添加统一个异常处理(数据校验)

定义一个异常处理类==@RestControllerAdvice==

package com.like.mall.product.exception;
@Slf4j
@RestControllerAdvice(basePackages = "com.like.mall")
public class MallException {

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleValidationError(MethodArgumentNotValidException e) {
        log.error("数据校验出现问题:", e.getMessage(), e.getClass());
        Map<String, String> map = new HashMap<>();
        e
                .getBindingResult()
                .getFieldErrors()
                .forEach(fieldError -> {
                    map.put(fieldError.getField(), fieldError.getDefaultMessage());
                });
        return R.error(400,"数据校验出现问题").put("data",map);
    }

    @ExceptionHandler
    public R handleException(Throwable e) {
        return  R.error();
    }
}

在需要校验的实体类的==添加需要的注解==(使用jsr303规范)

image-20201101090045777

在controller的接口上添加注解==@Valid==

image-20201101090157295

f.分组校验(数据校验)

1.定义几个空实现的接口
	- // 数据校验 - 添加public interface Add {}
	-// 数据校验 - 修改public interface Update {}
2.在实体类上的校验注解中添加分组信息
    - 	@NotNull(message = "修改必须指定品牌id",groups = {Update.class})
		@Null(message = "添加不能指定品牌id",groups = {Add.class})
		private Long brandId;
	-   @NotBlank(message = "品牌名必须提交",groups = {Add.class,Update.class})
		private String name;
3.修改接口中的注解为@Valid->@Validated({Add.class})