Microservice file upload practice

Keywords: Java

Initial architecture design

  Note: in order to reduce the learning difficulty, only the initial architecture design is made here, and the subsequent evolution will be based on this architecture. For example, we will add Gateway project, authentication project, etc

Project creation and initialization

Refer to the following engineering structure for project creation, such as

Create parent project

  Create a project parent project to manage project dependencies

 

 

Create file service project

Create a project to handle file upload business, for example:

 

 

Create client service project

Create a client project in which some static pages are defined, such as file upload page

 

 

Parent project initialization

Open the pom.xml file of the parent project and add the following dependencies:

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

File resource service implementation

Add project dependency

Add the following dependencies to the SCA resource project:

       <!--Spring Boot Web (service-built-in tomcat)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--Nacos Discovery (Service registration discovery)-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--Nacos Config (Configuration center)-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--Sentinel (Traffic guard-Current limiting and fusing)-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!--Spring Boot monitor-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

Service initialization configuration

Create a bootstrap.yml configuration file in the resources directory of the project (if subsequent configuration information is to be written to the configuration center, the configuration file name must be bootstrap.yml), and add the following contents:

server:
  port: 8881
spring:
  application:
    name: sca-resource
  servlet:
    multipart:
      max-file-size: 100MB #Controls the size of uploaded files
      max-request-size: 110MB #Request data size
  resources: #Define the path to the uploaded resource
    static-locations: file:d:/uploads  #Static resource path (resources originally stored in the resources/static directory can be stored in this directory)
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
jt:   #The configuration here will be read later in some related classes through the @ Value annotation
  resource:
      path: d:/uploads  #The root directory where the design upload file is stored (to be written to the configuration file later)
      host: http://localhost:8881 / # define the access server corresponding to the uploaded file

Build project startup class

In the current project, create a project startup class, for example:

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

After the class is created, start the current project to check whether it can be started successfully and whether there are configuration errors

Controller logic implementation

Define the Controller object that handles the upload request, for example:

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 {
      //When @ Slf4J is added to the class, you don't have to create the following log objects yourself
//    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. Create file storage directory (create by time - yyyy/MM/dd)
         //1.1 get a directory of the current time
         String dateDir = DateTimeFormatter.ofPattern("yyyy/MM/dd")
                 .format(LocalDate.now());
         //1.2 build directory file object
         File uploadFileDir=new File(resourcePath,dateDir);
         if(!uploadFileDir.exists())uploadFileDir.mkdirs();
         //2. Name the document (try not to repeat it)
         //2.1 obtain the suffix of the original document
         String originalFilename=uploadFile.getOriginalFilename();
         String ext = originalFilename.substring(
                 originalFilename.lastIndexOf("."));
         //2.2 build a new file name
         String newFilePrefix=UUID.randomUUID().toString();
         String newFileName=newFilePrefix+ext;
         //3. Start file upload
         //3.1 build a new file object and point to the final address of the actually uploaded file
         File file=new File(uploadFileDir,newFileName);
         //3.2 upload files (write file data to the designated service location)
         uploadFile.transferTo(file);
         String fileRealPath=resourceHost+dateDir+"/"+newFileName;
         log.debug("fileRealPath {}",fileRealPath);
         //Can I write the uploaded file information to the database later?
         return fileRealPath;
     }
}

Cross domain configuration implementation

When accessing the file upload service through the client project, we need to carry out cross domain configuration. There are many schemes in the cross domain configuration of the server. The most common is to carry out cross domain design at the filter level, such as:

package com.jt.files.config;

/**
 * Cross domain configuration (configure based on filter mode, and set the filter priority higher)
 */
@Configuration
public class CorsFilterConfig {
    @Bean
    public FilterRegistrationBean<CorsFilter> filterFilterRegistrationBean(){
        //1. Configure this filter (cross domain settings - url,method)
        UrlBasedCorsConfigurationSource configSource=new UrlBasedCorsConfigurationSource();
        CorsConfiguration config=new CorsConfiguration();
         //Which request headers are allowed to cross domains
        config.addAllowedHeader("*");
        //Which method types are allowed to cross domain get post delete put
        config.addAllowedMethod("*");
        // Which request sources (IP: ports) are allowed to cross domains
        config.addAllowedOrigin("*");
        //Whether to allow carrying cookie s across domains
        config.setAllowCredentials(true);
        //2. Register the filter and set its priority
        configSource.registerCorsConfiguration("/**", config);
        FilterRegistrationBean<CorsFilter> fBean= new FilterRegistrationBean(new CorsFilter(configSource));
        fBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return fBean;
    }
}

Client engineering logic implementation

In this project, our client project is designed based on springboot project. When the project goes online, its static resources can be directly put into a static resource directory

Add dependency

Add web dependency in pom file of SCA resource UI project, for example:

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

Build project startup class

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

Create file upload page

Create a static directory under the resources directory of the project (if this directory already exists, it does not need to be created), and then create a fileupload.html static page in this directory, for example:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>File upload demo</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>Upload file
            <input id="uploadFile" type="file" name="uploadFile">
        </label>
    </div>
    <button type="submit">Upload file</button>
</form>
</body>
<script>
    //Form submission event for jquery code
    function doUpload(){
        //Get all pictures selected by the user (get array)
        let files=document.getElementById("uploadFile").files;
        if(files.length>0){
            //Get the unique picture selected by the user (take it out of the array)
            let file=files[0];
            //Start uploading this picture
            //Since there are many uploaded codes, you do not want to interfere with other codes here, so define a method call
            upload(file);
        }
        //Block form submission effect
        return false;
    };
    // Method of uploading file to server
    function upload(file){
        //Define a form
        let form=new FormData();
        //Add file to form
        form.append("uploadFile",file);
        //Asynchronous commit
        let url="http://localhost:8881/resource/upload/";
        axios.post(url,form)
             .then(function (response){
                 alert("upload ok")
                 console.log(response.data);
             })
             .catch(function (e){//Execute catch code block on failure
                 console.log(e);
         })
    }
</script>
</html>

Start service access test

Step 1: start the nacos service (do service registration and configuration management here)
Step 2: start the SCA resource service, which provides file upload function
Step 3: start the SCA resource UI service, which is a client project and provides access to static resources. All pages are placed in this project
Step 4: open the browser and access the file upload page under SCA resource UI project, for example:

 

Engineering practice of API gateway

summary

API gateway is the entrance for external resources to access the internal resources of the service, so the file upload request should first request the gateway service, and then be forwarded by the gateway service to the specific resource service.

Service invocation architecture

  Project structure design

Create Gateway project and initialization

Step 1: create an SCA resource Gateway project, for example:

 

Step 2: add project dependencies, for example:

 

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

Step 3: create the configuration file bootstrap.xml, and then perform the initial configuration, for example:

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

Step 4: build the project startup class and start the service to check whether it is correct, for example:

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

Gateway cross domain configuration

When we access the gateway based on Ajax technology, we need to conduct cross domain design at the gateway level, for example:

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. Build a cross domain configuration based on url
        UrlBasedCorsConfigurationSource source= new UrlBasedCorsConfigurationSource();
        //2. Cross domain configuration
        CorsConfiguration config=new CorsConfiguration();
        //2.1 allow all IP: ports to cross domain
        config.addAllowedOrigin("*");
        //2.2 allow all request headers to cross domains
        config.addAllowedHeader("*");
        //2.3 allow all request methods to cross domains: get,post
        config.addAllowedMethod("*");
        //2.4 it is allowed to carry valid cookie s across domains
        config.setAllowCredentials(true);
        source.registerCorsConfiguration("/**",config);
        return new CorsWebFilter(source);
    }
}

For cross domain design in Spring Gateway project, in addition to cross domain filter configuration in java code in the gateway project, cross domain configuration can also be directly carried out in the configuration file, for example:

spring:
  cloud:
    gateway:
      globalcors: #Cross domain configuration
        corsConfigurations:
          '[/**]':
            allowedOrigins: "*"
            allowedHeaders: "*"
            allowedMethods: "*"
            allowCredentials: true

Start the project for service access

First open the gateway, the resource server, and the client engineering service (UI), and then modify the url to access the resource server in the fileupload.html file, such as

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

Limit file upload flow on the gateway

Step 1: add dependency in gateway pom file

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

  Step 2: add sentinel configuration in gateway configuration file

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

Step 3: configure jvm startup parameters when the gateway project is started, for example:

-Dcsp.sentinel.app.type=1

  Step 4: first perform an upload, and then design the current limiting rules for the upload

  Step 5: modify the file upload page js to process the current limit results, for example:

 function upload(file){
        //Define a form (form object provided in axios)
        let form=new FormData();
        //Add file to form
        form.append("uploadFile",file);
        //Asynchronous commit (now commit to gateway)
        //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){//Execute catch code block on failure
                //After the current is limited, the status code returned by the server is 429
                if(e.response.status==429){
                    alert("Upload too often");
                }
                console.log("error",e);
            })
    }

Step 6: start the service for file upload test to detect the current limiting effect

AOP mode operation logging

Page description

When the file upload business is implemented, add the operation of logging

Add project dependency

Add AOP dependency in SCA resource project, for example:

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

Create pointcut annotations

Our project aims to enhance the function of the target business, but the system should specify who is the target business. Here we define an annotation, which will be used to describe the target business later.

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 "";
}

Define pointcut methods

Describe the resource controller file upload method in the SCA resources project through the annotation RequiredLog defined above, for example:

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

Define log operation facet

In AOP programming, we will encapsulate the definitions of Pointcut and extended business logic (Around,...) through aspects, such as:

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 {
    //Define pointcuts
    @Pointcut("@annotation(com.jt.resource.annotation.RequiredLog)")
    public void doLog(){}//Icing on the cake (annotation description method)

    //Define extended business logic
    @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();//Execution chain (other aspects, target method - Jin)
        log.debug("After {}",System.currentTimeMillis());
        return result;//Execution result of target method (pointcut method)
    }
}

Analysis of AOP logging principle

When we record user operation logs based on AOP, the underlying workflow is as follows:

  Note: after defining the AOP Aspect in the project, when the system starts, it will load and analyze the classes described by the @ Aspect annotation, create a proxy object based on the description of the pointcut as the target type object, and create an execution chain inside the proxy object, which contains interceptors (encapsulating the pointcut information), notifications (Around,...), When we request the target object resources, we will directly call the resources in the order of the execution chain.
 

Posted by like_duh44 on Mon, 27 Sep 2021 04:51:55 -0700