电商项目6:商品模块-品牌管理

商品模块-品牌管理

  • 1、逆向工程生成菜单
  • 2、优化逆向生成的前端工程
    • 2.1、优化显示状态
  • 3、开通阿里云oss对象存储
    • 3.1、创建bucket
    • 3.2、java后端
  • 4、创建第三方工程
  • 5、服务端签名后直传
  • 6、前端联调oss
  • 7、表单新增,编辑
    • 7.1、新增
    • 7.2、修改
  • 8、JSR303校验注解
    • 8.1、校验注解
    • 8.2、统一异常处理:
    • 8.3、分组校验
    • 8.4、自定义校验
  • 9、品牌管理相关优化点
    • 9.1、品牌分类关联

1、逆向工程生成菜单

电商项目6:商品模块-品牌管理_第1张图片
将逆向工程生成的两个vue文件放置到前端项目,可以参考电商项目2逆向工程生成
电商项目6:商品模块-品牌管理_第2张图片

电商项目6:商品模块-品牌管理_第3张图片
将其两个vue文件复制到product目录下

然后重启前端项目
电商项目6:商品模块-品牌管理_第4张图片
只有查询,没有新增和其他按钮(批量删除、新增按钮)是因为有权限判断方法,此方法让它暂时返回为true

电商项目6:商品模块-品牌管理_第5张图片
电商项目6:商品模块-品牌管理_第6张图片
这样按钮就都出来,可以进行操作了
电商项目6:商品模块-品牌管理_第7张图片

2、优化逆向生成的前端工程

2.1、优化显示状态

电商项目6:商品模块-品牌管理_第8张图片

elementUI table组件:自定义列模板
电商项目6:商品模块-品牌管理_第9张图片

   
        
      

elementUI 组件 Switch组件
电商项目6:商品模块-品牌管理_第10张图片


        
      

电商项目6:商品模块-品牌管理_第11张图片
brand-add-or-update.vue


         
          
    

电商项目6:商品模块-品牌管理_第12张图片
效果:
电商项目6:商品模块-品牌管理_第13张图片

我们需要显示状态发生改变时。数据库也改掉

elementUI switch组件 监听事件
电商项目6:商品模块-品牌管理_第14张图片
发送http请求
brand.vue
电商项目6:商品模块-品牌管理_第15张图片

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

电商项目6:商品模块-品牌管理_第16张图片
但是发现一个问题。数据库也改了状态。但是重新刷新页面又红了,1和0状态反了
电商项目6:商品模块-品牌管理_第17张图片

elementUI Switch开关 属性
电商项目6:商品模块-品牌管理_第18张图片
新增这两个属性
绑定数字的1,0
电商项目6:商品模块-品牌管理_第19张图片
去掉三元表达式
电商项目6:商品模块-品牌管理_第20张图片
功能做好了
电商项目6:商品模块-品牌管理_第21张图片
电商项目6:商品模块-品牌管理_第22张图片

3、开通阿里云oss对象存储

电商项目6:商品模块-品牌管理_第23张图片
能在管理控制台看到oss就是开通了

打开文档
电商项目6:商品模块-品牌管理_第24张图片
文档中心打开
电商项目6:商品模块-品牌管理_第25张图片
Bucket:存储空间

3.1、创建bucket

电商项目6:商品模块-品牌管理_第26张图片
电商项目6:商品模块-品牌管理_第27张图片
点击确定。创建完毕

测试是否可以上传访问:
1、点击上传文件
电商项目6:商品模块-品牌管理_第28张图片
2、扫描文件
电商项目6:商品模块-品牌管理_第29张图片
3、上传,点击详情
电商项目6:商品模块-品牌管理_第30张图片
4、复制文件url。直接访问
电商项目6:商品模块-品牌管理_第31张图片
5、
电商项目6:商品模块-品牌管理_第32张图片
电商项目6:商品模块-品牌管理_第33张图片

使用这种方式来使用阿里云对象存储
电商项目6:商品模块-品牌管理_第34张图片

3.2、java后端

电商项目6:商品模块-品牌管理_第35张图片
文档中心打开sdk文档
https://help.aliyun.com/document_detail/32009.html

1、引入maven依赖


            com.aliyun.oss
            aliyun-sdk-oss
            3.15.1
        

2、将上传文件示例代码copy过来
电商项目6:商品模块-品牌管理_第36张图片
GulimallProductApplicationTest

package com.ljs.gulimall.product;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ljs.gulimall.product.entity.BrandEntity;
import com.ljs.gulimall.product.service.BrandService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallProductApplicationTest {

    @Autowired
    private BrandService brandService;

    @Test
    public void uploadTest() throws FileNotFoundException {

            // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
            String accessKeyId = "yourAccessKeyId";
            String accessKeySecret = "yourAccessKeySecret";
            // 填写Bucket名称,例如examplebucket。
            String bucketName = "examplebucket";
            // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
            String objectName = "exampledir/exampleobject.txt";
            // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
            // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
            String filePath= "D:\\localpath\\examplefile.txt";

            // 创建OSSClient实例。
            OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

            try {
                InputStream inputStream = new FileInputStream(filePath);
                // 创建PutObjectRequest对象。
                PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
                // 设置该属性可以返回response。如果不设置,则返回的response为空。
                putObjectRequest.setProcess("true");
                // 创建PutObject请求。
                PutObjectResult result = ossClient.putObject(putObjectRequest);
                // 如果上传成功,则返回200。
                System.out.println(result.getResponse().getStatusCode());
            } 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());
            } finally {
                if (ossClient != null) {
                    ossClient.shutdown();
                }
            }
    }

根据实际需要修改里面的代码

3、keyId,secret申请
电商项目6:商品模块-品牌管理_第37张图片
电商项目6:商品模块-品牌管理_第38张图片
电商项目6:商品模块-品牌管理_第39张图片
电商项目6:商品模块-品牌管理_第40张图片
申请好以后,点击添加权限
电商项目6:商品模块-品牌管理_第41张图片
修改代码:

  // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
            String endpoint = "oss-cn-shenzhen.aliyuncs.com";
            // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
            String accessKeyId = "LTAI5tGLaEV7SAFzbsBiqzLn";
            String accessKeySecret = "xxxxxx";
            // 填写Bucket名称,例如examplebucket。
            String bucketName = "gulimall-ljshello";
            // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
            String objectName = "huawei.png";
            // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
            // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
            String filePath= "C:\\Users\\Administrator\\Desktop\\huawei.png";

            // 创建OSSClient实例。
            OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

            try {
                InputStream inputStream = new FileInputStream(filePath);
                // 创建PutObjectRequest对象。
                PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
                // 设置该属性可以返回response。如果不设置,则返回的response为空。
                putObjectRequest.setProcess("true");
                // 创建PutObject请求。
                PutObjectResult result = ossClient.putObject(putObjectRequest);
                // 如果上传成功,则返回200。
                System.out.println(result.getResponse().getStatusCode());
                System.out.println("上传成功。。。");
            } 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());
            } finally {
                if (ossClient != null) {
                    ossClient.shutdown();
                }
            }

电商项目6:商品模块-品牌管理_第42张图片
电商项目6:商品模块-品牌管理_第43张图片

4、springcloudalibaba oss对象存储

在common工程里引入

 
        
            com.alibaba.cloud
            spring-cloud-starter-alicloud-oss
        

在product工程里注释原生oss依赖


在product工程的application.yml里配置

alicloud:
      access-key: LTAI5tGLaEV7SAFzbsBiqzLn
      secret-key: xxxx
      oss:
        endpoint: oss-cn-shenzhen.aliyuncs.com

然后简化代码:
GulimallProductApplicationTest

package com.ljs.gulimall.product;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ljs.gulimall.product.entity.BrandEntity;
import com.ljs.gulimall.product.service.BrandService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallProductApplicationTest {

    @Autowired
    private BrandService brandService;

    @Autowired
    OSSClient ossClient;

    @Test
    public void uploadTest() throws FileNotFoundException {
            // 填写Bucket名称,例如examplebucket。
            String bucketName = "gulimall-ljshello";
            // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
            String objectName = "xiaomi.png";
            // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
            // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
            String filePath= "C:\\Users\\Administrator\\Desktop\\xiaomi.png";

            try {
                InputStream inputStream = new FileInputStream(filePath);
                // 创建PutObjectRequest对象。
                PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
                ossClient.putObject(putObjectRequest);
                System.out.println("上传成功。。。");
            } 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());
            } finally {
                if (ossClient != null) {
                    ossClient.shutdown();
                }
            }
    }

4、创建第三方工程

电商项目6:商品模块-品牌管理_第44张图片
电商项目6:商品模块-品牌管理_第45张图片

在这里插入图片描述

改成2.1.8.release
电商项目6:商品模块-品牌管理_第46张图片
将springcloudalibaba依赖和对象存储依赖放到third工程中

 
            
                com.alibaba.cloud
                spring-cloud-alibaba-dependencies
                2.1.0.RELEASE
                pom
                import
            
 
        
            com.alibaba.cloud
            spring-cloud-starter-alicloud-oss
        

third工程引入common的依赖

  
            com.ljs.gulimall
            gulimall-common
            0.0.1-SNAPSHOT
        

###将common服务的springcloudalibaba依赖和对象存储依赖删除

新建bootstrap.properties

spring.application.name=gulimall-third-party
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=e56f61fc-9f9f-43bd-9c27-16f894235366

spring.cloud.nacos.config.ext-config[0].data-id=oss.yaml
spring.cloud.nacos.config.ext-config[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.ext-config[0].refresh=true

###nacos配置

在这里插入图片描述
电商项目6:商品模块-品牌管理_第47张图片
对third-party新增配置
电商项目6:商品模块-品牌管理_第48张图片

###新建application.yml

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: gulimall-third-party
server:
  port: 30000

因为第三方服务引入了common,common引入了mybatisplus。所以排除

 
            com.ljs.gulimall
            gulimall-common
            0.0.1-SNAPSHOT
            
                
                    com.baomidou
                    mybatis-plus-boot-starter
                
            
        

GulimallThirdPartyApplication

package com.ljs.gulimall.thirdparty;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class GulimallThirdPartyApplication {

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

}

点击启动,已注册成功
电商项目6:商品模块-品牌管理_第49张图片

测试第三方服务文件上传功能
将product的测试类移过来即可

5、服务端签名后直传

参考阿里云oos官方文档
https://help.aliyun.com/document_detail/31926.html
电商项目6:商品模块-品牌管理_第50张图片

application.yml

spring:
  cloud:
    alicloud:
      access-key: LTAI5tGLaEV7SAFzbsBiqzLn
      secret-key: xxx
      oss:
        endpoint: oss-cn-shenzhen.aliyuncs.com
        bucket: gulimall-ljshello
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: gulimall-third-party
server:
  port: 30000

  

OssController

package com.ljs.gulimall.thirdparty.controller;

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
    private OSSClient ossClient;

    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endPoint;

    @Value("${spring.cloud.alicloud.oss.bucket}")
    private String bucket;

    @Value("${spring.cloud.alicloud.access-key}")
    private String accessId;

    @RequestMapping("/oss/policy")
    public Map policy() {
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        // 填写Host地址,格式为https://bucketname.endpoint。
        String host = "https://" + bucket + "." + endPoint;
        // 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
        // String callbackUrl = "https://192.168.0.0:8888";
        // 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。

        // 前缀的生成方法可以根据每天日期作为文件夹生成
        String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        String dir = format + "/";

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

            Map respMap = new LinkedHashMap();
            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));
            return respMap;
        } catch (Exception e) {
            // Assert.fail(e.getMessage());
            System.out.println(e.getMessage());
        }
        return null;
    }
}

启动第三方服务报错
电商项目6:商品模块-品牌管理_第51张图片
排查问题:
找到oss的三个自动装配类
电商项目6:商品模块-品牌管理_第52张图片
发现这里注入了一个OSS的bean,而这个OSS是接口
电商项目6:商品模块-品牌管理_第53张图片

电商项目6:商品模块-品牌管理_第54张图片

所以我们自动装配时也直接装配这个OSS接口

访问接口,已生成签名
电商项目6:商品模块-品牌管理_第55张图片

走网关需要添加映射
gateway添加第三方服务路由

- id: third_party_route
            #负载均衡到renren-fast服务
  uri: lb://gulimall-third-party
  predicates:
     - Path=/api/thirdparty/**
  #网关重写
  filters:
     - RewritePath=/api/thirdparty/(?.*),/$\{segment}

电商项目6:商品模块-品牌管理_第56张图片

重启网关。在访问
http://localhost:88/api/thirdparty/oss/policy

电商项目6:商品模块-品牌管理_第57张图片

6、前端联调oss

前端上传主要使用upload组件
电商项目6:商品模块-品牌管理_第58张图片
将分布式基础的三个文件copy过来
电商项目6:商品模块-品牌管理_第59张图片
多文件上传
multiUpload.vue







单文件上传
singleUpload.vue







policy.js

import http from '@/utils/httpRequest.js'
export function policy() {
   return  new Promise((resolve,reject)=>{
        http({
            url: http.adornUrl("/thirdparty/oss/policy"),
            method: "get",
            params: http.adornParams({})
        }).then(({ data }) => {
            resolve(data);
        })
    });
}

将整个upload文件夹放于前端components文件夹下
电商项目6:商品模块-品牌管理_第60张图片

修改代码
1、替换文件上传的地址
电商项目6:商品模块-品牌管理_第61张图片
电商项目6:商品模块-品牌管理_第62张图片
2、修改
brand-add-or-update.vue

  // 引入文件上传组件
  import SingleUpload from "@/components/upload/singleUpload";

电商项目6:商品模块-品牌管理_第63张图片


      
      
          
      
    

电商项目6:商品模块-品牌管理_第64张图片
电商项目6:商品模块-品牌管理_第65张图片
重启项目后,调通验证签名接口
电商项目6:商品模块-品牌管理_第66张图片

由于前端是data中取数据
电商项目6:商品模块-品牌管理_第67张图片
修改后台接口

package com.ljs.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 com.ljs.gulimall.common.utils.R;
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
    private OSS ossClient;

    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endPoint;

    @Value("${spring.cloud.alicloud.oss.bucket}")
    private String bucket;

    @Value("${spring.cloud.alicloud.access-key}")
    private String accessId;

    @RequestMapping("/oss/policy")
    public R policy() {
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        // 填写Host地址,格式为https://bucketname.endpoint。
        String host = "https://" + bucket + "." + endPoint;
        // 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
        // String callbackUrl = "https://192.168.0.0:8888";
        // 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。

        // 前缀的生成方法可以根据每天日期作为文件夹生成
        String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        String dir = format + "/";

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

            Map respMap = new LinkedHashMap();
            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));

            return R.ok().put("data",respMap);
        } catch (Exception e) {
            // Assert.fail(e.getMessage());
            System.out.println(e.getMessage());
        }
        return null;
    }
}

在这里插入图片描述

再次访问。
出现跨域问题
电商项目6:商品模块-品牌管理_第68张图片

这时候需要设置阿里云对象存储
电商项目6:商品模块-品牌管理_第69张图片
电商项目6:商品模块-品牌管理_第70张图片
电商项目6:商品模块-品牌管理_第71张图片
电商项目6:商品模块-品牌管理_第72张图片
上传成功。
查看oss对象存储

发现加了一撇的文件夹。我们不需要
电商项目6:商品模块-品牌管理_第73张图片

修改前端,把这/去掉,因为后端dir带了/
电商项目6:商品模块-品牌管理_第74张图片

测试已上传成功
电商项目6:商品模块-品牌管理_第75张图片

7、表单新增,编辑

继续完成新增,编辑功能

7.1、新增

brand-add-or-update.vue
电商项目6:商品模块-品牌管理_第76张图片

配合前端调试神器vue-devtools
电商项目6:商品模块-品牌管理_第77张图片
点击保存

电商项目6:商品模块-品牌管理_第78张图片

图片地址不应该以这种文本方式展示。应该展示图片
电商项目6:商品模块-品牌管理_第79张图片
复制这块的template代码

brand.vue
电商项目6:商品模块-品牌管理_第80张图片

template中间代码使用图片组件
电商项目6:商品模块-品牌管理_第81张图片

电商项目6:商品模块-品牌管理_第82张图片

 
      
      

重启发现前端报错
电商项目6:商品模块-品牌管理_第83张图片导入elementUI时图片组件可能忘记引入了
电商项目6:商品模块-品牌管理_第84张图片

回到官网将完整的导入
电商项目6:商品模块-品牌管理_第85张图片

将这些复制进index.js

import {
  Pagination,
  Dialog,
  Autocomplete,
  Dropdown,
  DropdownMenu,
  DropdownItem,
  Menu,
  Submenu,
  MenuItem,
  MenuItemGroup,
  Input,
  InputNumber,
  Radio,
  RadioGroup,
  RadioButton,
  Checkbox,
  CheckboxButton,
  CheckboxGroup,
  Switch,
  Select,
  Option,
  OptionGroup,
  Button,
  ButtonGroup,
  Table,
  TableColumn,
  DatePicker,
  TimeSelect,
  TimePicker,
  Popover,
  Tooltip,
  Breadcrumb,
  BreadcrumbItem,
  Form,
  FormItem,
  Tabs,
  TabPane,
  Tag,
  Tree,
  Alert,
  Slider,
  Icon,
  Row,
  Col,
  Upload,
  Progress,
  Spinner,
  Badge,
  Card,
  Rate,
  Steps,
  Step,
  Carousel,
  CarouselItem,
  Collapse,
  CollapseItem,
  Cascader,
  ColorPicker,
  Transfer,
  Container,
  Header,
  Aside,
  Main,
  Footer,
  Timeline,
  TimelineItem,
  Link,
  Divider,
  Image,
  Calendar,
  Backtop,
  PageHeader,
  CascaderPanel,
  Loading,
  MessageBox,
  Message,
  Notification
} from 'element-ui';

Vue.use(Pagination);
Vue.use(Dialog);
Vue.use(Autocomplete);
Vue.use(Dropdown);
Vue.use(DropdownMenu);
Vue.use(DropdownItem);
Vue.use(Menu);
Vue.use(Submenu);
Vue.use(MenuItem);
Vue.use(MenuItemGroup);
Vue.use(Input);
Vue.use(InputNumber);
Vue.use(Radio);
Vue.use(RadioGroup);
Vue.use(RadioButton);
Vue.use(Checkbox);
Vue.use(CheckboxButton);
Vue.use(CheckboxGroup);
Vue.use(Switch);
Vue.use(Select);
Vue.use(Option);
Vue.use(OptionGroup);
Vue.use(Button);
Vue.use(ButtonGroup);
Vue.use(Table);
Vue.use(TableColumn);
Vue.use(DatePicker);
Vue.use(TimeSelect);
Vue.use(TimePicker);
Vue.use(Popover);
Vue.use(Tooltip);
Vue.use(Breadcrumb);
Vue.use(BreadcrumbItem);
Vue.use(Form);
Vue.use(FormItem);
Vue.use(Tabs);
Vue.use(TabPane);
Vue.use(Tag);
Vue.use(Tree);
Vue.use(Alert);
Vue.use(Slider);
Vue.use(Icon);
Vue.use(Row);
Vue.use(Col);
Vue.use(Upload);
Vue.use(Progress);
Vue.use(Spinner);
Vue.use(Badge);
Vue.use(Card);
Vue.use(Rate);
Vue.use(Steps);
Vue.use(Step);
Vue.use(Carousel);
Vue.use(CarouselItem);
Vue.use(Collapse);
Vue.use(CollapseItem);
Vue.use(Cascader);
Vue.use(ColorPicker);
Vue.use(Transfer);
Vue.use(Container);
Vue.use(Header);
Vue.use(Aside);
Vue.use(Main);
Vue.use(Footer);
Vue.use(Timeline);
Vue.use(TimelineItem);
Vue.use(Link);
Vue.use(Divider);
Vue.use(Image);
Vue.use(Calendar);
Vue.use(Backtop);
Vue.use(PageHeader);
Vue.use(CascaderPanel);

然后重启爆另外的错
电商项目6:商品模块-品牌管理_第86张图片
去掉这里的:
电商项目6:商品模块-品牌管理_第87张图片

但还是加载失败

电商项目6:商品模块-品牌管理_第88张图片

查看数据库保存了没
保存了
电商项目6:商品模块-品牌管理_第89张图片

将地址拿出来也可以直接下载
电商项目6:商品模块-品牌管理_第90张图片
发现这里写错了。应该是logo
电商项目6:商品模块-品牌管理_第91张图片

电商项目6:商品模块-品牌管理_第92张图片
这里可以看到,有展示。但是展示不对。可以使用原生的img标签
电商项目6:商品模块-品牌管理_第93张图片

 

这样图片就展示了
电商项目6:商品模块-品牌管理_第94张图片

7.2、修改

点击修改
会回显正确数据
电商项目6:商品模块-品牌管理_第95张图片

可以换掉图片
电商项目6:商品模块-品牌管理_第96张图片

##前端表单验证
电商项目6:商品模块-品牌管理_第97张图片

电商项目6:商品模块-品牌管理_第98张图片
只是限制了不能为空的情况

这样就不合理了
电商项目6:商品模块-品牌管理_第99张图片

可以使用自定义校验
电商项目6:商品模块-品牌管理_第100张图片
对于数字的可以做绑定处理
电商项目6:商品模块-品牌管理_第101张图片

 
   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)) {
                callback(new Error("排序必须是数字"));
              } else if (value < 0){
                callback(new Error("排序必须大于等于0"));
              }else {
                callback();
              }
            }, trigger: 'blur' },
        ],

电商项目6:商品模块-品牌管理_第102张图片

8、JSR303校验注解

8.1、校验注解

1、给Bean添加校验注解:javax.validation.constaints,并自定义自己的message
2、开启校验功能valid。
效果:校验错误后,会有默认的提示
3、对校验的Bean后面可以紧跟BindingResult 对象。可以从此对象取出发生的错误信息和错误字段

BrandEntity

package com.ljs.gulimall.product.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.io.Serializable;

/**
 * 品牌
 * 
 * @author liangjiansong
 * @email [email protected]
 * @date 2022-10-22 21:21:08
 */
@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地址必须合法")
	private String logo;
	/**
	 * 介绍
	 */
	private String descript;
	/**
	 * 显示状态[0-不显示;1-显示]
	 */
	private Integer showStatus;
	/**
	 * 检索首字母
	 */
	@NotEmpty
	@Pattern(regexp ="^[a-zA-Z]$",message = "检索首字母必须是一个字母")
	private String firstLetter;

	/**
	 * 排序
	 */
	@NotNull
	@Min(value = 0,message = "排序必须大于等于0")
	private Integer sort;

}

BrandController

  /**
     * 保存
     */
    @RequestMapping("/save")
    // @RequiresPermissions("product:brand:save")
    public R save(@Valid @RequestBody BrandEntity brand , BindingResult result){
        if (result.hasErrors()){
            Map map = new HashMap<>();
            List fieldErrors = result.getFieldErrors();
            fieldErrors.forEach(item -> {
                // 获取封装的错误信息
                String message = item.getDefaultMessage();
                // 获取某个字段出现的错误
                String field = item.getField();
                map.put(field,message);
            });
            // error
            return R.error(400,"提交的数据不合法").put("data",map);
        }
		brandService.save(brand);

        return R.ok();
    }

8.2、统一异常处理:

GulimallExcepitonControllerAdvice

package com.ljs.gulimall.product.exception;

import com.ljs.gulimall.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
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.List;
import java.util.Map;

@Slf4j
@RestControllerAdvice(basePackages = "com.ljs.gulimall.product.controller")
public class GulimallExcepitonControllerAdvice {

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleValidException(MethodArgumentNotValidException e){
        BindingResult result = e.getBindingResult();
        Map map = new HashMap<>();
        List fieldErrors = result.getFieldErrors();
        fieldErrors.forEach(item -> {
            // 获取封装的错误信息
            String message = item.getDefaultMessage();
            // 获取某个字段出现的错误
            String field = item.getField();
            map.put(field,message);
        });
        // error
        return R.error(400,"提交的数据不合法").put("data",map);
    }

    @ExceptionHandler(value = Throwable.class)
    public R handleException(Throwable throwable){
        return R.error();
    }
}

这样就不用每个controller的方法里编写BindingResult处理了
BrandController

 /**
     * 保存
     */
    @RequestMapping("/save")
    // @RequiresPermissions("product:brand:save")
    public R save(@Valid @RequestBody BrandEntity brand){
     /*   if (result.hasErrors()){
            Map map = new HashMap<>();
            List fieldErrors = result.getFieldErrors();
            fieldErrors.forEach(item -> {
                // 获取封装的错误信息
                String message = item.getDefaultMessage();
                // 获取某个字段出现的错误
                String field = item.getField();
                map.put(field,message);
            });
            // error
            return R.error(400,"提交的数据不合法").put("data",map);
        }*/
		brandService.save(brand);

        return R.ok();
    }

###状态码的维护不应写死。应该维护。通常枚举

在common工程里新增一个枚举类
BizCodeEnum

package com.ljs.gulimall.common.Enum;

public enum BizCodeEnum {
    UNKNOW_EXCEPTION(10000,"系统未知异常"),

    VALID_EXCEPTION(10001,"参数格式校验失败");

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

修改全局异常类

package com.ljs.gulimall.product.exception;

import com.ljs.gulimall.common.Enum.BizCodeEnum;
import com.ljs.gulimall.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
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.List;
import java.util.Map;

@Slf4j
@RestControllerAdvice(basePackages = "com.ljs.gulimall.product.controller")
public class GulimallExcepitonControllerAdvice {

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleValidException(MethodArgumentNotValidException e){
        BindingResult result = e.getBindingResult();
        Map map = new HashMap<>();
        List fieldErrors = result.getFieldErrors();
        fieldErrors.forEach(item -> {
            // 获取封装的错误信息
            String message = item.getDefaultMessage();
            // 获取某个字段出现的错误
            String field = item.getField();
            map.put(field,message);
        });
        // error
        return R.error(BizCodeEnum.VALID_EXCEPTION.getCode(),BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data",map);
    }

    @ExceptionHandler(value = Throwable.class)
    public R handleException(Throwable throwable){
        return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(), BizCodeEnum.UNKNOW_EXCEPTION.getMsg());
    }
}

电商项目6:商品模块-品牌管理_第103张图片

8.3、分组校验

1、在实体类加groups属性

/**
	 * 品牌id
	 */
	@TableId
	@NotNull(message = "编辑时id不能为空",groups = {UpdateGroup.class})
	@Null(message = "新增时id为空",groups = {AddGroup.class})
	private Long brandId;
	/**
	 * 品牌名
	 */
	@NotBlank(message = "品牌名不能为空",groups = {UpdateGroup.class,AddGroup.class})
	private String name;

2、在common工程中新增AddGroup和UpdateGroup接口(空接口)

3、在controller中将@Valid注解换成@Validated注解

 /**
     * 保存
     */
    @RequestMapping("/save")
    // @RequiresPermissions("product:brand:save")
    public R save(@Validated(value = {AddGroup.class}) @RequestBody BrandEntity brand){
		brandService.save(brand);
        return R.ok();
    }

但是我们发现,没有加分组注解的其他字段。没有校验
电商项目6:商品模块-品牌管理_第104张图片

当controller层如果@Validated指定了分组校验。则实体类指定了这个类的校验生效。如果如果@Validated没有指定分组校验。则实体类没有指定分组校验的注解生效。
电商项目6:商品模块-品牌管理_第105张图片

8.4、自定义校验

1、编写自定义校验注解
2、编写自定义校验器
3、关联自定义校验器和自定义校验注解

ListValue

package com.ljs.gulimall.common.valid;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
    String message() default "{javax.validation.constraints.NotBlank.message}";

    Class[] groups() default { };

    Class[] payload() default { };

    int[] vals() default { };
}

BrandEntity

	/**
	 * 显示状态[0-不显示;1-显示]
	 */
	@ListValue(vals = {0,1}, groups = {AddGroup.class})
	private Integer showStatus;

ValidationMessages.properties

javax.validation.constraints.NotBlank.message=提交的数据不在范围内

ListValueConstraintValidator

package com.ljs.gulimall.common.valid;

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

public class ListValueConstraintValidator implements ConstraintValidator {
    Set set = new HashSet<>();

    // 初始化方法
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }
    }

    // 判断是否校验成功
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return set.contains(value);
    }
}

ListValue

// 可以指定多个不同的校验器,适配不同类型的校验
@Constraint(validatedBy = { ListValueConstraintValidator.class})

电商项目6:商品模块-品牌管理_第106张图片

由于修改状态时。进了这个校验。所以单纯修改品牌不应该和修改状态为同一个接口
电商项目6:商品模块-品牌管理_第107张图片
前后端都需要改

后端:
BrandController

 /**
     * 修改品牌状态
     */
    @RequestMapping("/updateStatus")
    // @RequiresPermissions("product:brand:update")
    public R updateStatus(@Validated(value = {UpdateStatusGroup.class}) @RequestBody BrandEntity brand){
        brandService.updateById(brand);
        return R.ok();
    }

BrandEntity

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

前端:
brand.vue

电商项目6:商品模块-品牌管理_第108张图片

9、品牌管理相关优化点

电商项目6:商品模块-品牌管理_第109张图片
分页插件未引入。后端应引入分页插件

MybatisConfig

package com.ljs.gulimall.product.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@MapperScan("com.ljs.gulimall.product.dao")
public class MybatisConfig {
    // 引入分页插件
    @Bean
    public PaginationInterceptor paginationInterceptor(){
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置请求页面大于最大页后操作,true到首页。false继续请求
        paginationInterceptor.setOverflow(true);
        // 设置最大单页限制数量 -1为不限制 500为具体数字
        paginationInterceptor.setLimit(1000);
        return paginationInterceptor;
    }
}



电商项目6:商品模块-品牌管理_第110张图片

品牌模糊查询有问题
BrandServiceImpl

queryPage

 @Override
    public PageUtils queryPage(Map params) {
        // 条件查询
        QueryWrapper brandEntityQueryWrapper = new QueryWrapper<>();
        if (Objects.nonNull(params.get("key"))){
            String key = (String) params.get("key");
            brandEntityQueryWrapper.eq("brand_id",key).or().like("name",key);
        }
        IPage page = this.page(
                new Query().getPage(params),
                brandEntityQueryWrapper
        );

        return new PageUtils(page);
    }

9.1、品牌分类关联

将一部分前端代码拷贝入代码中
将前端代码的common和product放置于前端项目中

电商项目6:商品模块-品牌管理_第111张图片
电商项目6:商品模块-品牌管理_第112张图片
主要是做这一块功能

多对多关系。设计关联关系表
电商项目6:商品模块-品牌管理_第113张图片
1、获取当前品牌关联关系列表(接口)

/**
     * 列表
     */
    @GetMapping("/catelog/list")
    public R catelogList(@RequestParam("brandId") Long brandId){
        List list = categoryBrandRelationService.
                list(new QueryWrapper().eq("brand_id",brandId));
        return R.ok().put("data", list);
    }

2、新增品牌与分类的关联关系(接口)

 /**
     * 保存
     */
    @RequestMapping("/save")
    // @RequiresPermissions("product:categorybrandrelation:save")
    public R save(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){
		categoryBrandRelationService.saveDetail(categoryBrandRelation);
        return R.ok();
    }
void saveDetail(CategoryBrandRelationEntity categoryBrandRelation);
 @Override
    public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
        BrandEntity brandEntity = brandDao.selectById(categoryBrandRelation.getBrandId());
        CategoryEntity categoryEntity = categoryDao.selectById(categoryBrandRelation.getCatelogId());
        if (brandEntity != null){
            categoryBrandRelation.setBrandName(brandEntity.getName());
        }
        if (categoryEntity != null){
            categoryBrandRelation.setCatelogName(categoryEntity.getName());
        }
        this.save(categoryBrandRelation);
    }

有相同的前端问题
在这里插入图片描述

样式有问题。选中后无选中

在style中指定样式


电商项目6:商品模块-品牌管理_第114张图片

测试了下目前关联关系展示、新增、删除无问题

###当品牌信息更新后,关联表品牌名无更新。就不好。需要做判断
在处理过程中还需加入事务

修改品牌及分类
BrandController

 /**
     * 修改
     */
    @RequestMapping("/update")
    // @RequiresPermissions("product:brand:update")
    public R update(@Validated(value = {UpdateGroup.class}) @RequestBody BrandEntity brand){
		brandService.updateDetail(brand);

        return R.ok();
    }

CategoryController

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

        return R.ok();
    }

BrandService

void updateDetail(BrandEntity brand);

CategoryService

void updateDetail(CategoryEntity category);

BrandServiceImpl

 @Override
    public void updateDetail(BrandEntity brand) {
        this.updateById(brand);
        // 当更新了品牌名。其他关联关系表品牌名也需要更新
        if(Strings.isNotBlank(brand.getName())){
            categoryBrandRelationService.updateBrand(brand.getBrandId(),brand.getName());
        }
    }

CategoryServiceImpl

 @Override
    public void updateDetail(CategoryEntity category) {
        this.updateById(category);
        // 当有级联更新分类名称时,同时更新关联表名称
        if (Strings.isNotBlank(category.getName())){
            categoryBrandRelationService.updateCateGory(category.getCatId(),category.getName());
        }
    }

CategoryBrandRelationService

 void updateBrand(Long brandId, String name);
 void updateCateGory(Long catId, String name);

CategoryBrandRelationServiceImpl

   @Transactional
    @Override
    public void updateBrand(Long brandId, String name) {
        CategoryBrandRelationEntity relationEntity = new CategoryBrandRelationEntity();
        relationEntity.setBrandId(brandId);
        relationEntity.setBrandName(name);
        this.update(relationEntity,new QueryWrapper().eq("brand_id",brandId));
    }

@Transactional
    @Override
    public void updateCateGory(Long catId, String name) {
        // 自定义sql方式
        this.baseMapper.updateCateGory(catId,name);
    }

CategoryBrandRelationDao

 /**
     * updateCateGory
     *
     * @param catId catId
     * @param name name
     */
    void updateCateGory(@Param("catId") Long catId, @Param("name") String name);

CategoryBrandRelationDao.xml

  
        update pms_category_brand_relation
        set catelog_name = #{name}
        where catelog_id = #{catId}
    

你可能感兴趣的:(vue.js,前端,javascript)