• 谷粒商城笔记-环境配置(2)——文件上传、java参数验证、递归,分页、事务


    18.阿里云OSS文件上传功能

            

       18.1   创建三方服务

    • 创建微服务gulimall-third-part:

    组织名:com.atguigu.gulimall;模块名:gulimall-third-part;包名:com.atguigu.gulimall.thirdpart

    Name:gulimall.thirdpart 。之后在下一步的依赖中添加web—>spring web,Spring Cloud Routing—> open Feign

    • 添加公用配置依赖:

                a.添加common 公共依赖

    View Code

                 b.nacos注册中心,配置中心

    注册中心: 添加pom引用(common中包含),新建配置文件(bootstrap.properties)添加配置,开启注册中心(在启动服务main函数类上)@EnableDiscoveryClient

    配置文件:

    spring.application.name=gulimall-third-part

    spring.cloud.nacos.config.server-addr=127.0.0.1:8848

    server.port= 88

     18.2 OSS阿里云存储

     分布式系统中的文件存储:https://www.jianshu.com/p/1be6bf51566f

     阿里云教程:https://help.aliyun.com/learn/learningpath/oss.html?spm=5176.7933691.J_7985555940.3.53e94c59v339me

    阿里云使用使用官网:https://help.aliyun.com/document_detail/32009.html

    1. 登录阿里云账号,进入对象存储界面(对象存储OSS)
    2. 在Bucket 列表创建一个gulimaill-hello2的bucket,创建过程中需要的参数介绍:https://help.aliyun.com/document_detail/31827.html

     

     3.创建访问密钥 在鼠标放到用户头像显示的AccessKey 管理——>选择开始使用子用户管理AccessKey——>创建用户名与显示名都为gulimall的用户之后,         访问方式Open API 调用访问——为新用户添加权限——>创建新的AccessKey,之后保存id与密码。

    AliyunOSSFullAccess

    管理对象存储服务(OSS)权限

            4.用文件上传方式上传文件:参考地址:https://help.aliyun.com/document_detail/84781.html

                本次尝试使用的是上传文件流;

    View Code

       18.3 Spring Alibaba OSS 封装的组件

                Spring Alibaba OSS 封装的组件:https://github.com/alibaba/aliyun-spring-boot/tree/master/aliyun-spring-boot-samples/aliyun-oss-spring-boot-sample

                1、添加pom文件的引用

                     <dependency>   

                         <groupId>com.alibaba.cloud</groupId>   

                             <artifactId>aliyun-oss-spring-boot-starter</artifactId>

                            </dependency>

                 2、在配置文件中.直接添加配置

      // application.properties

    alibaba.cloud.access-key=your-ak

    alibaba.cloud.secret-key=your-sk

    alibaba.cloud.oss.endpoint=***

        3、在微服务中的调用: OSSClient 以服务注册的方式注入。

    @Service
     public class YourService {
         @Autowired
         private OSSClient ossClient;
    
         public void saveFile() {
                     String bucketName = "gulimaill-hello2";
    // 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
            InputStream inputStream = new FileInputStream("/D:\testa2.txt");
    // 依次填写Bucket名称(例如examplebucket)和Object完整路径(例如exampledir/exampleobject.txt)。Object完整路径中不能包含Bucket名称。
            ossClient.putObject(bucketName, "testa2.txt", inputStream);
    
         }
     }
    View Code

                 4、配置文件配置在nacos配置中心中

                       

    根据nacos中的配置,配置成配置文件

    spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    spring.cloud.nacos.config.namespace=d3791198-6eea-45e8-8e33-8e1a37bc5f34

    spring.cloud.nacos.config.ext-config[0].data-id=gulimall-third-part-oss.yml
    spring.cloud.nacos.config.ext-config[0].group=DEFAULT_GROUP
    spring.cloud.nacos.config.ext-config[0].refresh=true 

    18.4 分布式系统中的文件上传模型  

                   参考文档:https://help.aliyun.com/document_detail/31926.html

    1、服务器模拟policy,微服务中生成一个通用方法

    @RestController
    public class OSSController {
        @Autowired
        OSS ossClient;
        @Value("${spring.cloud.alicloud.oss.endpoint}")
        private  String endpoint;
        @Value("${spring.cloud.alicloud.oss.bucket}")
        private String bucket;
        @Value("${spring.cloud.alicloud.access-key}")
        private String accessId;
       
        @RequestMapping("/oss/policy")
        public   Map<String, String>  doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
           /* String accessId = "<yourAccessKeyId>"; // 请填写您的AccessKeyId。
            String accessKey = "<yourAccessKeySecret>"; // 请填写您的AccessKeySecret。
            String endpoint = "oss-cn-hangzhou.aliyuncs.com"; // 请填写您的 endpoint。*/
            //String bucket = "bucket-name"; // 请填写您的 bucketname 。
            String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
            // callbackUrl为上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
            //String callbackUrl = "http://88.88.88.88:8888";
            //修改为按照时间格式的方式
            String dateFormat=new SimpleDateFormat("yyyy-MM-dd").format(new Date());
            String dir =dateFormat+"/"; // 用户上传文件时指定的前缀。
            // 创建OSSClient实例。
            //OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);
            Map<String, String> respMap = new LinkedHashMap<String, String>();
            try {
                long expireTime = 30;
                long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
                Date expiration = new Date(expireEndTime);
                // PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
                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.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());
            } finally {
                ossClient.shutdown();
            }
            return  respMap;
        }
    }
    View Code

            2、测试结果:

                   调用链接:http://localhost:30000/oss/policy

    返回结果:

    {"accessid":"LTAI5tEVEUcQMFAYHM1p1Rwe","policy":"etJleHBpcmF0aW9uIjoiMjAyMS0xMC0wNVQwMjoyNDo1Mi4xNThaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIyMDIxLTEwLTA1LyJdXX0=","signature":"/j/0l4ZFpq2MQ4rSGCBsL4tt6N4=","dir":"2021-10-05/","host":"https://gulimaill-hello2.oss-cn-shanghai.aliyuncs.com","expire":"1633400692"}

            3、网关配置:

                   policy的链接访问统一从: http://localhost:88/api/thirdpart/oss/policy

                       在网关配置中添加:

    id: third_party_route
      uri: lb://gulimall-third-party
      predicates:
        - Path=/api/thirdparty/**
      filters:
        - RewritePath=/api/thirdparty/(?<segment>.*),/${segment}

    19.JAVA参数校验(Valid,Validated,自定义检验)

    JSR-303校验

         JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。

    参考文件:https://www.jianshu.com/p/554533f88370

    19.1 添加pom文件             

    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>2.0.1.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>6.1.5.Final</version>
    </dependency>

    19.2一般验证机制 @Valid

                  a.目前JSR-303 可以验证的种类:

                 

        b.简单使用方法:

           controller中加校验注解@Valid,开启校验,给校验的Bean后,紧跟一个BindResult。就可以获取到校验的结果。在文件ValidationMessages.properties,可以查看到整个错误规则。ValidationMessages中存在中文注解。

    /*
    * 用来验证测试用
    * */
    @RequestMapping("/validateTest")
    public R validateTest(@Valid @RequestBody CouponEntity coupon, 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);
        }
        return R.ok();
    }
    View Code                       

                            c.错误码的定义:

                                  统一错误码定义:10000 前两位为业务场景,后三位为错误代码

    package com.atguigu.common.exception;
    
    public enum BizCodeEnume {
        UNKNOW_EXCEPTION(10000,"系统未知异常"),
        VAILD_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;
        }
    }
    View Code

                            d.  统一异常处理@ExceptionHandler                            

    常用到的4个注解:

    @ControllerAdvice标注在类上,通过“basePackages”能够说明处理哪些路径下的异常。

    @ResponseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML。

    @RestControllerAdvice 是@ControllerAdvice+@responseBody的功能。
    @ExceptionHandler(value = 异常类型.class)标注在方法上,表示处理的异常类型。

    以下为自定义的异常统一处理类型

    @Slf4j
    //@ResponseBody
    //@ControllerAdvice(basePackages = "com.atguigu.gulimall.coupon.controller")
    @RestControllerAdvice(basePackages = "com.atguigu.gulimall.coupon.controller")
    public class GulimallExceptionControllerAdvice {
    
        @ExceptionHandler(value= MethodArgumentNotValidException.class)
        public R handleVaildException(MethodArgumentNotValidException e){
            log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
            BindingResult bindingResult = e.getBindingResult();
    
            Map<String,String> errorMap = new HashMap<>();
            bindingResult.getFieldErrors().forEach((fieldError)->{
                errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
            });
            //return  R.error(400,"自己写的异常汇总方法");
            return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
        }
    
        @ExceptionHandler(value = Throwable.class)
        public R handleException(Throwable throwable){
    
            log.error("错误:",throwable);
            //return R.error(400,"自己写的异常汇总方法");
            return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
        }
    }
    View Code

                                  常见的异常统一处理类型

    /**
     * 异常处理器 在common中汇总统一异常:
     *
     * @author Mark sunlightcs@gmail.com
     */
    @RestControllerAdvice
    public class RRExceptionHandler {
       private Logger logger = LoggerFactory.getLogger(getClass());
    
       /**
        * 处理自定义异常
        */
       @ExceptionHandler(RRException.class)
       public R handleRRException(RRException e){
          R r = new R();
          r.put("code", e.getCode());
          r.put("msg", e.getMessage());
    
          return r;
       }
    
       @ExceptionHandler(NoHandlerFoundException.class)
       public R handlerNoFoundException(Exception e) {
          logger.error(e.getMessage(), e);
          return R.error(404, "路径不存在,请检查路径是否正确");
       }
    
       @ExceptionHandler(DuplicateKeyException.class)
       public R handleDuplicateKeyException(DuplicateKeyException e){
          logger.error(e.getMessage(), e);
          return R.error("数据库中已存在该记录");
       }
    
       @ExceptionHandler(AuthorizationException.class)
       public R handleAuthorizationException(AuthorizationException e){
          logger.error(e.getMessage(), e);
          return R.error("没有权限,请联系管理员授权");
       }
    
       @ExceptionHandler(Exception.class)
       public R handleException(Exception e){
          logger.error(e.getMessage(), e);
          return R.error();
       }
    }
    View Code

    e.测试用例:

    /**
     * 优惠卷名字
     */
    @NotBlank(message ="优惠卷名字不能为空" )
    @NotNull
    private String couponName;
    
    访问链接:http://localhost:7000/coupon/coupon/validatetest2
    
    request:{"id":3,"couponName":"","couponType":7 }
    
    respnse:
    {
        "msg": "参数格式校验失败",
        "code": 10001,
        "data": {
            "couponName": "优惠卷名字不能为空"
        }
    }
    View Code

             19.3分组校验功能(多场景校验)

                        a.创建分组接口,无需具体的实现类

    package com.atguigu.common.valid;
    
    public interface AddGroup {
    }
    public interface UpdateGroup {
    }
    View Code

                        b.vo实体上添加group,分组信息

                            在controller的方法上或者方法参数上写要处理的分组的接口信息,如@Validated(AddGroup.class)

                        c.测试用例

    /**
     * 优惠券图片
     */
    @NotBlank(message ="优惠券图片不能为空",groups = {AddGroup.class})
    private String couponImg;
    /**
     * 优惠卷名字
     */
    @NotBlank(message ="优惠卷名字不能为空",groups = {AddGroup.class, UpdateGroup.class})
    private String couponName;
    /**
     * 备注
     */
    @NotBlank(message ="备注不能为空",groups = {UpdateGroup.class})
    private String note;
    
    @NotBlank(message ="优惠券图片2不能为空")
    private String couponImg2;
    
    /*
     * 用来测试分组验证
     * */
    @RequestMapping("/validatetest3save")
    public R validateTest3save(@Validated(AddGroup.class) @RequestBody CouponEntity coupon){
        return R.ok();
    }
    
    /*
     * 用来测试分组验证
     * */
    @RequestMapping("/validatetest3update")
    public R validateTest3update(@Validated({UpdateGroup.class}) @RequestBody CouponEntity coupon){
        return R.ok();
    }
    
    原验证规则
    /*
     * 用来验证测试用
     * */
    @RequestMapping("/validatetest2")
    public R validateTest2(@Valid @RequestBody CouponEntity coupon){
        return R.ok();
    }
    
    
    原验证规则:
    http://localhost:7000/coupon/coupon/validatetest2
    {"id":3,"couponName":"","couponType":7 }
    {
        "msg": "参数格式校验失败",
        "code": 10001,
        "data": {
            "couponImg2": "优惠券图片2不能为空"
        }
    }
    保存结果:
    http://localhost:7000/coupon/coupon/validatetest3save
    {"id":3,"couponName":"","couponType":7 }
    {
        "msg": "参数格式校验失败",
        "code": 10001,
        "data": {
            "couponName": "优惠卷名字不能为空",
            "couponImg": "优惠券图片不能为空"
        }
    }
    更新结果:
    http://localhost:7000/coupon/coupon/validatetest3update
    {"id":3,"couponName":"","couponType":7 }
    {
        "msg": "参数格式校验失败",
        "code": 10001,
        "data": {
            "note": "备注不能为空",
            "couponName": "优惠卷名字不能为空"
        }
    }
    View Code

                           根据测试结果分析:

    由保存与修改的验证结果,看出
    在这种情况下,没有指定分组的校验注解,默认是不起作用的。想要起作用就必须要加groups。

              19.4自定义验证机制 @Valid

                      a.自定义注解:需要默认的三个方法(message(),groups(),payload())

    package com.atguigu.common.valid;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    import java.lang.annotation.*;
    
    @Documented
    @Constraint(
            validatedBy = {ListValueConstraintValidator.class}
    )
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ListValue {
        String message() default "{javax.validation.constraints.NotBlank.message}";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
        int[] vals() default{};
    }
    View Code

     因为上面的message值对应的最终字符串需要去ValidationMessages.properties中获得,所以我们在common中新建文件                                          ValidationMessages.properties      

               com.atguigu.common.valid.ListValue.message=必须提交指定的值 [0,1,2,3,4]

                       b.自定义校验器

    package com.atguigu.common.valid;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    import java.util.HashSet;
    import java.util.Set;
    
    public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
    
        private Set<Integer> set =new HashSet<>();
        //初始化操作
        @Override
        public void initialize(ListValue constraintAnnotation) {
            int [] vals= constraintAnnotation.vals();
            for (int val:vals){
                set.add(val);
            }
        }
    
       //验证规则是否正常的通过
        @Override
        public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
            return set.contains(integer);
        }
    }
    View Code

                              实现统一接口: ConstraintValidator, 第一个泛型参数是所对应的校验注解类型,第二个是校验对象类型

                      c. 自定义注解与校验器的关联

                         @Constraint(validatedBy = { ListValueConstraintValidator.class})

                      d.测试用例:

    /**
     * 优惠卷类型[0->全场赠券;1->会员赠券;2->购物赠券;3->注册赠券]
     */
    @ListValue(vals={0,1,2,3},groups = {AddGroup.class},message ="必须是指定的通用类型" )
    private Integer couponType;
    /**
    
    测试结果:
    http://localhost:7000/coupon/coupon/validatetest3save
    {"id":3,"couponName":"","couponType":7 }
    {
        "msg": "参数格式校验失败",
        "code": 10001,
        "data": {
            "couponName": "优惠卷名字不能为空",
            "couponType": "必须是指定的通用类型",
            "couponImg": "优惠券图片不能为空"
        }
    }
    View Code

    20. 实践案例总结

         20.1MyBatis-Plus的逻辑删除以及常用的注解

             a.@TableField  该字段只是实体中的字段,与数据库表字段没有实际的映射关系

    @TableField(exist = false)
    private String couponImg2;

    b.执行SQL的日志打印功能:在yml文件中添加配置

    logging:
    level:
    com.atguigu.gulimall: debug

                 注意事项:Debug前与: 要有一个空格。

             c. 逻辑删除操作正常的配置      

    1). 在yml文件中添加配置。
             mybatis-plus:
             mapper-locations: classpath:/mapper/**/*.xml
            global-config:
            db-config:
               id-type: auto
                logic-delete-value: 1
                 logic-not-delete-value: 0

    2). 在DAO数据库对象上添加注解。

    /**
    * 发布状态[0-未发布,1-已发布],用来测试逻辑删除的功能
    */
    @TableLogic(value = "1",delval = "0")
        private Integer publish;

    3).测试用例:

    执行删除操作:

    @RequestMapping("/delete/{id}")
    public R deleteByid(@PathVariable("id") Long id){
    couponService.removeByIds(Arrays.asList(id));
    return R.ok();
    }

    执行的sql日志打印出来:

          执行查询操作:

    /**
    * 信息
    */
    @RequestMapping("/info/{id}")
    //@RequiresPermissions("coupon:coupon:info")
    public R info(@PathVariable("id") Long id){
    CouponEntity coupon = couponService.getById(id);
    return R.ok().put("coupon", coupon);
    }

    执行的sql日志打印出来::

      SELECT id,coupon_type,coupon_img,coupon_name,note,num,amount,per_limit,min_point,start_time,end_time,use_type,publish_count,use_count,receive_count,enable_start_time,enable_end_time,code,member_level,publish FROM sms_coupon WHERE id=? AND publish=1

    测试结果分析:
    逻辑删除的添加,delete,remove操作删除为逻辑更新,其他的操作执行语句在执行先会默认添加逻辑删除的条件帅选。

    d.常用的分页插件 Pageutils的正常使用

    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CouponEntity> page = this.page(
        new Query<CouponEntity>().getPage(params),
        new QueryWrapper<CouponEntity>()
       );
       return new PageUtils(page);
    }

    分页查询:pageUtils,MyBatis Plus分页查询功能参考文档:https://blog.csdn.net/hancoder/article/details/113787197参考官网:https://mp.baomidou.com/guide/page.html

  • 相关阅读:
    【计算机视觉】OpenCV篇(6)
    【计算机视觉】OpenCV篇(5)
    C++解决头文件相互包含问题的方法
    【计算机视觉】OpenCV篇(4)
    java mysql多次事务 模拟依据汇率转账,并存储转账信息 分层完成 dao层 service 层 client层 连接池使用C3p0 写入库使用DBUtils
    MySQL事务隔离级别 解决并发问题
    在jdbc基础上进阶一小步的C3p0 连接池(DBCP 不能读xml配置文件,已淘汰) 和DBUtils 中两个主要类QueryRunner和ResultSetHandler的使用
    java数据库 JDBC操作MySQL数据库常用API 部门表和员工表 创建表 添加数据 查询数据
    java XML 通过BeanUtils的population为对象赋值 根据用户选择进行dom4j解析
    java基础 xml 使用dom4j解析 xml文件 servlet根据pattern 找到class
  • 原文地址:https://www.cnblogs.com/q994321263/p/15368959.html
Copyright © 2020-2023  润新知