06-微服务文件上传实战(总结与练习)

文章目录

项目简介

业务描述

基于Spring Cloud Alibaba解决方案实现文件上传,例如
在这里插入图片描述

初始架构设计

本次项目实践,整体上基于前后端分离架构,服务设计上基于spring cloud alibaba解决方案进行实现,例如:
在这里插入图片描述
说明,为了降低学习难度,这里只做了初始架构设计,后续会逐步基于这个架构进行演进,例如我们会加上网关工程,认证工程等.

工程创建及初始化

工程结构

参考如下工程结构,进行项目创建,例如:
在这里插入图片描述

创建父工程

创建项目父工程用来管理项目依赖.

在这里插入图片描述

在这里插入图片描述

创建文件服务工程

创建用于处理文件上传业务的工程,例如:

在这里插入图片描述

在这里插入图片描述

创建客户端服务工程

创建一个客户端工程,在此工程中定义一些静态页面,例如文件上传页面.

在这里插入图片描述
在这里插入图片描述

父工程初始化

打开父工程的pom.xml文件,添加如下依赖:

 <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.3.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR9</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
     <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

 
 

    文件资源服务实现

    添加项目依赖

    在sca-resource工程中添加如下依赖:

           <!--Spring Boot Web (服务-内置tomcat)-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--Nacos Discovery (服务注册发现)-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
            <!--Nacos Config (配置中心)-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            </dependency>
            <!--Sentinel (流量防卫兵-限流和熔断)-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            </dependency>
            <!--Spring Boot 监控-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
    
     
     

      服务初始化配置

      在项目的resources目录下创建bootstrap.yml配置文件(假如后续配置信息要写到配置中心配置文件名必须为bootstrap.yml),并添加如下内容:

      server:
        port: 8881
      spring:
        application:
          name: sca-resource
        servlet:
          multipart:
            max-file-size: 100MB #控制上传文件的大小
            max-request-size: 110MB #请求数据大小
        resources: #定义可以访问到上传资源的路径
          static-locations: file:d:/uploads  #静态资源路径(原先存储到resources/static目录下的资源可以存储到此目录中)
        cloud:
          nacos:
            discovery:
              server-addr: localhost:8848
            config:
              server-addr: localhost:8848
      jt:   #这里的配置,后续会在一些相关类中通过@Value注解进行读取
        resource:
            path: d:/uploads  #设计上传文件存储的根目录(后续要写到配置文件)
            host: http://localhost:8881/ #定义上传文件对应的访问服务器
      
       
       

        构建项目启动类

        在当前工程中,创建项目启动类,例如:

        package com.jt;
        
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        
        @SpringBootApplication
        public class FileApplication {
            public static void main(String[] args) {
                SpringApplication.run(FileApplication.class, args);
            }
        }
        
        

        类创建以后,启动当前项目,检测是否可以启动成功,是否有配置错误.

        Controller逻辑实现

        定义处理上传请求的Controller对象,例如:

        package com.jt.resource.controller;
        
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.beans.factory.annotation.Value;
        import org.springframework.web.bind.annotation.PostMapping;
        import org.springframework.web.bind.annotation.RequestMapping;
        import org.springframework.web.bind.annotation.RestController;
        import org.springframework.web.multipart.MultipartFile;
        
        import java.io.File;
        import java.io.IOException;
        import java.time.LocalDate;
        import java.time.format.DateTimeFormatter;
        import java.util.UUID;
        
        @Slf4j
        @RestController
        @RequestMapping("/resource/")
        public class ResourceController {
              //当了类的上面添加了@Slf4J就不用自己创建下面的日志对象了
        //    private static final Logger log=
        //            LoggerFactory.getLogger(ResourceController.class);
        
             @Value("${jt.resource.path}")
             private String resourcePath;//="d:/uploads/";
             @Value("${jt.resource.host}")
             private String resourceHost;//="http://localhost:8881/";
        
             @PostMapping("/upload/")
             public String uploadFile(MultipartFile uploadFile) throws IOException {
                 //1.创建文件存储目录(按时间创建-yyyy/MM/dd)
                 //1.1获取当前时间的一个目录
                 String dateDir = DateTimeFormatter.ofPattern("yyyy/MM/dd")
                         .format(LocalDate.now());
                 //1.2构建目录文件对象
                 File uploadFileDir=new File(resourcePath,dateDir);
                 if(!uploadFileDir.exists())uploadFileDir.mkdirs();
                 //2.给文件起个名字(尽量不重复)
                 //2.1获取原文件后缀
                 String originalFilename=uploadFile.getOriginalFilename();
                 String ext = originalFilename.substring(
                         originalFilename.lastIndexOf("."));
                 //2.2构建新的文件名
                 String newFilePrefix=UUID.randomUUID().toString();
                 String newFileName=newFilePrefix+ext;
                 //3.开始实现文件上传
                 //3.1构建新的文件对象,指向实际上传的文件最终地址
                 File file=new File(uploadFileDir,newFileName);
                 //3.2上传文件(向指定服务位置写文件数据)
                 uploadFile.transferTo(file);
                 String fileRealPath=resourceHost+dateDir+"/"+newFileName;
                 log.debug("fileRealPath {}",fileRealPath);
                 //后续可以将上传的文件信息写入到数据库?
                 return fileRealPath;
             }
        }
        
        

        跨域配置实现

        我们在通过客户端工程,访问文件上传服务时,需要进行跨域配置,在服务端的跨域配置中有多种方案,最常见是在过滤器的层面进行跨域设计,例如:

        package com.jt.files.config;
        
        /**
         * 跨域配置(基于过滤器方式进行配置,并且将过滤优先级设置高一些)
         */
        @Configuration
        public class CorsFilterConfig {
            @Bean
            public FilterRegistrationBean<CorsFilter> filterFilterRegistrationBean(){
                //1.对此过滤器进行配置(跨域设置-url,method)
                UrlBasedCorsConfigurationSource configSource=new UrlBasedCorsConfigurationSource();
                CorsConfiguration config=new CorsConfiguration();
                 //允许哪种请求头跨域
                config.addAllowedHeader("*");
                //允许哪种方法类型跨域 get post delete put
                config.addAllowedMethod("*");
                // 允许哪些请求源(ip:port)跨域
                config.addAllowedOrigin("*");
                //是否允许携带cookie跨域
                config.setAllowCredentials(true);
                //2.注册过滤器并设置其优先级
                configSource.registerCorsConfiguration("/**", config);
                FilterRegistrationBean<CorsFilter> fBean= new FilterRegistrationBean(new CorsFilter(configSource));
                fBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
                return fBean;
            }
        }
        
        

        客户端工程逻辑实现

        本次项目我们的客户端工程基于springboot工程进行设计,项目上线时可以将其静态资源直接放到一个静态资源目录中.

        添加依赖

        在sca-resource-ui工程的pom文件中添加web依赖,例如:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
         
         

          构建项目启动类

          package com.jt;
          
          import org.springframework.boot.SpringApplication;
          import org.springframework.boot.autoconfigure.SpringBootApplication;
          
          @SpringBootApplication
          public class ClientApplication {
              public static void main(String[] args) {
                  SpringApplication.run(ClientApplication .class, args);
              }
          }
          
          

          创建文件上传页面

          在工程的resources目录下创建static目录(假如这个目录已经存在则无需创建),然后在此目录创建fileupload.html静态页面,例如:

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>文件上载演示</title>
              <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
          </head>
          <body>
          <form id="fileForm" method="post" enctype="multipart/form-data" onsubmit="return doUpload()">
              <div>
                  <label>上传文件
                      <input id="uploadFile" type="file" name="uploadFile">
                  </label>
              </div>
              <button type="submit">上传文件</button>
          </form>
          </body>
          <script>
              //jquery代码的表单提交事件
              function doUpload(){
                  //获得用户选中的所有图片(获得数组)
                  let files=document.getElementById("uploadFile").files;
                  if(files.length>0){
                      //获得用户选中的唯一图片(从数组中取出)
                      let file=files[0];
                      //开始上传这个图片
                      //由于上传代码比较多,不想和这里其它代码干扰,所以定义一个方法调用
                      upload(file);
                  }
                  //阻止表单提交效果
                  return false;
              };
              // 将file上传到服务器的方法
              function upload(file){
                  //定义一个表单
                  let form=new FormData();
                  //将文件添加到表单中
                  form.append("uploadFile",file);
                  //异步提交
                  let url="http://localhost:8881/resource/upload/";
                  axios.post(url,form)
                       .then(function (response){
                           alert("upload ok")
                           console.log(response.data);
                       })
                       .catch(function (e){//失败时执行catch代码块
                           console.log(e);
                   })
              }
          </script>
          </html>
          
           
           

            启动服务访问测试

            第一步:启动nacos服务(在这里做服务的注册和配置管理)
            第二步:启动sca-resource服务,此服务提供文件上传功能
            第三步:启动sca-resource-ui服务,此服务为客户端工程,提供静态资源的访问.所有页面放到此工程中.
            第四步:打开浏览器,访问sca-resource-ui工程下的文件上传页面,例如:
            在这里插入图片描述

            API网关(Gateway)工程实践

            概述

            API 网关是外部资源对服务内部资源访问的入口,所以文件上传请求应该首先请求的是网关服务,然后由网关服务转发到具体的资源服务上。

            服务调用架构

            在这里插入图片描述

            工程项目结构设计

            在这里插入图片描述

            创建网关工程及初始化

            第一步:创建sca-resource-gateway工程,例如:

            在这里插入图片描述
            第二步:添加项目依赖,例如:

             <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-starter-gateway</artifactId>
                </dependency>
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
                </dependency>
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
                </dependency>
            
             
             

              第三步:创建配置文件bootstrap.xml,然后进行初始配置,例如:

              server:
                port: 9000
              spring:
                application:
                  name: sca-resource-gateway
                cloud:
                  nacos:
                    discovery:
                      server-addr: localhost:8848
                    config:
                      server-addr: localhost:8848
                      file-extension: yml
                  gateway:
                    discovery:
                      locator:
                        enabled: true
                    routes:
                      - id: router01
                        uri: lb://sca-resource
                        predicates:
                          - Path=/sca/resource/upload/**
                        filters:
                          - StripPrefix=1
              
               
               

                第四步:构建项目启动类,并进行服务启动,检测是否正确,例如:

                package com.jt;
                
                import org.springframework.boot.SpringApplication;
                import org.springframework.boot.autoconfigure.SpringBootApplication;
                
                @SpringBootApplication
                public class ResourceApplication {
                    public static void main(String[] args) {
                        SpringApplication.run(ResourceApplication.class,args);
                    }
                }
                
                

                网关跨域配置

                当我们基于Ajax技术访问网关时,需要在网关层面进行跨域设计,例如:

                package com.jt.config;
                
                import org.springframework.context.annotation.Bean;
                import org.springframework.web.cors.CorsConfiguration;
                import org.springframework.web.cors.reactive.CorsWebFilter;
                import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
                
                //@Configuration
                public class CorsFilterConfig {
                    @Bean
                    public CorsWebFilter corsWebFilter(){
                        //1.构建基于url方式的跨域配置
                        UrlBasedCorsConfigurationSource source= new UrlBasedCorsConfigurationSource();
                        //2.进行跨域配置
                        CorsConfiguration config=new CorsConfiguration();
                        //2.1允许所有ip:port进行跨域
                        config.addAllowedOrigin("*");
                        //2.2允许所有请求头跨域
                        config.addAllowedHeader("*");
                        //2.3允许所有请求方式跨域:get,post,..
                        config.addAllowedMethod("*");
                        //2.4允许携带有效cookie进行跨域
                        config.setAllowCredentials(true);
                        source.registerCorsConfiguration("/**",config);
                        return new CorsWebFilter(source);
                    }
                }
                
                

                Spring Gateway工程中的跨域设计,除了可以在网关项目中以java代码方式进行跨域过滤器配置,还可以直接在配置文件进行跨域配置,例如:

                spring:
                  cloud:
                    gateway:
                      globalcors: #跨域配置
                        corsConfigurations:
                          '[/**]':
                            allowedOrigins: "*"
                            allowedHeaders: "*"
                            allowedMethods: "*"
                            allowCredentials: true
                
                 
                 

                  启动工程进行服务访问

                  首先打开网关(Gateway),资源服务器(Resource),客户端工程服务(UI),然后修改fileupload.html文件中访问资源服务端的url,例如

                   let url="http://localhost:9000/sca/resource/upload/";
                  
                   
                   

                    接下来进行访问测试,例如:
                    在这里插入图片描述

                    网关上对文件上传限流

                    第一步:在网关pom文件中添加依赖

                    <dependency>
                        <groupId>com.alibaba.cloud</groupId>
                        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
                    </dependency>
                    
                    <dependency>
                        <groupId>com.alibaba.cloud</groupId>
                        <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
                    </dependency>
                    
                    

                    第二步:在网关配置文件中添加sentinel配置

                    sentinel:
                      transport:
                        dashboard: localhost:8180
                      eager: true
                    
                     
                     

                      第三步:在网关项目启动时,配置jvm启动参数,例如:

                      -Dcsp.sentinel.app.type=1
                      
                       
                       

                        第四步:先执行一次上传,然后对上传进行限流规则设计

                        在这里插入图片描述
                        第五步:修改文件上传页面js,对限流结果进行处理,例如:

                         function upload(file){
                                //定义一个表单(axios中提供的表单对象)
                                let form=new FormData();
                                //将文件添加到表单中
                                form.append("uploadFile",file);
                                //异步提交(现在是提交到网关)
                                //let url="http://localhost:8881/resource/upload/"
                                let url="http://localhost:9000/sca/resource/upload/";
                                axios.post(url,form)
                                    .then(function (response){
                                        alert("upload ok")
                                        console.log(response.data);
                                    })
                                    .catch(function (e){//失败时执行catch代码块
                                        //被限流后服务端返回的状态码为429
                                        if(e.response.status==429){
                                            alert("上传太频繁了");
                                        }
                                        console.log("error",e);
                                    })
                            }
                        
                         
                         

                          第六步:启动服务进行文件上传测试,检测限流效果

                          AOP方式操作日志记录

                          页面描述

                          在实现文件上传业务时,添加记录日志的操作.

                          添加项目依赖

                          在sca-resource工程中添加AOP依赖,例如:

                          <dependency>
                              <groupId>org.springframework.boot</groupId>
                              <artifactId>spring-boot-starter-aop</artifactId>
                          </dependency>
                          
                           
                           

                            创建切入点注解

                            我们项目要为目标业务实现功能增强,锦上添花,但系统要指定谁是目标业务,这里我们定义一个注解,后续用此注解描述目标业务。

                            package com.jt.resource.annotation;
                            
                            import java.lang.annotation.ElementType;
                            import java.lang.annotation.Retention;
                            import java.lang.annotation.RetentionPolicy;
                            import java.lang.annotation.Target;
                            
                            @Retention(RetentionPolicy.RUNTIME)
                            @Target(ElementType.METHOD)
                            public @interface RequiredLog {
                                String value() default "";
                            }
                            
                            

                            定义切入点方法

                            通过上面定义的注解RequiredLog,对sca-resources工程中的ResourceController文件上传方法进行描述,例如:

                            @RequiredLog("upload file")
                            @PostMapping("/upload/")
                            public String uploadFile(MultipartFile uploadFile) throws IOException {...}
                            
                             
                             

                              说明:通过@RequiredLog注解描述的方法可以认为锦上添花的“锦”,后续添花的行为可以放在切面的通知方法中。

                              定义日志操作切面

                              在AOP编程设计中,我们会通过切面封装切入点(Pointcut)和扩展业务逻辑(Around,…)的定义,例如:

                              package com.jt.resource.aspect;
                              
                              import lombok.extern.slf4j.Slf4j;
                              import org.aspectj.lang.ProceedingJoinPoint;
                              import org.aspectj.lang.annotation.Around;
                              import org.aspectj.lang.annotation.Aspect;
                              import org.aspectj.lang.annotation.Pointcut;
                              import org.springframework.stereotype.Component;
                              @Slf4j
                              @Aspect
                              @Component
                              public class LogAspect {
                                  //定义切入点
                                  @Pointcut("@annotation(com.jt.resource.annotation.RequiredLog)")
                                  public void doLog(){}//锦上添花的锦(注解描述的方法)
                              
                                  //定义扩展业务逻辑
                                  @Around("doLog()")
                                  //@Around("@annotation(com.jt.resource.annotation.RequiredLog)")
                                  public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
                                      log.debug("Before {}",System.currentTimeMillis());
                                      Object result=joinPoint.proceed();//执行执行链(其它切面,目标方法-锦)
                                      log.debug("After {}",System.currentTimeMillis());
                                      return result;//目标方法(切入点方法)的执行结果
                                  }
                              }
                              
                              

                              AOP 方式日志记录原理分析

                              我们在基于AOP方式记录用户操作日志时,其底层工作流程如下:
                              在这里插入图片描述
                              说明:当我们在项目中定义了AOP切面以后,系统启动时,会对有@Aspect注解描述的类进行加载分析,基于切入点的描述为目标类型对象,创建代理对象,并在代理对象内部创建一个执行链,这个执行链中包含拦截器(封装了切入点信息),通知(Around,…),目标对象等,我们请求目标对象资源时,会直接按执行链的顺序对资源进行调用。

                              总结(Summary)

                              本章节已经文件上传为例回顾和加强微服务基础知识的掌握和实践。

                              • 0
                                点赞
                              • 1
                                收藏
                                觉得还不错? 一键收藏
                              • 0
                                评论

                              “相关推荐”对你有帮助么?

                              • 非常没帮助
                              • 没帮助
                              • 一般
                              • 有帮助
                              • 非常有帮助
                              提交
                              评论
                              添加红包

                              请填写红包祝福语或标题

                              红包个数最小为10个

                              红包金额最低5元

                              当前余额3.43前往充值 >
                              需支付:10.00
                              成就一亿技术人!
                              领取后你会自动成为博主和红包主的粉丝 规则
                              hope_wisdom
                              发出的红包
                              实付
                              使用余额支付
                              点击重新获取
                              扫码支付
                              钱包余额 0

                              抵扣说明:

                              1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
                              2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

                              余额充值