• 谷粒商城笔记+踩坑(4)——商品服务-品牌管理


    导航:

    谷粒商城笔记+踩坑汇总篇

     Java笔记汇总:

    【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析-CSDN博客

    目录

    7、商品服务-品牌管理

    7.1、添加“品牌管理”到人人后台管理系统

    7.1.1、在人人后台管理系统中新增“品牌管理”菜单

    7.1.2、人人生成的前端vue文件复制到前端工程

    7.1.3、修改权限

    7.1.4、测试增删改查基础功能

    7.2、新增时显示状态开关(仅前端)

    7.3、文件上传功能,阿里云云存储

    7.3.1、分布式系统上传文件

    7.3.2、开通阿里云OSS对象存储服务,创建新的Bucket

    7.3.3、子账户创建和授权,获取Endpoint、AccessKey ID、AccessKey Secret

    7.3.4、测试普通上传方式(不建议)

    7.3.5、建立第三方服务模块,实现服务端签名后直传

    7.3.6、测试上传文件功能

    7.3.7、实现服务端签名后直传文件,新建OssController 

    7.3.8、配置网关路由

    7.3.9、启动网关、测试获取oss签名请求

    7.3.10、前端联调,实现文件上传功能

    7.3.11、在阿里云配置跨域规则

    7.3.12、启动测试 

    7.4、效果优化-显示图片(仅前端)

    7.5、前端表单校验

    7.6、JSR303数字校验

    7.6.1、基本校验实现,@Valid

    7.6.2、统一封装错误状态码

    7.6.3、商品模块统一封装异常处理类,品牌参数校验异常

    7.6.4、分组校验,增改校验分开,@Validated

    7.6.5、编写自定义校验,必须提交指定数值

    7.6.6、使用自定义校验,showStatus只能是0或1

    7.6.8、最终校验的实体类和controller代码

    7.6.9、postman测试校验


    7、商品服务-品牌管理

    7.1、添加“品牌管理”到人人后台管理系统

    7.1.1、在人人后台管理系统中新增“品牌管理”菜单

    菜单管理->新增菜单

    image-20210927135437474

    7.1.2、人人生成的前端vue文件复制到前端工程

    前端代码路径\product\main\resources\src\views\modules\product

    image-20210927135553792

    7.1.3、修改权限

    没有新增删除按钮: 修改权限,Ctrl+Shift+F查找isAuth后,

    在src/utils/index.js修改isAuth,注释并全部返回为true

    image-20210927135728379

    image-20210927135749448

    7.1.4、测试增删改查基础功能

    运行项目, 可以发现增删改查都是成功的. 

    注意:现在没有添加表单校验,新增时表单有非法输入会失败,例如状态设为汉字。 

    http://localhost:8001/#/product-brand

    7.2、新增时显示状态开关(仅前端)

    1、在列表中添加自定义列:中间加标签。可以通过 Scoped slot 可以获取到 row, column, $index 和 store(table 内部的状态管理)的数据

    2、修改开关状态,发送修改请求

    3、数据库中showStatus是01,开关默认值是true/false。 所以在开关中设置:active-value="1" 、:inactive-value="0"属性,与数据库同步

    代码+注释:

    1. <el-table-column
    2. prop="showStatus"
    3. header-align="center"
    4. align="center"
    5. label="显示状态"
    6. >
    7. <template slot-scope="scope">
    8. <el-switch
    9. v-model="scope.row.showStatus"
    10. active-color="#13ce66"
    11. inactive-color="#ff4949"
    12. :active-value="1"
    13. :inactive-value="0"
    14. @change="updateBrandStatus(scope.row)"
    15. >
    16. el-switch>
    17. template>
    18. el-table-column>
    19. <el-form-item label="显示状态" prop="showStatus">
    20. <el-switch
    21. v-model="dataForm.showStatus"
    22. active-color="#13ce66"
    23. inactive-color="#ff4949"
    24. :active-value="1"
    25. :inactive-value="0"
    26. >
    27. el-switch>
    28. el-form-item>
    1. //brand.vue中新增方法,用来修改状态
    2. updateBrandStatus(data) {
    3. let { brandId, showStatus } = data;
    4. this.$http({
    5. url: this.$http.adornUrl("/product/brand/update"),
    6. method: "post",
    7. data: this.$http.adornData({ brandId, showStatus }, false),
    8. }).then(({ data }) => {
    9. this.$message({
    10. message: "状态修改成功",
    11. type: "success",
    12. });
    13. });
    14. },

    7.3、文件上传功能,阿里云云存储

    7.3.1、分布式系统上传文件

    单体应用上传:上传文件到服务器,想获取文件时再向服务器发请求获取文件。

    分布式系统上传: 因为有多台服务器,为防止负载均衡导致获取文件时没找到对应的服务器,所以使用专门的存读文件服务器,或者云存储

    和传统的单体应用不同,这里我们选择将数据上传到分布式文件服务器上

    这里我们选择将图片放置到阿里云上,使用对象存储

    帮助文档:

    • https://github.com/alibaba/aliyun-spring-boot/blob/master/aliyun-spring-boot-samples/aliyun-oss-spring-boot-sample/README-zh.md
    • https://help.aliyun.com/document_detail/32009.html?spm=a2c4g.11186623.6.768.549d59aaWuZMGJ

    上传策略:服务端签名后直传

    image-20210927154406800

    7.3.2、开通阿里云OSS对象存储服务,创建新的Bucket

    尽管开通即可,oss是按量计费,开发阶段不会超过一块钱。 

    注册、登录、实名认证、开通oss对象存储:

    对象存储OSS_云存储服务_企业数据管理_存储-阿里云

    点击立即开通 

    点击“管理控制台” :

    oss基本概念:基本概念-Object-对象-存储-对象存储 OSS-阿里云

    存储空间(Bucket)

    存储空间是用户用于存储对象(Object)的容器,所有的对象都必须隶属于某个存储空间。存储空间具有各种配置属性,包括地域、访问权限、存储类型等。用户可以根据实际需求,创建不同类型的存储空间来存储不同的数据。

    建议一个项目创建一个存储空间。 

    对象(Object)

    对象是OSS存储数据的基本单元,也被称为OSS的文件。和传统的文件系统不同,对象没有文件目录层级结构的关系。对象由元信息(Object Meta),用户数据(Data)和文件名(Key)组成,并且由存储空间内部唯一的Key来标识。对象元信息是一组键值对,表示了对象的一些属性,比如最后修改时间、大小等信息,同时用户也可以在元信息中存储一些自定义的信息。

    创建bucket: 

    image-20210927154050990

    手动上传任意文件测试:

    点击文件详情、 复制url就能直接下载文件:

    7.3.3、子账户创建和授权,获取EndpointAccessKey IDAccessKey Secret

    创建子账户

    image-20210927154807136

    点击创建用户

    image-20210927154842396

    新建成功后得到AccessKey IDAccessKey Secret

    子账户分配权限,管理OSS对象存储服务

    给oss完全权限: 

    Endpoint。 (gulimall-xmh -> 概览 -> Endpoint(地域节点))

    image-20210927155403292

    7.3.4、测试普通上传方式(不建议)

    经过服务器不建议 

    安装-gt-lt-Java-对象存储 OSS-阿里云

    product模块导入依赖

    1. <dependency>
    2. <groupId>com.aliyun.ossgroupId>
    3. <artifactId>aliyun-sdk-ossartifactId>
    4. <version>3.15.0version>
    5. dependency>

    上传文件流,将下面代码中String都改成自己的信息:

    1. @Test
    2. public void testOss(){
    3. String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    4. // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    5. String accessKeyId = "yourAccessKeyId";
    6. String accessKeySecret = "yourAccessKeySecret";
    7. // 填写Bucket存储空间名称,例如gulimall-hello。
    8. String bucketName = "examplebucket";
    9. // 填写存储对象Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
    10. String objectName = "exampledir/exampleobject.txt";
    11. // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
    12. // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
    13. String filePath= "D:\\localpath\\examplefile.txt";
    14. // 创建OSSClient实例。
    15. OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    16. try {
    17. InputStream inputStream = new FileInputStream(filePath);
    18. // 创建PutObject请求。
    19. ossClient.putObject(bucketName, objectName, inputStream);
    20. } catch (OSSException oe) {
    21. System.out.println("Caught an OSSException, which means your request made it to OSS, "
    22. + "but was rejected with an error response for some reason.");
    23. System.out.println("Error Message:" + oe.getErrorMessage());
    24. System.out.println("Error Code:" + oe.getErrorCode());
    25. System.out.println("Request ID:" + oe.getRequestId());
    26. System.out.println("Host ID:" + oe.getHostId());
    27. } catch (ClientException ce) {
    28. System.out.println("Caught an ClientException, which means the client encountered "
    29. + "a serious internal problem while trying to communicate with OSS, "
    30. + "such as not being able to access the network.");
    31. System.out.println("Error Message:" + ce.getMessage());
    32. } finally {
    33. if (ossClient != null) {
    34. ossClient.shutdown();
    35. }
    36. }
    37. }

    运行,上传成功:

    复制文件url在浏览器也是可以打开的。 

    7.3.5、建立第三方服务模块,实现服务端签名后直传

    删除前面测试时引入的依赖和测试类,以后导入starter依赖。 

    官方演示示例 :

    aliyun-spring-boot/aliyun-spring-boot-samples/aliyun-oss-spring-boot-sample at master · alibaba/aliyun-spring-boot · GitHub

    • 新建springboot模块,gulimall-third-party

    • 第三方模块引入common、oss依赖和alibaba管理依赖
    1. <properties>
    2. <java.version>11java.version>
    3. <spring-cloud.version>2021.0.4spring-cloud.version>
    4. properties>
    5. <dependencies>
    6. <dependency>
    7. <groupId>org.springframework.bootgroupId>
    8. <artifactId>spring-boot-starter-webartifactId>
    9. dependency>
    10. <dependency>
    11. <groupId>org.springframework.cloudgroupId>
    12. <artifactId>spring-cloud-starter-openfeignartifactId>
    13. dependency>
    14. <dependency>
    15. <groupId>org.springframework.bootgroupId>
    16. <artifactId>spring-boot-starter-testartifactId>
    17. <scope>testscope>
    18. dependency>
    19. <dependency>
    20. <groupId>com.vince.gulimallgroupId>
    21. <artifactId>gulimall-commonartifactId>
    22. <version>0.0.1-SNAPSHOTversion>
    23. dependency>
    24. <dependency>
    25. <groupId>com.alibaba.cloudgroupId>
    26. <artifactId>spring-cloud-starter-alicloud-ossartifactId>
    27. <version>2.2.0.RELEASEversion>
    28. dependency>
    29. dependencies>
    30. <dependencyManagement>
    31. <dependencies>
    32. <dependency>
    33. <groupId>org.springframework.cloudgroupId>
    34. <artifactId>spring-cloud-dependenciesartifactId>
    35. <version>${spring-cloud.version}version>
    36. <type>pomtype>
    37. <scope>importscope>
    38. dependency>
    39. <dependency>
    40. <groupId>com.alibaba.cloudgroupId>
    41. <artifactId>spring-cloud-alibaba-dependenciesartifactId>
    42. <version>2021.0.1.0version>
    43. <type>pomtype>
    44. <scope>importscope>
    45. dependency>
    46. dependencies>
    47. dependencyManagement>

    • nacos新建命名空间third-party

    • 命名空间中新建配置文件oss.yml

     

    1. spring:
    2. cloud:
    3. alicloud:
    4. oss:
    5. endpoint: xxx
    6. bucket: xxx
    7. access-key: xxx
    8. secret-key: xxx
    • 项目新建bootstrap.yml
    1. spring:
    2. application:
    3. name: gulimall-third-party
    4. cloud:
    5. nacos:
    6. config:
    7. server-addr: 127.0.0.1:8848
    8. file-extension: yaml
    9. namespace: de0e12ff-8fc4-45a0-bdee-5b5618f4054f
    10. extension-configs:
    11. - data-id: oss.yml
    12. group: DEFAULT_GROUP
    13. refresh: true

    坑点:报错没有配置endpoint:

    SpringBoot 2.4.x的版本之后,对于bootstrap.properties/bootstrap.yaml配置文件(我们合起来成为Bootstrap配置文件)的支持,需要导入如下的依赖

    这里在common中导入:

    1. <dependency>
    2. <groupId>org.springframework.cloudgroupId>
    3. <artifactId>spring-cloud-starter-bootstrapartifactId>
    4. <version>3.1.4version>
    5. dependency>

    • 项目新建application.yml
    1. spring:
    2. cloud:
    3. nacos:
    4. discovery:
    5. server-addr: 127.0.0.1:8848
    6. application:
    7. name: gulimall-third-party
    8. server:
    9. port: 30000
    • 主启动类注解@EnableDiscoveryClient开启nacos注册
    1. @SpringBootApplication
    2. @EnableDiscoveryClient
    3. public class GulimallThirdPartyApplication {
    4. public static void main(String[] args) {
    5. SpringApplication.run(GulimallThirdPartyApplication.class, args);
    6. }
    7. }

    7.3.6、测试上传文件功能

    在第三方模块的pom里排除mybatisplus依赖:

    1. <dependency>
    2. <groupId>com.vince.gulimallgroupId>
    3. <artifactId>gulimall-commonartifactId>
    4. <version>0.0.1-SNAPSHOTversion>
    5. <exclusions>
    6. <exclusion>
    7. <groupId>com.baomidougroupId>
    8. <artifactId>mybatis-plus-boot-starterartifactId>
    9. exclusion>
    10. exclusions>
    11. dependency>

    编写测试类:

    注意修改bucket 

    1. @SpringBootTest
    2. class GulimallThirdPartyApplicationTest {
    3. @Autowired
    4. OSSClient ossClient;
    5. @Test
    6. public void testUpload() throws FileNotFoundException {
    7. //上传文件流。
    8. InputStream inputStream = new FileInputStream("E:\\SystemDefault\\桌面\\1.jpg");
    9. ossClient.putObject("改成自己bucketName例如gulimall-xmh", "hahaha1.jpg", inputStream);
    10. // 关闭OSSClient。
    11. ossClient.shutdown();
    12. System.out.println("上传成功.");
    13. }
    14. }

    7.3.7、实现服务端签名后直传文件,新建OssController 

    在第三方模块controller.OssController 

    代码来源:简单上传-上传-文件-OSS-对象存储 OSS-阿里云 

    这里主要是从yml里注入oss需要的关键属性,删去了跨域相关的代码,因为跨域我们之前在网关模块的配置类里已经统一配置了跨域规则。

    1. @RestController
    2. public class OssController {
    3. @Autowired
    4. OSS ossClient; //注意不是OssClient
    5. //从nacos配置的yml里注入阿里云云存储的关键属性
    6. @Value("${spring.cloud.alicloud.oss.endpoint}")
    7. String endpoint;
    8. @Value("${spring.cloud.alicloud.oss.bucket}")
    9. String bucket;
    10. @Value("${spring.cloud.alicloud.access-key}")
    11. String accessId;
    12. @Value("${spring.cloud.alicloud.secret-key}")
    13. String accessKey;
    14. @RequestMapping("/oss/policy")
    15. public R policy(){
    16. String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
    17. // 用户上传文件时指定的前缀
    18. String dir = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
    19. Map respMap=null;
    20. try {
    21. long expireTime = 30;
    22. long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
    23. Date expiration = new Date(expireEndTime);
    24. PolicyConditions policyConds = new PolicyConditions();
    25. policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
    26. policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
    27. String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
    28. byte[] binaryData = postPolicy.getBytes("utf-8");
    29. String encodedPolicy = BinaryUtil.toBase64String(binaryData);
    30. String postSignature = ossClient.calculatePostSignature(postPolicy);
    31. respMap= new LinkedHashMap();
    32. respMap.put("accessid", accessId);
    33. respMap.put("policy", encodedPolicy);
    34. respMap.put("signature", postSignature);
    35. respMap.put("dir", dir);
    36. respMap.put("host", host);
    37. respMap.put("expire", String.valueOf(expireEndTime / 1000));
    38. } catch (Exception e) {
    39. // Assert.fail(e.getMessage());
    40. System.out.println(e.getMessage());
    41. } finally {
    42. ossClient.shutdown();
    43. }
    44. return R.ok().put("data",respMap);
    45. }
    46. }

    文件真正访问地址:https://bucket名.endpoint名/文件名

    例如:

    启动访问获取签名:

    7.3.8、配置网关路由

    1. # oss等第三方模块路由
    2. - id: third_party_route
    3. uri: lb://gulimall-third-party
    4. predicates:
    5. - Path=/api/thirdparty/**
    6. filters:
    7. # http://localhost:88/api/thirdparty/oss/policy--->http://localhost:30000/oss/policy
    8. - RewritePath=/api/thirdparty/(?.*),/$\{segment}

    注意:位置要放到/api的上面

     

    7.3.9、启动网关、测试获取oss签名请求

    访问http://localhost:88/api/thirdparty/oss/policy测试

    7.3.10、前端联调,实现文件上传功能

    • 将资料里的upload文件夹cv在/renren-fast-vue/src/components中
    • 修改multiUpload.vue和singeUplad.vue组件中el-upload中的action属性,替换成自己的Bucket域名

    image-20210927155622860

    • 修改\src\views\modules\productbrand-add-or-update.vue,把单个文件上传组件应用到brand-add-or-update.vue
    1. //在