Grain Mall - Basics - Commodity Service 2 - Brand Management (P59-P69) + (P75)


Merchandise Service - Brand Management

1. Commodity service-API-brand management

1. Use the front-end and back-end codes of reverse engineering

  1. Added brand management menu
    insert image description here
  2. Copy and /produt/main/resources/src/views/modules/productunder the reverse generated front-end code to the directory of the front-end projectbrand.vuebrand-add-or-update.vuegulimall-renren\gulimall-fast-vue\src\views\modules\product

The displayed page does not have add and delete functions, this is because of permission control

  1. Fix permission issues
  • v-if="isAuth('product:brand:save')"Determine whether there is permission, change to true

<el-button v-if="isAuth('product:brand:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
<el-button v-if="isAuth('product:brand:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
     
 
 /**
 * 是否有权限
 * @param {*} key
 */
export function isAuth (key) {
    
    
  // return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) !== -1 || false
  return true
}

2. Effect optimization and display switch

  1. To solve the console grammar check error, comment
    out build/webpack.base.conf.jsthe grammar check in
const createLintingRule = () => ({
    
    
    // test: /\.(js|vue)$/,
    // loader: 'eslint-loader',
    // enforce: 'pre',
    // include: [resolve('src'), resolve('test')],
    // options: {
    
    
    //   formatter: require('eslint-friendly-formatter'),
    //   emitWarning: !config.dev.showEslintErrorsInOverlay
    // }
})
  1. quick display switch

brand.vue

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


brand-add-or-update.vue

<el-form-item label="显示状态" prop="showStatus">
        <!-- 显示开关 -->
        <el-switch v-model="dataForm.showStatus" active-color="#13ce66" inactive-color="#ff4949" :active-value="1" :inactive-value="0" @change="updateBrandStatus(scope.row)">
        </el-switch>
</el-form-item>

  1. Finish state modification switch
 // 1. 显示转态修改的方法
    updateBrandStatus(data) {
    
    
      console.log("最新信息", data)
      let {
    
     brandId, showStatus } = data;
      // 1. 发送请求修改状态
      this.$http({
    
    
        url: this.$http.adornUrl("/product/brand/update"),
        method: "post",
        data: this.$http.adornData({
    
     brandId, showStatus: showStatus }, false)
      }).then(({
    
     data }) => {
    
    
        this.$message({
    
    
          type: "success",
          message: "状态更新成功"
        })
      });

    },

3. Open and use cloud storage

(1) Alibaba Cloud object storage oss

  1. Create a Bucket (as a project)

  2. This method is to manually upload pictures, in fact, we can set automatic upload pictures to Alibaba Cloud in the program

  3. object storage.

insert image description here

  • Direct transmission after server-side signature

The uploaded account information is stored in the application server. To
upload, first find the application server to ask for a policy upload strategy to generate an anti-counterfeit signature

insert image description here


(2) oss integration test

  1. import dependencies
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.8.0</version>
</dependency>
  1. Test upload function

Open a sub-account
Add access control

   @Test
    public void testUpload() throws FileNotFoundException {
    
    

        String endpoint = "oss-cn-beijing.aliyuncs.com";

        // 云账号AccessKey有所有API访问权限
        String accessKeyId = "xxxxxx";
        String accessKeySecret = "xxxxxxx";

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

        // 上传文件流。
        InputStream inputStream = new FileInputStream("C:\\Users\\文\\Desktop\\1.jpg");
        ossClient.putObject("gulimall-ljn", "1.jpg", inputStream);

        // 关闭OSSClient。
        ossClient.shutdown();
        System.out.println("上传成功.");
    }


(3)SpringCloud Alibaba

  1. import dependencies
  <!--        对象存储oss-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
        </dependency>
  1. application.ymlConfiguration key, secretand endpointrelated information
spring:
  cloud:
    alicloud:
      access-key: xxxx
      secret-key: xxxxx
      oss:
        endpoint: oss-cn-beijing.aliyuncs.com
  1. test
    @Autowired
    OSSClient ossClient;

    @Test
    public void testUpload() throws FileNotFoundException {
    
    

        // 上传文件流。
        InputStream inputStream = new FileInputStream("C:\\Users\\文\\Desktop\\1.jpg");
        ossClient.putObject("gulimall-ljn", "2.jpg", inputStream);

        // 关闭OSSClient。
        ossClient.shutdown();
        System.out.println("上传成功.");
    }

4. Create a third-party module (and complete adding upload function)

(1) Create a modulegulimall-third-party

  1. import dependency
    import common
    add in dependency managementspring-cloud-alibaba-dependencies
 <dependencyManagement>

  <!--        对象存储oss-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
        </dependency>

        <!--        依赖common-->
        <dependency>
            <groupId>com.ljn.gulimall</groupId>
            <artifactId>gulimall-commom</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <dependencies>
            <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>

  1. Nacos related configuration
  • create gulimall-third-partynamespace
  • Add oss.yml (object storage configuration file)
spring:
  cloud:
    alicloud:
      access-key: xxxxx
      secret-key: grbcDMCvxz0IR4r30DIrKU3ZGrfoZf
      oss:
        endpoint: oss-cn-beijing.aliyuncs.com
  • application.yml
server:
  port: 30000

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

logging:
  level:
    com.yxj.gulimall.product: debug

  • bootstrap.peoperties
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=d9ce505a-86f2-41e4-afbe-c4d62785b3ea
#加载nacos中的配置文件
spring.cloud.nacos.config.ext-config[0].data-id=oss.yml
spring.cloud.nacos.config.ext-config[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.ext-config[0].refresh=true


  1. oss test
  @Autowired
    OSSClient ossClient;

    @Test
    public void testUpload() throws FileNotFoundException {
    
    

        // 上传文件流。
        InputStream inputStream = new FileInputStream("C:\\Users\\文\\Desktop\\1.jpg");
        ossClient.putObject("gulimall-ljn", "4.jpg", inputStream);

        // 关闭OSSClient。
        ossClient.shutdown();
        System.out.println("上传成功.");
    }


(2) Improvement: direct transmission after signing on the server side

When the JavaScript client is used to directly sign (see JavaScript client signature direct transfer), the AccessKeyID and AccessKeySecret will be exposed on the front-end page, so there are serious security risks.

Therefore, OSS provides a solution for direct transmission after the server signs.

The principle of direct transmission after server-side signature is as follows:

insert image description here

  1. Write com.ljn.gulimall.thirdparty.controller.OssControllerclass: get server signature

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

    @RequestMapping("/oss/policy")
    public Map<String, String> policy() {
    
    

        // host的格式为 bucketname.endpoint
        String host = "https://" + bucket + "." + endpoint;

        String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        // 用户上传文件时指定的前缀。
        String dir = format;

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

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

}
  1. Test: http://localhost:30000/oss/policy
  • return signed data
{
    
    
    "accessid":"xxxxxx",
    "policy":"eyJleHBpcmF0aW9uIjoiMjAyMS0wMi0xNFQxMDoyOToxMS43ODhaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIyMDIxLTAyLTE0Il1dfQ==",
    "signature":"0OXDXrQ1vRNl61N5IaZXRFckCKM=",
    "dir":"2021-02-14",
    "host":"https://gulimall-fermhan.oss-cn-qingdao.aliyuncs.com",
    "expire":"1613298551"}

}
  1. Configure routing rules in the gate gateway
 # third-party 服务路由
        - id: third_party_route
            uri: lb://gulimall-gateway
            predicates:
              - Path=/api/thirdparty/**
            filters:
              - RewritePath=/api/thirdparty/(?<segment>/?.*),/$\{
    
    segment}

In the future, the access path when uploading files ishttp://localhost:88/api/thirdparty/oss/policy

(3) oss front-end joint debugging test upload

  1. Upload component upload
    insert image description here
  • 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);
    })
  });
}
  • singleUpload.vue
<template> 
  <div>
    <el-upload
      action="http://gulimall-ljn.oss-cn-beijing.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) {
      
      
        console.log("123124214124")
        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>

  • multiUpload.vue
<template>
  <div>
    <el-upload action="http://gulimall-ljn.oss-cn-beijing.aliyuncs.com" :data="dataObj" list-type="picture-card" :file-list="fileList" :before-upload="beforeUpload" :on-remove="handleRemove" :on-success="handleUploadSuccess" :on-preview="handlePreview" :limit="maxCount" :on-exceed="handleExceed">
      <i class="el-icon-plus"></i>
    </el-upload>
    <el-dialog :visible.sync="dialogVisible">
      <img width="100%" :src="dialogImageUrl" alt />
    </el-dialog>
  </div>
</template>
<script>
import {
    
     policy } from "./policy";
import {
    
     getUUID } from '@/utils'
export default {
    
    
  name: "multiUpload",
  props: {
    
    
    //图片属性数组
    value: Array,
    //最大上传图片数量
    maxCount: {
    
    
      type: Number,
      default: 30
    }
  },
  data() {
    
    
    return {
    
    
      dataObj: {
    
    
        policy: "",
        signature: "",
        key: "",
        ossaccessKeyId: "",
        dir: "",
        host: "",
        uuid: ""
      },
      dialogVisible: false,
      dialogImageUrl: null
    };
  },
  computed: {
    
    
    fileList() {
    
    
      let fileList = [];
      for (let i = 0; i < this.value.length; i++) {
    
    
        fileList.push({
    
     url: this.value[i] });
      }

      return fileList;
    }
  },
  mounted() {
    
     },
  methods: {
    
    
    emitInput(fileList) {
    
    
      let value = [];
      for (let i = 0; i < fileList.length; i++) {
    
    
        value.push(fileList[i].url);
      }
      this.$emit("input", value);
    },
    handleRemove(file, fileList) {
    
    
      this.emitInput(fileList);
    },
    handlePreview(file) {
    
    
      this.dialogVisible = true;
      this.dialogImageUrl = file.url;
    },
    beforeUpload(file) {
    
    
      let _self = this;
      return new Promise((resolve, reject) => {
    
    
        policy()
          .then(response => {
    
    
            console.log("这是什么${filename}");
            _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 => {
    
    
            console.log("出错了...", err)
            reject(false);
          });
      });
    },
    handleUploadSuccess(res, file) {
    
    
      this.fileList.push({
    
    
        name: file.name,
        // url: this.dataObj.host + "/" + this.dataObj.dir + "/" + file.name; 替换${filename}为真正的文件名
        url: this.dataObj.host + "/" + this.dataObj.key.replace("${filename}", file.name)
      });
      this.emitInput(this.fileList);
    },
    handleExceed(files, fileList) {
    
    
      this.$message({
    
    
        message: "最多只能上传" + this.maxCount + "张图片",
        type: "warning",
        duration: 1000
      });
    }
  }
};
</script>
<style>
</style>
  1. Components used: brand-add-or-update.vuemedium
 <el-form-item label="品牌logo地址" prop="logo">
      <!-- 使用上传组件 -->
      <single-upload  v-model="dataForm.logo"></single-upload>
</el-form-item>
      
// 导入上传组件
import singleUpload from '../../../components/upload/singleUpload.vue';
export default {
    
    
  components: {
    
     singleUpload },
  ...
<template>
  <div>
    <el-upload action="http://gulimall-ljn.oss-cn-beijing.aliyuncs.com" :data="dataObj" list-type="picture-card" :file-list="fileList" :before-upload="beforeUpload" :on-remove="handleRemove" :on-success="handleUploadSuccess" :on-preview="handlePreview" :limit="maxCount" :on-exceed="handleExceed">
      <i class="el-icon-plus"></i>
    </el-upload>
    <el-dialog :visible.sync="dialogVisible">
      <img width="100%" :src="dialogImageUrl" alt />
    </el-dialog>
  </div>
</template>
<script>
import {
    
     policy } from "./policy";
import {
    
     getUUID } from '@/utils'
export default {
    
    
  name: "multiUpload",
  props: {
    
    
    //图片属性数组
    value: Array,
    //最大上传图片数量
    maxCount: {
    
    
      type: Number,
      default: 30
    }
  },
  data() {
    
    
    return {
    
    
      dataObj: {
    
    
        policy: "",
        signature: "",
        key: "",
        ossaccessKeyId: "",
        dir: "",
        host: "",
        uuid: ""
      },
      dialogVisible: false,
      dialogImageUrl: null
    };
  },
  computed: {
    
    
    fileList() {
    
    
      let fileList = [];
      for (let i = 0; i < this.value.length; i++) {
    
    
        fileList.push({
    
     url: this.value[i] });
      }

      return fileList;
    }
  },
  mounted() {
    
     },
  methods: {
    
    
    emitInput(fileList) {
    
    
      let value = [];
      for (let i = 0; i < fileList.length; i++) {
    
    
        value.push(fileList[i].url);
      }
      this.$emit("input", value);
    },
    handleRemove(file, fileList) {
    
    
      this.emitInput(fileList);
    },
    handlePreview(file) {
    
    
      this.dialogVisible = true;
      this.dialogImageUrl = file.url;
    },
    beforeUpload(file) {
    
    
      let _self = this;
      return new Promise((resolve, reject) => {
    
    
        policy()
          .then(response => {
    
    
            console.log("这是什么${filename}");
            _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 => {
    
    
            console.log("出错了...", err)
            reject(false);
          });
      });
    },
    handleUploadSuccess(res, file) {
    
    
      this.fileList.push({
    
    
        name: file.name,
        // url: this.dataObj.host + "/" + this.dataObj.dir + "/" + file.name; 替换${filename}为真正的文件名
        url: this.dataObj.host + "/" + this.dataObj.key.replace("${filename}", file.name)
      });
      this.emitInput(this.fileList);
    },
    handleExceed(files, fileList) {
    
    
      this.$message({
    
    
        message: "最多只能上传" + this.maxCount + "张图片",
        type: "warning",
        duration: 1000
      });
    }
  }
};
</script>
<style>
</style>



  1. Cross-domain problem when uploading starts: just set it in the basic settings in oss
    insert image description here
  2. test
    insert image description here

5. Form validation & custom validator (front-end validation)

  1. Modify brand.vue, customize display logo
 <el-table-column prop="logo" header-align="center" align="center" label="品牌logo地址">
        <template slot-scope="scope">
          <img :src="scope.row.logo" style="width:100px ; height:80pxx" />
        </template>
      </el-table-column>

  1. brand-add-or-update.vueCustom inspection rules in

 // v-model.number
 <el-form-item label="排序" prop="sort">
        <el-input v-model.number="dataForm.sort" placeholder="排序"></el-input>
     	 </el-form-item>
</el-form>

 firstLetter: [
          {
    
    
            validator: (rule, value, callback) => {
    
    
              if (value == "" && value != 0) {
    
    
                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(parseInt(value)) || parseInt(value) < 0) {
    
    
              callback(new Error("排序字段必须是一个大于等于0的整数"));
            } else {
    
    
              callback();
            }
          }, trigger: "blur"
        }]

6. JSR303 data verification (backend verification)


(1) JSR303 data verification

  1. productBrandEntityAdd verification, annotation and define your own message prompt in the service
  • import javax.validation.constraintsUnder the reference package, such as:
	@NotBlank(message = "品牌名不能为空")
	private String name;

	@NotEmpty
    @URL(message = "logo地址必须合法")
    private String logo;
    
    @Pattern(regexp = "/^[a-zA-Z]$/", message = "检索首字母必须是一个字母")
    private String firstLetter;

    @Min(value = 0,message = "排序必须大于等于0")
    private Integer sort;
  1. @Valid注解Add open check in the method of controller
  • The new version of springboot needs to add validationa starter
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-validation</artifactId>
     <version>2.3.7.RELEASE</version>
</dependency>

like:

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

        return R.ok();
    }
  1. After the custom encapsulation
    is given to the verified Bean, followed by one BindResult, the result of the verification can be obtained. After getting the verification result, you can customize the package.
   @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) -> {
    
    
                //获取发生错误时的message
                String message = item.getDefaultMessage();
                //获取发生错误的字段
                String field = item.getField();
                map.put(field, message);
            });
            return R.error(400, "提交的数据不合法").put("data", map);
        } else {
    
    

            brandService.save(brand);
        }

        return R.ok();
    }

This is to set a content check for the request. It is obviously not appropriate to configure it separately for each request. In fact, exceptions can be handled uniformly.


(2) Unified exception handling

  1. Unified exception handling@ControllerAdvice
  • You can use what SpringMvc provides @ControllerAdvice, by basePackagesbeing able to describe which paths to handle exceptions.

  • Extract an exception handling class:com.ljn.gulimall.product.exception.GuliMallExceptionControllerAdvice


@Slf4j
@RestControllerAdvice(basePackages = "com.ljn.gulimall.product.controller")
public class GuliMallExceptionControllerAdvice {
    
    
	// 数据校验异常	
    @ExceptionHandler(value = Exception.class)
    public R handleValidException(MethodArgumentNotValidException exception) {
    
    

        Map<String, String> map = new HashMap<>();
        // 1. 获取数据校验的错误结果
        BindingResult bindingResult = exception.getBindingResult();
        // 2. 遍历获取结果
        bindingResult.getFieldErrors().forEach(fieldError -> {
    
    
            String message = fieldError.getDefaultMessage();
            String field = fieldError.getField();
            map.put(field, message);
        });

        log.error("数据校验出现问题{},异常类型{}", exception.getMessage(), exception.getClass());

        return R.error(400, "数据校验出现问题").put("data", map);
    }
  //     默认异常
    @ExceptionHandler(value = Throwable.class)
    public R handleException(Throwable throwable) {
    
    
        log.error("未知异常{},异常类型{}", throwable.getMessage(), throwable.getClass());
        return R.error(400, "数据校验出现问题");
    }
}
  1. Error status code setting
  • In order to define these error status codes, we can define a constant class separately to store these error status codes
    insert image description here
public enum BizCodeEnume {
    
    

    UNKNOW_EXEPTION(10000, "系统未知异常"),

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

    private int code;
    private String msg;
    

    BizCodeEnume(int code, String msg) {
    
    
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
    
    
        return code;
    }

    public String getMsg() {
    
    
        return msg;
    }
}

test:
insert image description here

(3) JSR303 group inspection

  • @ValidatedAnnotation specifies grouping
  • On the label groups, specify under what circumstances a verification is required
  • By default, in the case of group verification, if the verification annotation of the specified group is not specified, it will not take effect, and it will only take effect when there is no group.

For example: Specify that verification is required when updating and adding. No id is required when adding, and id is required when modifying

@NotNull(message = "修改必须定制品牌id", groups = {
    
    UpdateGroup.class})
@Null(message = "新增不能指定id", groups = {
    
    AddGroup.class})
@TableId
private Long brandId;

@RequestMapping("/save")
public R save(@Validated(AddGroup.class) @RequestBody BrandEntity brand) {
    
    
    brandService.save(brand);

    return R.ok();
}

(4) JSR303 custom validation annotation

  • Write custom inspection annotations
@Documented
@Constraint(validatedBy = {
    
    })
@Target({
    
    ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
    
    

    // 使用该属性去Validation.properties中取
    String message() default "{com.ljn.common.valid.ListValue.message}";

    Class<?>[] groups() default {
    
    };

    Class<? extends Payload>[] payload() default {
    
    };

    int[] value() default {
    
    };
}

  • commonCreate file ValidationMessages.propertiesconfiguration file
com.ljn.common.valid.ListValue.message=必须提交指定的值
  • Write a custom validatorConstraintValidator
public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
    
    

    private Set<Integer> set = new HashSet<>();

    // 初始化
    @Override
    public void initialize(ListValue constraintAnnotation) {
    
    

        int[] value = constraintAnnotation.value();
        for (int i : value) {
    
    
            set.add(i);
        }
    }

    // 判断是否校验成功
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
    
    
        return set.contains(value);
    }
}
  • Associate Validators and Validation Annotations
@Constraint(validatedBy = {
    
     ListValueConstraintValidator.class})
一个校验注解可以匹配多个校验器

7. Brand classification association and cascading update

(1) Introduce the mybatis paging plugin

The paging that comes with mp is memory paging, which has low performance, so you need to manually write the paging configuration and use physical paging

Under prduct services, create a new configfolder, MyBatisConfigclass

@Configuration
@EnableTransactionManagement
@MapperScan("com.ljn.gulimall.product.dao")
public class MyBatisConfig {
    
    

    // 引入分页插件
    @Bean
    public PaginationInterceptor paginationInterceptor() {
    
    

        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        //设置请求的页面大于最大页后操作,true调回到首页,false继续请求
        paginationInterceptor.setOverflow(true);
        // 设置最大单页限制
        paginationInterceptor.setLimit(1000);
        return paginationInterceptor;

    }
}

(2) Brand fuzzy query

Revisecom.ljn.gulimall.product.service.impl.BrandServiceImpl

   @Override
    public PageUtils queryPage(Map<String, Object> params) {
    
    
        // 1、获取key
        String key = (String) params.get("key");
        QueryWrapper<BrandEntity> queryWrapper = new QueryWrapper<>();

        if (!StringUtils.isEmpty(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);
    }

(3) Association classification

  • The newly added Huawei, Xiaomi, and oppo should all be brands under mobile phones, but they 品牌对分类may be 一对多. For example, Xiaomi corresponds to mobile phones and TVs
  • 多对多The relationship should have relationtables, such as pms_category_brand_relationbrand association classification table
  1. Get brand association classification, modifycom.ljn.gulimall.product.controller.CategoryBrandRelationController
	/**
     * 获取当前品牌关联的所有分类列表
     */
    @GetMapping("/catelog/list")
    public R catelogList(@RequestParam("brandId") Long brandId) {
    
    
        List<CategoryBrandRelationEntity> data = categoryBrandRelationService.list(
                new QueryWrapper<CategoryBrandRelationEntity>()
                        .eq("brandId", brandId)
        );


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

	
  1. Add brand and category association

The category name could have been in the brand table, but because association queries have an impact on database performance, data in large tables is never associated in e-commerce, even if it is checked step by step. Therefore, redundant fields such as name can be saved
. Optimize save, use the association table to save when saving, but do not need to associate when selecting

   /**
     * 保存
     */
    @RequestMapping("/save")
    public R save(@RequestBody CategoryBrandRelationEntity categoryBrandRelation) {
    
    
        // 自定义sava
        categoryBrandRelationService.saveDetail(categoryBrandRelation);

        return R.ok();
    }

com.ljn.gulimall.product.service.impl.CategoryBrandRelationServiceImpl

	 @Autowired
    BrandDao brandDao;

    @Autowired
    CategoryDao categoryDao;

 // 保存详细信息
    @Override
    public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
    
    
        // 1.获取id
        Long brandId = categoryBrandRelation.getBrandId();
        Long catelogId = categoryBrandRelation.getCatelogId();
        // 2.根据id获取详情
        BrandEntity brandEntity = brandDao.selectById(brandId);
        CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
        // 3.将名字设置到关联关系
        categoryBrandRelation.setBrandName(brandEntity.getName());
        categoryBrandRelation.setCatelogName(categoryEntity.getName());
        // 4.保存完整信息
        this.save(categoryBrandRelation);
    }
  1. Optimization: keep the data of redundant fields consistent

If the name in the category table changes, the category name field in the brand table should change synchronously.

Therefore, it should be modified brandControllerso that the name in the classification table is detected for synchronization when it is updated


    /**
     * 修改
     */
    @RequestMapping("/update")
    public R update(@Validated(UpdateGruop.class) @RequestBody BrandEntity brand) {
    
    
        // 修改为更新细节
        brandService.updateDetail(brand);

        return R.ok();
    }

BrandServiceImpl

 	@Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Override
    public void updateDetail(BrandEntity brand) {
    
    
        // 保证冗余字段的数据一致
        // 1. 更新自己表中的数据
        this.updateById(brand);
        // 2.同步更新其他关联表中的数据
        if(!StringUtils.isEmpty(brand.getName())){
    
    
            categoryBrandRelationService.updateBrand(brand.getBrandId(),brand.getName());
            
            //TODO 更新其他关联
        }
    }

CategoryBrandRelationServiceImpl

   @Override
    public void updateBrand(Long brandId, String name) {
    
    
        CategoryBrandRelationEntity relationEntity = new CategoryBrandRelationEntity();
        relationEntity.setBrandId(brandId);
        relationEntity.setBrandName(name);
        this.update(relationEntity, new QueryWrapper<CategoryBrandRelationEntity>().eq("brand_id", brandId));
    }
  1. Synchronous classification (with the second method)
    CategoryBrandRelationServiceImpl
   @Override
    public void updateCategory(Long catId, String name) {
    
    
    	// 这里使用baseMapper
        this.baseMapper.updateCategory(catId, name);
    }

CategoryBrandRelationDao

@Mapper
public interface CategoryBrandRelationDao extends BaseMapper<CategoryBrandRelationEntity> {
    
    

   void updateCategory(@Param("catId") Long catId, @Param("name") String name);
}

CategoryBrandRelationDao.xml


   <update id="updateCategory">
        update  pms_category_brand_relation set catelog_name=#{name} where catelog_id=#{catId}
    </update>

Guess you like

Origin blog.csdn.net/ljn1046016768/article/details/124330573