需求

1. 表单校验需求

1.1 前端校验

参考element-ui官网: https://element.eleme.cn/#/zh-CN/component/form

前端表格展示

<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="140px">
  <el-form-item label="品牌名" prop="name">
    <el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
  </el-form-item>

  <el-form-item label="品牌logo地址" prop="logo">
    <single-upload v-model="dataForm.logo"></single-upload>
  </el-form-item>

  <el-form-item label="介绍" prop="descript">
    <el-input v-model="dataForm.descript" placeholder="介绍"></el-input>
  </el-form-item>

  <!--激活是1 ,不激活是0-->
  <el-form-item label="显示状态" prop="showStatus">
    <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-item label="检索首字母" prop="firstLetter">
    <el-input v-model="dataForm.firstLetter" placeholder="检索首字母"></el-input>
  </el-form-item>

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

校验规则

//和前端输入内容进行双向绑定
dataForm: {
      brandId: 0,
      name: "",
      logo: "",
      descript: "",
      showStatus: 1,
      firstLetter: "",
      sort: 0
    },

dataRule: {
    //name字段校验规则,失去焦点触发。
    name: [{ required: true, message: "品牌名不能为空", trigger: "blur" }],

    //logo字段校验规则
    logo: [{ required: true, message: "品牌logo地址不能为空", trigger: "blur" }],

    //描述字段校验规则
    descript: [{ required: true, message: "介绍不能为空", trigger: "blur" }],

    //展示状态校验规则
    showStatus: [{
        required: true,
        message: "显示状态[0-不显示;1-显示]不能为空",
        trigger: "blur"
      }],
    
    //首字母校验规则,自定义校验规则,传入一个校验器。
    //rule:校验规则, value:传入的值, callback:回调函数。
    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"
      }
    ]
  }

1.2 后端校验

只有前端校验是不够的的,如果通过postman等工具直接向后端发请求,那前端校验规则就不起作用了。

思路

  • 在pojo上添加校验注解。
  • controller收到请求,将请求体封装为pojo,且根据注解对其进行校验。
  • 校验不通过,将异常抛出。
  • 新建一个统一处理异常的类。
  • 不同情况,校验规则不同,分组校验
    • 新增时,id自增,不允许传递。
    • 修改时,id必须传递。
  • 显示状态只能传入0或者1,自定义规则。

1.新增分组校验接口

  • 定义空接口即可,只是一个标识。
public interface AddGroup {
}

public interface UpdateGroup {
}

public interface UpdateStateGroup {
}

2.pojo添加校验注解

  • 品牌id新增和修改校验规则不同,指定不同接口。
  • 品牌名不能为空。
  • logo不能为空,且必须是合法的url。
  • 品牌介绍不能为空。
  • 显示状态需要自定义校验器。
  • 检索首字母不能为空,且必须符合规则,使用正则表达式即可。
  • 排序不能为空,且必须符合规则,使用正则表达式即可。
//品牌id
@TableId
@Null(message = "新增不能指定id",groups = {AddGroup.class})
@NotNull(message = "修改必须指定id",groups = {UpdateGroup.class})
private Long brandId;

//品牌名
@NotBlank(message = "品牌名不能为空",groups = {AddGroup.class,UpdateGroup.class})
private String name;

//品牌logo地址
@NotEmpty(groups = {AddGroup.class,UpdateGroup.class})
@URL(message = "logo必须是一个合法的URL地址",groups ={AddGroup.class,UpdateGroup.class} )
private String logo;

//品牌介绍
@NotEmpty(groups = {AddGroup.class,UpdateGroup.class})
private String descript;

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

//检索首字母,自定义注解,传入一个正则表达式
@Pattern(regexp = "^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups ={AddGroup.class,UpdateGroup.class})
@NotEmpty(groups ={AddGroup.class,UpdateGroup.class} )
private String firstLetter;

//排序
@Min(value =0,message = "排序必须是一个大于等于0的整数",groups ={AddGroup.class,UpdateGroup.class})
@NotNull(groups ={AddGroup.class,UpdateGroup.class} )
private Integer sort;

3.Controller接收请求,并进行注解校验

  • 根据不同请求指定不同校验分组。
//新增指定校验组为AddGroup
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
    brandService.save(brand);
    return R.ok();
}

//修改指定校验组为UpdateGroup
@RequestMapping("/update")
public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
    brandService.updateDetail(brand);
    return R.ok();
}

4.统一处理校验出错时返回结果

  • 指定包名,该异常处理器只对该包下的异常进行捕获处理。
  • 指定异常类型,根据不同的异常进行不同的处理。
@Slf4j
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class MyExceptionControllerAdvice {

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleValidException(MethodArgumentNotValidException e){
        BindingResult result = e.getBindingResult();
        //新建一个集合,用来存放错误数据
        HashMap<String, String> map = new HashMap<>();
        result.getFieldErrors().forEach((item)-> {
            //错误属性字段
            String field = item.getField();
            //错误提示信息
            String message = item.getDefaultMessage();
            map.put(field, message);
        });
        log.error("错误信息"+map);
        return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",map);
    }

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

2. 父子组件传参

某一页面引用了子组件,需要在子组件点击时获取子组件节点信息,并将该信息传递给父组件。

1.引入子组件,并绑定子组件定义的点击事件son-node-click,和父组件的方法fathernodeclick进行双向绑定。

  • 可以将子组件的事件参数,传递给父组件绑定的方法中。
// 引入子组件,占页面纵向6份
<el-col :span="6">
  <category @son-node-click="fathernodeclick"></category>
</el-col>

// 表格部分,占18份
<el-col :span="18">
...
</el-col>

子组件

  • 将回调事件node-click绑定子组件方法nodeclick
<el-tree :data="menus" :props="defaultProps"  node-key="catId" :default-expanded-keys="expanded" ref="menuTree" @node-click="nodeclick">
</el-tree>
  • 通过$emit将子组件事件和参数发散出去
nodeclick(data, node, component) {
  console.log("树节点被点击了", data, node, component);
  //向父组件发送事件(携带参数)和父组件的一个方法进行绑定。
  this.$emit("son-node-click", data, node, component);
},
  • 父组件通过事件名称进行接收。
  • 如果是第三级分类。就将点击的节点id传递给后端,重新查询数据更新表格内容。
<category @son-node-click="fathernodeclick"></category>

//感知树节点被点击
fathernodeclick(data, node, component) {
  if (node.level == 3) {
    this.catId = data.catId;
    this.getDataList(); //重新查询
  }
},

3.事务

修改一个表内容,与之相关的其他表格内的数据也应同时修改。

  • 为了性能,数据库通常会做一些冗余设计。
  • 如果该表数据修改成功,其他表也应该成功。该表修改失败,其他表也修改失败。

品牌表

品牌与分类关联表

1.修改品牌表内容时,应同步修改品牌和分类关联表,且使用事务。

  • 使用@Transactional注解标注的方法都是事务。
@Autowired
private CategoryBrandRelationService categoryBrandRelationService;

@Override
@Transactional
public void updateDetail(BrandEntity brand) {
    //先修改品牌表自身数据
    this.updateById(brand);
    //调用关联表方法,修改关联表内容
    //如果修改品牌内容时,品牌名不为空。则同步修改关联表内容。
    if(!StringUtils.isEmpty(brand.getName())) {
        categoryBrandRelationService.updataBrand(brand.getBrandId(), brand.getName());
    }
}

2.关联表更新内容

  • 根据品牌id更新所有品牌id的品牌名。
@Override
public void updataBrand(Long brandId, String name) {
    CategoryBrandRelationEntity entity = new CategoryBrandRelationEntity();
    entity.setBrandId(brandId);
    entity.setBrandName(name);
    this.update(entity,new UpdateWrapper<CategoryBrandRelationEntity>().eq("brand_id",brandId));
}

3.开启事务注解

  • 使用@EnableTransactionManagement注解开启事务功能。
@Configuration
@EnableTransactionManagement //开启事务功能,标注事务注解就能生效
@MapperScan("com.atguigu.gulimall.product.dao")
public class MybatisConfig {
    //引入分页插件
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
         paginationInterceptor.setOverflow(true);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
         paginationInterceptor.setLimit(1500);
        // 开启 count 的 join 优化,只针对部分 left join
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
        return paginationInterceptor;
    }
}
posted @ 2022-07-28 18:34  初夏那片海  阅读(58)  评论(0)    收藏  举报