【谷粒商城基础篇】商品服务开发:品牌管理

news2024/11/18 10:39:26

在这里插入图片描述

谷粒商城笔记合集

分布式基础篇分布式高级篇高可用集群篇
===简介&环境搭建===
项目简介与分布式概念(第一、二章)
基础环境搭建(第三章)
===整合SpringCloud===
整合SpringCloud、SpringCloud alibaba(第四、五章)
===前端知识===
前端开发基础知识(第六章)
===商品服务开发===
商品服务开发:基础概念、三级分类(第七、八章)
商品服务开发:品牌管理(第九章)
商品服务开发:属性分组、平台属性(第十、十一章)
商品服务:商品维护(第十二、十三章)
===仓储服务开发===
仓储服务:仓库维护(第十四章)
基础篇总结(第十五章)

九、商品服务&品牌管理⚠️

9.1 前端开发:引入组件

注意:组件中使用到的所有API都是逆向工程生成好的

  1. 管理系统前端项目 系统管理-菜单管理 中商品系统目录下新增:品牌管理菜单

    在这里插入图片描述

  2. 逆向工程renren-generator 中生成的前端组件拷贝到 管理系统前端项目renren-fast-vue 中

    在这里插入图片描述

  3. 保存修改,查看组件效果

    在这里插入图片描述

9.2 前后端联调:显示状态按钮优化

在这里插入图片描述

API

/**
 * 修改状态
 */
@RequestMapping("/update/status")
public R updateStatus(@RequestBody BrandEntity brand){
    brandService.updateById(brand);

    return R.ok();
}

前端

  1. src/utils/index.js:注释权限验证

    /**
     * 是否有权限
     * @param {*} key
     */
    export function isAuth (key) {
      // return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) !== -1 || false
      return true
    }
    
  2. views/modules/product/brand.vue:优化显示状态按钮

    <template>
      <div class="mod-config">
        <el-table
          :data="dataList"
          border
          v-loading="dataListLoading"
          @selection-change="selectionChangeHandle"
          style="width: 100%;">
          <el-table-column
            prop="showStatus"
            header-align="center"
            align="center"
            label="显示状态">
            <template slot-scope="scope">
              <el-switch v-model="scope.row.showStatus" active-color="#13ce66" inactive-color="#ff4949" :active-value="1" :inactive-value="0" @change="updateBrandStatus(scope.row)"></el-switch>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </template>
    
  3. views/modules/product/brand-add-or-update.vue:新增对话框优化显示状态按钮

    <template>
      <el-dialog
        :title="!dataForm.brandId ? '新增' : '修改'"
        :close-on-click-modal="false"
        :visible.sync="visible">
        <el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="140px">
        <el-form-item label="显示状态" prop="showStatus">
          <!-- :active-value="1" :inactive-value="0" @change="updateBrandStatus(scope.row) -->
          <el-switch v-model="dataForm.showStatus" active-color="#13ce66" inactive-color="#ff4949" :active-value="1" :inactive-value="0"></el-switch>
        </el-form-item>
        </el-form>
      </el-dialog>
    </template>
    
  4. 修改请求URL:/product/brand/update/status

9.3 文件上传分析💡

9.3.1 文件存储方式

在这里插入图片描述

9.3.2 云存储上传方式

1)普通上方式

在这里插入图片描述

2)服务端签名后上传

在这里插入图片描述

9.4 后端开发:第三方服务-OSS⚠️

9.4.1 创建阿里云OSS相关服务

  1. 创建OSS存储桶

    在这里插入图片描述

  2. 创建RAM用户

    在这里插入图片描述

  3. 给创建的RAM用户添加OSS管理权限

    在这里插入图片描述

9.4.2 创建第三方服务模块💡

  1. 创建 第三方服务模块:bilimall-third-party

    在这里插入图片描述

    在这里插入图片描述

  2. 第三方服务 中修改 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>
        <!-- 1、修改 SpringBoot 版本 -->
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.8.RELEASE</version>
            <relativePath/>
        </parent>
        <!-- 2、修改描述信息 -->
        <description>第三方服务</description>
        <properties>
            <java.version>17</java.version>
            <!-- 3、修改 SpringCloud 版本 -->
            <spring-cloud.version>Greenwich.SR3</spring-cloud.version>
        </properties>
        <dependencies>
            <!-- 4、引入 Spring Cloud Alibaba Oss -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
            </dependency>
            <!-- 5、引入 公共依赖 -->
            <dependency>
                <groupId>cn.lzwei.bilimall</groupId>
                <artifactId>bilimall-common</artifactId>
                <version>0.0.1-SNAPSHOT</version>
                <exclusions>
                    <!-- 6、剔除 MybatisPlus 依赖 -->
                    <exclusion>
                        <groupId>com.baomidou</groupId>
                        <artifactId>mybatis-plus-boot-starter</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
        <dependencyManagement>
            <dependencies>
                <!-- 7、引入 Spring cloud alibaba -->
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                    <version>2.1.0.RELEASE</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    </project>
    
  3. 第三方服务 中创建配置文件配置nacos注册中心、oss、服务端口:application.yaml

    spring:
      cloud:
        nacos:
          discovery:
            server-addr: 114.132.162.129:8848
        alicloud:
          access-key: LTAI5t9sqxLYEuj8ShZ4km9b
          secret-key: UN7Bl1u9wj4mOK4qzni7YeKc3xgvOr
          oss:
            endpoint: oss-cn-guangzhou.aliyuncs.com
      application:
        name: bilimall-third-party
    server:
      port: 30000
    
  4. nacos服务 中为 第三方服务 创建命名空间:third-party

  5. nacos服务third-party命名空间 中添加 OSS配置文件:oss.yaml

    spring:
      cloud:
        alicloud:
          access-key: LTAI5t9sqxLYEuj8ShZ4km9b
          secret-key: UN7Bl1u9wj4mOK4qzni7YeKc3xgvOr
          oss:
            endpoint: oss-cn-guangzhou.aliyuncs.com
    
  6. 第三方服务 创建配置文件配置nacos配置中心:bootstrap.properties

    spring.application.name=bilimall-third-party
    spring.cloud.nacos.config.server-addr=114.132.162.129:8848
    spring.cloud.nacos.config.namespace=1185ba57-96ae-4b78-ba83-cc4e5f1ae05a
    #命名空间中,默认配置文件的分组:服务名.properties
    spring.cloud.nacos.config.group=dev
    #命名空间中,其他配置集[0]
    spring.cloud.nacos.config.ext-config[0].data-id=oss.yaml
    spring.cloud.nacos.config.ext-config[0].group=dev
    spring.cloud.nacos.config.ext-config[0].refresh=true
    
  7. 第三方服务 的主启动类上开启 服务发现与注册 功能

    @EnableDiscoveryClient
    

9.4.3 API:OSS服务端签名💡

  1. 第三方服务 中创建 cn.lzwei.bilimall.thirdparty.controller.OssController

    package cn.lzwei.bilimall.thirdparty.controller;
    
    @RestController
    public class OssController {
        @Resource
        OSS ossClient;
    
        @Value("${spring.cloud.alicloud.access-key}")
        private String accessId;
        @Value("${spring.cloud.alicloud.oss.endpoint}")
        private String endpoint;
        @Value("${spring.cloud.alicloud.oss.bucket}")
        private String bucket;
    
        @RequestMapping("/oss/policy")
        public R policy(){
            // 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
            String dir = DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDate.now())+"/";
            String host= "https://" + bucket + "." + endpoint;
    
            Map<String, String> respMap = null;
            try {
                long expireTime = 30;
                long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
                Date expiration = new Date(expireEndTime);
                PolicyConditions policyConds = new PolicyConditions();
                policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
                policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
    
                String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
                byte[] binaryData = postPolicy.getBytes("utf-8");
                String encodedPolicy = BinaryUtil.toBase64String(binaryData);
                String postSignature = ossClient.calculatePostSignature(postPolicy);
    
                respMap = new LinkedHashMap<String, String>();
                respMap.put("accessId", accessId);
                respMap.put("policy", encodedPolicy);
                respMap.put("signature", postSignature);
                respMap.put("dir", dir);
                respMap.put("host", host);
                respMap.put("expire", String.valueOf(expireEndTime / 1000));
                // respMap.put("expire", formatISO8601Date(expiration));
    
    
            } catch (Exception e) {
                // Assert.fail(e.getMessage());
                System.out.println(e.getMessage());
            }
            return R.ok().put("data",respMap);
        }
    }
    
  2. 第三方服务 中添加配置信息:

    spring:
      cloud:
        alicloud:
          oss:
            bucket: bilimall-20221226
    
  3. 网关服务bilimall-gateway 中添加路由规则:注意规则顺序,越具体的匹配路径优先级越高

    spring:
      cloud:
        gateway:
          routes:
            - id: thir_party_route
              uri: lb://bilimall-third-party
              predicates:
                - Path=/api/thirdparty/**
              filters:
                - RewritePath=/api/thirdparty/?(?<segment>.*),/$\{segment}
    
  4. 启动 第三方服务 ,查看访问效果

    在这里插入图片描述

9.5 前后端联调:文件上传💡

9.5.1 前端开发:文件上传控件

  1. 将文件上传组件的文件夹拷贝到 src/components 目录下

    在这里插入图片描述

  2. 修改 components/upload/singleUpload.vue:修改上传的请求地址为自己 存储桶域名

    <template> 
      <div>
        <el-upload
          action="https://bilimall-20221226.oss-cn-guangzhou.aliyuncs.com"
        </el-upload>
      </div>
    </template>
    
  3. 修改 components/upload/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'		//删除请求参数
        }).then(({ data }) => {
          resolve(data)
        })
      })
    }
    
  4. 在品牌新增的弹窗组件中引入文件上传组件:views/modules/product/brand-add-or-update.vue

    <template>
      <el-dialog
        :title="!dataForm.brandId ? '新增' : '修改'"
        :close-on-click-modal="false"
        :visible.sync="visible">
        <el-form-item label="品牌logo地址" prop="logo">
          <!-- <el-input v-model="dataForm.logo" placeholder="品牌logo地址"></el-input> -->
          <singleUpload v-model="dataForm.logo"></singleUpload>
        </el-form-item>
      </el-dialog>
    </template>
    
    <script>
      import singleUpload from '@/components/upload/singleUpload'
      export default {
        components: {
          singleUpload
        }
      }
    </script>
    

9.5.2 出现问题:上传阿里云跨域

在这里插入图片描述

9.5.3 问题解决:配置阿里云跨域

  • 在阿里云控制台中对 存储桶bilimall-20221226 进行跨域设置

    在这里插入图片描述

  • 上传成功

    在这里插入图片描述

9.6 API&前端开发:新增、表单图片显示💡

在这里插入图片描述

API

BrandController

/**
 * 保存
 */
@RequestMapping("/save")
public R save(@RequestBody BrandEntity brand){
	brandService.save(brand);

    return R.ok();
}

前端

  1. 将 views/modules/product/brand.vue 表单品牌logo地址显示更改为图片控件:引入elementui所有组件,使用<el-image>,效果不佳

  2. 将 views/modules/product/brand.vue 表单品牌logo地址显示使用原生 <image>

    <template>
      <div class="mod-config">
        <el-table
          :data="dataList"
          border
          v-loading="dataListLoading"
          @selection-change="selectionChangeHandle"
          style="width: 100%;">
          <el-table-column
            prop="logo"
            header-align="center"
            align="center"
            label="品牌logo地址">
            <template slot-scope="scope">
              <!-- <el-image
                  style="width: 100px; height: 80px"
                  :src="scope.row.logo"
              fit="contain"></el-image> -->
              <img :src="scope.row.logo" style="width: 100px; height: 80px" />
            </template>
          </el-table-column>
        </el-table>
      </div>
    </template>
    
  3. 更改 views/modules/product/brand-add-or-update.vue 的表单校验规则:首字母、排序

    <template>
      <el-dialog
        :title="!dataForm.brandId ? '新增' : '修改'"
        :close-on-click-modal="false"
        :visible.sync="visible">
        <el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="140px">
        <el-form-item label="排序" prop="sort">
          <el-input v-model.number="dataForm.sort" placeholder="排序"></el-input>
        </el-form-item>
        </el-form>
      </el-dialog>
    </template>
    
    <script>
      export default {
        data () {
          return {
            dataRule: {
              firstLetter: [
                {
                  validator: (rule, value, callback) => {
                    if (value === '') {
                      callback(new Error('首字母必须填写'))
                    } else if (!/^[a-zA-Z]$/.test(value)) {
                      callback(new Error('首字母必须a-z或者A-Z之间'))
                    } else {
                      callback()
                    }
                  },
                  trigger: 'blur'
                }
              ],
              sort: [
                {
                  validator: (rule, value, callback) => {
                    if (value === '') {
                      callback(new Error('排序字段必须填写'))
                    } else if (!Number.isInteger(value) || value < 0) {
                      callback(new Error('排序必须是一个大于等于0的整数'))
                    } else {
                      callback()
                    }
                  },
                  trigger: 'blur'
                }
              ]
            }
          }
        }
      }
    </script>
    

9.7 后端开发:数据校验&统一异常处理⚠️

9.7.1 API:添加JSR303校验💡

在这里插入图片描述

添加校验,开启校验功能并封装返回信息

  1. 给 BrandEntity 添加校验规则:添加javax.validation.constraints包下的注解 @NotBlank、@URL、@Pattern、@Min@NotEmpty、@NotNull

    @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(message = "品牌logo地址")
    	@URL(message = "品牌logo地址必须是标准的url地址")
    	private String logo;
    	/**
    	 * 介绍
    	 */
    	private String descript;
    	/**
    	 * 显示状态[0-不显示;1-显示]
    	 */
    	@NotNull(message = "显示状态必须非空")
    	private Integer showStatus;
    	/**
    	 * 检索首字母
    	 */
    	@NotEmpty(message = "首字母非空")
    	@Pattern(regexp = "^[a-zA-Z]$",message = "首字母必须是a-z或A-Z")
    	private String firstLetter;
    	/**
    	 * 排序
    	 */
    	@NotNull(message = "排序非空")
    	@Min(message = "排序字段必须非空且大于等于0",value = 0)
    	private Integer sort;
    }
    
  2. 在 BrandController 相应API中开启校验功能:添加 @Valid 注解

    效验的bean后紧跟一个 BindingResult 获取到效验的结果

    /**
     * 保存
     */
    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
        if (result.hasErrors()) {
            Map<String,String> map=new HashMap();
            //1.获取所有校验异常的字段,并进行遍历组装
            result.getFieldErrors().forEach(item->{
                String field = item.getField();
                String defaultMessage = item.getDefaultMessage();
                map.put(field,defaultMessage);
            });
            //2.返回收集结果
            return R.error(400,"数据校验失败").put("data",map);
        }else {
            brandService.save(brand);
            return R.ok();
        }
    }
    

9.7.2 统一异常处理:获取校验异常类型

2022-12-27 17:59:36.053 ERROR 21812 --- [io-11000-exec-1] .b.p.e.BilimallExceptionControllerAdvice : 数据校验异常信息...异常类型class org.springframework.web.bind.MethodArgumentNotValidException

这里使用到了 SpringMVC 的注解 @ControllerAdvice

  • 编写异常处理类使用SpringMvc的@ControllerAdvice

  • 使用@ExceptionHandler标记方法可以处理异常

发送请求获取校验异常类型:MethodArgumentNotValidException

  1. 关闭 BrandController 中的校验异常拦截

    /**
     * 保存
     */
    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand/**,BindingResult result*/){
    //    if (result.hasErrors()) {
    //        Map<String,String> map=new HashMap();
    //        //1.获取所有校验异常的字段,并进行遍历组装
    //        result.getFieldErrors().forEach(item->{
    //            String field = item.getField();
    //            String defaultMessage = item.getDefaultMessage();
    //            map.put(field,defaultMessage);
    //        });
    //        //2.返回收集结果
    //        return R.error(400,"数据校验失败").put("data",map);
    //    }else {
    //        brandService.save(brand);
    //        return R.ok();
    //    }
        brandService.save(brand);
        return R.ok();
    }
    
  2. 在 商品服务 中添加统一异常处理类:BilimallExceptionControllerAdvice

    /**
     * 集中处理所有异常
     */
    @Slf4j
    @ResponseBody
    @ControllerAdvice(basePackages = "cn.lzwei.bilimall.product.controller")
    public class BilimallExceptionControllerAdvice {
    
        /**
         * 数据校验失败
         * @param e
         * @return
         */
        @ExceptionHandler(value = Exception.class)
        public R handleVaildException(Exception e){
            log.debug("数据校验异常信息{},异常类型{}",e.getMessage(),e.getClass());//获取校验异常类型
            return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(), BizCodeEnume.VAILD_EXCEPTION.getMessage()).put("data",e.getMessage());
        }
    }
    

9.7.3 统一异常处理:优化⚠️

在这里插入图片描述

区分数据校验异常、其他异常

统一异常码、异常信息:后端将定义的错误码写入到开发手册,前端出现对于的错误,就可以通过手册查询到对应的异常

  1. 公共服务 中增加异常状态枚举类:cn.lzwei.common.exception.BizCodeEnume

    /***
     * 错误码和错误信息定义类
     * 1. 错误码定义规则为5为数字
     * 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
     * 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
     * 错误码列表:
     *  10: 通用
     *      001:参数格式校验
     *  11: 商品
     *  12: 订单
     *  13: 购物车
     *  14: 物流
     */
    public enum BizCodeEnume {
        UNKNOW_EXCEPTION(10000,"系统未知异常"),
        VAILD_EXCEPTION(10001,"参数格式校验失败");
        private Integer code;
        private String message;
        BizCodeEnume(Integer code,String message){
            this.code=code;
            this.message=message;
        }
        public Integer getCode() {
            return code;
        }
        public String getMessage() {
            return message;
        }
    }
    
  2. 商品服务 统一异常处理类 BilimallExceptionControllerAdvice 中进一步优化:区分数据校验异常、其他异常;统一异常码、异常信息

    /**
     * 集中处理所有异常
     */
    @Slf4j
    @RestControllerAdvice(basePackages = "cn.lzwei.bilimall.product.controller")
    public class BilimallExceptionControllerAdvice {
    
        /**
         * 数据校验失败
         * @param e
         * @return
         */
        @ExceptionHandler(value = MethodArgumentNotValidException.class)
        public R handleVaildException(MethodArgumentNotValidException e){
            log.error("数据校验异常信息{},异常类型{}",e.getMessage(),e.getClass());
            Map<String,String> map=new HashMap();
            //1.获取所有校验异常的字段,并进行遍历组装
            BindingResult bindingResult = e.getBindingResult();
            bindingResult.getFieldErrors().forEach(item->{
                String field = item.getField();
                String defaultMessage = item.getDefaultMessage();
                map.put(field,defaultMessage);
            });
            //2.返回收集结果
            return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(), BizCodeEnume.VAILD_EXCEPTION.getMessage()).put("data",map);
        }
    
        /**
         * 其他异常
         */
        @ExceptionHandler(value = Throwable.class)
        public R handleException(Throwable t){
            return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(), BizCodeEnume.UNKNOW_EXCEPTION.getMessage());
        }
    }
    

9.7.4 数据校验:分组校验⚠️

注意💡:

API指定分组@Validated(value = {xxx.class}) :@NotNull(groups = {xxx.class}) 生效,其他不生效

API未指定分组 @Valid:@NotNull(groups = {xxx.class})不生效,其他生效

在这里插入图片描述

  1. 公共服务 中新增分组接口:cn.lzwei.common.valid.SaveGroup、UpdateStatusGroup、UpdateGroup

    public interface SaveGroup {
    }
    public interface UpdateStatusGroup {
    }
    public interface UpdateGroup {
    }
    
  2. 商品服务 BrandEntity中添加校验分组

    @Data
    @TableName("pms_brand")
    public class BrandEntity implements Serializable {
    	private static final long serialVersionUID = 1L;
    
    	/**
    	 * 品牌id
    	 */
    	@NotNull(message = "修改时品牌id不能为空",groups = {UpdateGroup.class, UpdateStatusGroup.class})
    	@Null(message = "新增品牌时id必须为空",groups = {SaveGroup.class})
    	@TableId
    	private Long brandId;
    	/**
    	 * 品牌名
    	 */
    	@NotBlank(message = "品牌名称至少包含一个非空字符",groups = {UpdateGroup.class,SaveGroup.class})
    	@Null(message = "修改状态时品牌名必须为空",groups = {UpdateStatusGroup.class})
    	private String name;
    	/**
    	 * 品牌logo地址
    	 */
    	@NotEmpty(message = "品牌logo地址",groups = {UpdateGroup.class,SaveGroup.class})
    	@URL(message = "品牌logo地址必须是标准的url地址",groups = {UpdateGroup.class,SaveGroup.class})
    	@Null(message = "修改状态时品牌logo地址必须为空",groups = {UpdateStatusGroup.class})
    	private String logo;
    	/**
    	 * 介绍
    	 */
    	@Null(message = "修改状态时介绍必须为空",groups = {UpdateStatusGroup.class})
    	private String descript;
    	/**
    	 * 显示状态[0-不显示;1-显示]
    	 */
    	@NotNull(message = "显示状态必须非空",groups = {UpdateGroup.class,SaveGroup.class, UpdateStatusGroup.class})
    	private Integer showStatus;
    	/**
    	 * 检索首字母
    	 */
    	@NotEmpty(message = "首字母非空",groups = {UpdateGroup.class,SaveGroup.class})
    	@Pattern(regexp = "^[a-zA-Z]$",message = "首字母必须是a-z或A-Z",groups = {UpdateGroup.class,SaveGroup.class})
    	@Null(message = "修改状态时首字母必须为空",groups = {UpdateStatusGroup.class})
    	private String firstLetter;
    	/**
    	 * 排序
    	 */
    	@NotNull(message = "排序非空",groups = {UpdateGroup.class,SaveGroup.class})
    	@Min(message = "排序字段必须非空且大于等于0",value = 0,groups = {UpdateGroup.class,SaveGroup.class})
    	@Null(message = "修改状态时排序必须为空",groups = {UpdateStatusGroup.class})
    	private Integer sort;
    }
    
  3. 商品服务 BrandController中添加注解开启分组校验:@Validated

    /**
     * 保存
     */
    @RequestMapping("/save")
    public R save(@Validated(value = {SaveGroup.class}) @RequestBody BrandEntity brand/**,BindingResult result*/){
        brandService.save(brand);
        return R.ok();
    }
    /**
     * 修改
     */
    @RequestMapping("/update")
    public R update(@Validated(value = {UpdateGroup.class}) @RequestBody BrandEntity brand){
    	brandService.updateById(brand);
        return R.ok();
    }
    /**
     * 修改状态
     */
    @RequestMapping("/update/status")
    public R updateStatus(@Validated(value = {UpdateStatusGroup.class}) @RequestBody BrandEntity brand){
        brandService.updateById(brand);
        return R.ok();
    }
    

9.7.5 数据校验:自定义校验⚠️

在这里插入图片描述

  1. 公共服务 中创建自定义校验注解:cn.lzwei.common.valid.@ListValue

    @Constraint(validatedBy = {StatusConstraintValidatorForInteger.class }) //校验器
    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    @Documented
    public @interface ListValue {
        String message() default "{cn.lzwei.common.valid.ListValue.message}"; //默认在配置文件中获取消息
    
        Class<?>[] groups() default { };
    
        Class<? extends Payload>[] payload() default { };
    
        int[] value() default { }; //可接受的值
    }
    
  2. 公共服务 中创建配置文件并配置 自定义注解 的默认消息:ValidationMessages.properties

    cn.lzwei.common.valid.ListValue.message     = 必须使用指定的值
    
  3. 公共服务 中为自定义校验注解创建校验器:cn.lzwei.common.valid.StatusConstraintValidatorForInteger

    /**
     * 自定义校验注解@ListValue 的Integer校验器
     */
    public class StatusConstraintValidatorForInteger implements ConstraintValidator<ListValue,Integer> {
    
        Set<Integer> set=new HashSet<>();
        //初始化:获取可接受的数据集合
        @Override
        public void initialize(ListValue constraintAnnotation) {
            int[] value = constraintAnnotation.value();
            if (value!=null || value.length>0){
                for (int i : value) {
                    set.add(i);
                }
            }
        }
        //判断:值是否可接受
        @Override
        public boolean isValid(Integer value, ConstraintValidatorContext context) {
            return set.contains(value);
        }
    }
    
  4. 商品服务 的 BrandEntity 的显示状态上使用自定义注解

    public class BrandEntity implements Serializable {
    	private static final long serialVersionUID = 1L;
        ...
            
    	/**
    	 * 品牌id
    	 */
    	@NotNull(message = "修改时品牌id不能为空",groups = {UpdateGroup.class, UpdateStatusGroup.class})
    	@Null(message = "新增品牌时id必须为空",groups = {SaveGroup.class})
    	@TableId
    	private Long brandId;
    	/**
    	 * 显示状态[0-不显示;1-显示]
    	 */
    	@ListValue(value={1,0},groups = {UpdateStatusGroup.class})
    	@NotNull(message = "显示状态必须非空",groups = {UpdateGroup.class,SaveGroup.class, UpdateStatusGroup.class})
    	private Integer showStatus;
    }
    

9.8 后端开发:优化 分页&搜索💡

在这里插入图片描述

  1. 分页优化:cn.lzwei.bilimall.product.config.MybatisConfig

    /**
     * 分页查询
     */
    @MapperScan("cn.lzwei.bilimall.product.dao")
    @Configuration
    @EnableTransactionManagement
    public class MybatisConfig {
    
        @Bean
        public PaginationInterceptor paginationInterceptor(){
            PaginationInterceptor paginationInterceptor=new PaginationInterceptor();
            //设置请求的页面大于最大页后操作,true回到首页,false继续请求。默认false
            paginationInterceptor.setOverflow(true);
            //设置最大单页限制数量,默认500条,-1不受限制
            paginationInterceptor.setLimit(1000);
            return paginationInterceptor;
        }
    }
    
  2. 搜索优化:cn.lzwei.bilimall.product.service.impl.BrandServiceImpl

    @Service("brandService")
    public class BrandServiceImpl extends ServiceImpl<BrandDao, BrandEntity> implements BrandService {
    
        @Override
        public PageUtils queryPage(Map<String, Object> params) {
            String key = (String) params.get("key");
            QueryWrapper<BrandEntity> queryWrapper=new QueryWrapper();
            //添加搜索条件
            if(!StringUtils.isNullOrEmpty(key)){
                queryWrapper.eq("brand_id",key).or().like("name",key);
            }
            IPage<BrandEntity> page=  this.page(
                    new Query<BrandEntity>().getPage(params),
                    queryWrapper
            );
            return new PageUtils(page);
        }
    
    }
    

9.9 API:关联分类 查询&新增💡

在这里插入图片描述

在这里插入图片描述

API开发

  1. CategoryBrandRelationController

    @RestController
    @RequestMapping("product/categorybrandrelation")
    public class CategoryBrandRelationController {
        @Autowired
        private CategoryBrandRelationService categoryBrandRelationService;
    
        /**
         * 获取品牌关联的所有分类列表
         */
        @GetMapping("/catelog/list")
        public R cateloglist(@RequestParam(name = "brandId") Long brandId){
            List<CategoryBrandRelationEntity> data=categoryBrandRelationService.list(
                    new QueryWrapper<CategoryBrandRelationEntity>().eq("brand_id",brandId)
            );
    
            return R.ok().put("data", data);
        }
        /**
         * 保存品牌的分类信息:添加冗余字段 分类名+品牌名
         */
        @RequestMapping("/save")
        public R save(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){
    		categoryBrandRelationService.saveDetail(categoryBrandRelation);
    
            return R.ok();
        }
    }
    
  2. CategoryBrandRelationService

    public interface CategoryBrandRelationService extends IService<CategoryBrandRelationEntity> {
    
        PageUtils queryPage(Map<String, Object> params);
    
        /**
         * 保存品牌的分类信息:添加冗余字段 分类名+品牌名
         */
        void saveDetail(CategoryBrandRelationEntity categoryBrandRelation);
    }
    
  3. CategoryBrandRelationServiceImpl

    @Service("categoryBrandRelationService")
    public class CategoryBrandRelationServiceImpl extends ServiceImpl<CategoryBrandRelationDao, CategoryBrandRelationEntity> implements CategoryBrandRelationService {
    
        @Resource
        BrandService brandService;
        @Resource
        CategoryService categoryService;
    
        /**
         * 保存品牌的分类信息:添加冗余字段 分类名+品牌名
         */
        @Override
        public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
            //1.获取品牌、分类信息
            Long brandId = categoryBrandRelation.getBrandId();
            Long catelogId = categoryBrandRelation.getCatelogId();
            BrandEntity brand = brandService.getById(brandId);
            CategoryEntity category = categoryService.getById(catelogId);
            //2.添加冗余字段 分类名+品牌名
            categoryBrandRelation.setBrandName(brand.getName());
            categoryBrandRelation.setCatelogName(category.getName());
            //3.保存关联信息
            this.save(categoryBrandRelation);
        }
    }
    

9.10 API:修改优化&品牌分类关系表💡

在这里插入图片描述

  1. BrandController:修改品牌,还需同步修改品牌分类关系表

    @RestController
    @RequestMapping("product/brand")
    public class BrandController {
        @Autowired
        private BrandService brandService;
        /**
         * 品牌修改:并更新在其他表中的冗余字段
         */
        @RequestMapping("/update")
        public R update(@Validated(value = {UpdateGroup.class}) @RequestBody BrandEntity brand){
    		brandService.updateDetail(brand);
    
            return R.ok();
        }
    }
    
  2. BrandService:修改品牌,还需同步修改品牌分类关系表

    public interface BrandService extends IService<BrandEntity> {
        /**
         * 品牌修改:并更新在其他表中的冗余字段
         */
        void updateDetail(BrandEntity brand);
    }
    
  3. BrandServiceImpl:修改品牌,还需同步修改品牌分类关系表

    @Service("brandService")
    public class BrandServiceImpl extends ServiceImpl<BrandDao, BrandEntity> implements BrandService {
        @Resource
        CategoryBrandRelationService categoryBrandRelationService;
        /**
         * 品牌修改:并更新在其他表中的冗余字段
         */
        @Transactional
        @Override
        public void updateDetail(BrandEntity brand) {
            this.updateById(brand);
            //1.更新 品牌分类关联表
            Long brandId = brand.getBrandId();
            String name = brand.getName();
            categoryBrandRelationService.updateBrand(brandId,name);
            //TODO 品牌修改:更新在其他表中的冗余字段
        }
    }
    
  4. CategoryBrandRelationService:修改品牌,还需同步修改品牌分类关系表

    public interface CategoryBrandRelationService extends IService<CategoryBrandRelationEntity> {
        /**
         * 品牌修改:并更新在其他表中的冗余字段
         */
        void updateBrand(Long brandId, String name);
    }
    
  5. CategoryBrandRelationServiceImpl:修改品牌,还需同步修改品牌分类关系表

    @Service("categoryBrandRelationService")
    public class CategoryBrandRelationServiceImpl extends ServiceImpl<CategoryBrandRelationDao, CategoryBrandRelationEntity> implements CategoryBrandRelationService {
        /**
         * 品牌修改:并更新在其他表中的冗余字段
         */
        @Override
        public void updateBrand(Long brandId, String name) {
            CategoryBrandRelationEntity categoryBrandRelationEntity = new CategoryBrandRelationEntity();
            categoryBrandRelationEntity.setBrandName(name);
            baseMapper.update(
                    categoryBrandRelationEntity,
                    new UpdateWrapper<CategoryBrandRelationEntity>().eq("brand_id",brandId)
            );
        }
    }
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/136034.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux进程间通信---->共享内存

文章目录什么是共享内存共享内存基本原理和共享内存有关的系统接口ftokshmgetipc相关命令查看相关共享内存信息删除相关共享内存信息shmat/shmdtshmctlipc系列设计思想总结什么是共享内存 前面我们学习了管进程间通信的一种方式—>管道。 而我们今天将要介绍的共享内存也是…

10天,几万字,源码深度解析之 Spring IOC

历时 10 天&#xff0c;终于把 Sping 源码系列写完了&#xff0c;该系列一共 5 篇&#xff0c;后续会整理成 PDF 教程&#xff0c;本文是最后一篇。 这篇文章主要讲解 IOC 容器的创建过程&#xff0c;让你对整体有一个全局的认识&#xff0c;文章没有复杂嵌套的 debug 流程&am…

SA实战 ·《SpringCloud Alibaba实战》第06章-快速搭建三大微服务并完成交互开发与测试

作者:冰河 星球:http://m6z.cn/6aeFbs 博客:https://binghe.gitcode.host 文章汇总:https://binghe.gitcode.host/md/all/all.html 大家好,我是冰河~~ 在《SpringCloud Alibaba实战》专栏中前面的文章,我们为开发用户微服务、商品微服务和订单微服务做了充分的准备。今天…

微服务 SpringBoot 整合 Redis GEO 实现附近商户功能

文章目录⛄引言♨️广播站一、Redis GEO 数据结构用法⛅GEO基本语法、指令⚡使用GEO存储经纬度、查询距离二、SpringBoot 整合Redis 导入 店铺数据 到GEO三、SpringBoot 整合 Redis 实现 附近商户功能☁️需求介绍⚡核心源码✅附近商户效果图⛵小结⛄引言 本文参考黑马 点评项…

Spring之xml方式整合第三方框架

目录 一&#xff1a;概述 二&#xff1a;代码演示 二&#xff1a;Spring整合MyBatis的原理剖析 三&#xff1a;案例演示 一&#xff1a;概述 xml整合第三方框架有两种整合方案&#xff1a; 不需要自定义名空间,不需要使用Spring的配置文件配置第三方框架本身内容,例如&…

VScode连接本地Docker

一、安装VScode和Docker 1、vscode的安装 官网下载到最新的X64安装包&#xff0c;然后使用下述命令进行安装&#xff1a; dpkg -i code_1.73.1-1667967334_amd64.deb 安装成功之后&#xff0c;可以在应用的安装列表中查看到。 2、docker的安装 同上 二、创建docker的用户…

使用nginx临时搭建rtmp服务器

使用nginx临时搭建rtmp服务器 文章目录使用nginx临时搭建rtmp服务器系统环境搭建步骤RTMP服务验证由于需要研究rtmp协议交互方式及报数据格式&#xff0c;使用nginx临时搭建一个rtmp服务器&#xff0c;主要通过nginx的rtmp扩展模块实现接收RTMP推送的音视频流&#xff0c;同时提…

【C++】缺省参数

其实在C基础一文中已经介绍过了缺省参数&#xff0c;但是每次用这玩意都是很迷&#xff0c;今天趁着复习c知识&#xff0c;再来总结一下缺省参数。 &#x1f308;1.缺省参数知识图&#xff1a; 看来看去也就这么多的知识点&#xff0c;接下来就一一介绍一下&#xff1a; &…

prometheus监控微服务端口和主机存活

简介&#xff1a; BlackBox Exporter 顾名思义就是在应用程序的外部对其进行探测&#xff0c; 支持 HTTP、HTTPS、DNS、TCP、ICMP等方式对目标进行检测。 官方下载链接 https://github.com/prometheus/blackbox_exporter/releases/download/v0.21.1/blackbox_exporter-0.21.…

CRM管理系统软件哪家好?

规模不大的企业&#xff0c;往往抗风险能力较差、资金不足、员工也相对比较少&#xff0c;此时&#xff0c;客户资源&#xff0c;客户开发往往成为企业生存的基础。 对于企业&#xff0c;一款合适的CRM客户管理系统&#xff0c;绝对是小规模企业的必备工具&#xff0c;可以帮助…

自除数判断,除自身以外数组乘积,[ ]操作符,二维数组内存存储计算,有关进制转换与取数字每一位的问题

tips 1. 表达式求值的时候&#xff0c;首先当然是从左往右看&#xff0c;确定优先级&#xff08;只针对相邻操作符才有意义&#xff09;&#xff0c;相邻操作符按照优先级高低计算&#xff0c;如果&#xff08;相邻&#xff09;操作符的优先级相同&#xff08;也就是两个操作符…

4G低功耗摄像头模组如何快速唤醒拍照

对于应用在野外恶劣环境&#xff0c;无电无网络的情况下&#xff0c;需要一款能支持太阳能供电或者电池供电&#xff0c;不过前提是&#xff0c;功耗需要足够低&#xff0c;还需要能支持无线网络&#xff0c;能上传图片回到服务器&#xff0c;用于监测一些野外作业的数据&#…

Vue.set()的使用,以及对其进行深入解析

目录 Vue.set()使用 Vue.delete()的使用 Vue.set()方法原理解析 总结 Vue.set()使用 vue 在实例上添加新的属性的时候&#xff0c;该属性&#xff0c;并不是响应式的。同样删除某一属性的时候&#xff0c;也不会实时渲染到页面上。 比如&#xff1a; <p> 年龄&#x…

Python开发案例之用Python子进程关闭Excel自动化中的弹窗

利用Python进行Excel自动化操作的过程中&#xff0c;尤其是涉及VBA时&#xff0c;可能遇到消息框/弹窗&#xff08;MsgBox&#xff09;。此时需要人为响应&#xff0c;否则代码卡死直至超时 [^1] [^2]。根本的解决方法是VBA代码中不要出现类似弹窗&#xff0c;但有时我们无权修…

在专网建设场景,LoRa和NB的技术优劣对比

先说结论&#xff1a;运营商在大铺NB&#xff0c;LoRa更适用于专网。 对于某个企业或者组织的实际应用来说&#xff0c;最后很可能是nb做骨架&#xff0c;lora做补充&#xff0c;混合应用。除非是nb在覆盖继续完善做到无死角 其实&#xff0c;对于物联网复杂的应用场景来说&am…

国产的内网穿透工具也很优秀,这10款工具推荐正在寻找的你!

什么是内网穿透&#xff1f; 首先&#xff0c;我们生活中的网络从应用上可以分为内网和外网&#xff1b; 内网就是你自己的网络环境&#xff0c;就你自己能访问&#xff0c;比如你本地测试进行的localhost&#xff1b; 外网就不言而喻了&#xff0c;你看网页&#xff0c;视频…

利用vite创建vue3工程

目录 什么是vite 优势&#xff1a; 简单理解&#xff1a; 1、创建工程 2、进入工程目录&#xff0c;安装依赖 3、启动​编辑 什么是vite 官方创建的前端构建工具 优势&#xff1a; 1开发环境中&#xff0c;无需打包操作&#xff0c;可快速冷启动 2轻量快速的热重载 3真…

Word文件加密的方法有哪些?两种方法告诉你

日常生活工作中&#xff0c;我们经常会使用到Word文档。有时里面有些比较重要的内容&#xff0c;我们不想别人随便可以更改我们输入的内容、窥探我们的隐私&#xff0c;我们该怎么做&#xff1f;建议给你的word文件加密&#xff0c;这样就能更好保护我们的信息。 操作环境&…

C语言论坛系统[2023-01-03]

C语言论坛系统[2023-01-03] 论坛系统设计 课程说明 需要提交的内容包括两个部分。 第一部分&#xff0c;对代码功能的讲解。 课设要求最后每个同学录制一个讲解视频&#xff0c;对着自己代码的功能进行讲解。 讲解时&#xff0c;主要涉及一个几个标准步骤&#xff1a; 步骤一…

【实操篇】Linux定时任务调度

目录 ●crond任务调度 简要介绍 基本语法 常用选项 参数细节说明 典型案例 应用实例 ●crond任务调度 简要介绍&#xff1a; 任务调度&#xff0c;它是指系统在某个特定时间去执行的特定命令或程序。它分为两类&#xff0c;第一类为系统工作&#xff08;一些周…