2020-01-03

Application.java

package com.caoheyang;

import com.caoheyang.config.CorsConfig;
import com.caoheyang.controller.DataController;
import com.caoheyang.controller.HomeController;
import com.caoheyang.service.ShortUrlService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;


/**
 * 系统启动类
 *
 * @author CaoHeYang
 * <p>
 * 1、We use direct @Import instead of @ComponentScan to speed up cold starts
 * 2、ComponentScan(basePackages = "com.caoheyang.controller")
 * </p>
 * @date 20191221
 */
@SpringBootApplication
@Import({HomeController.class, DataController.class, ShortUrlService.class, CorsConfig.class})
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

DataController.java

package com.caoheyang.controller;

import com.caoheyang.model.request.GenerateShortUrlDomain;
import com.caoheyang.model.response.ShortUrlDomain;
import com.caoheyang.service.ShortUrlService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;


/**
 * Short主数据处理
 *
 * @author CaoHeYang
 * @date 20191221
 */
@RestController
@EnableWebMvc
public class DataController {

    private final static Logger logger = LogManager.getLogger(DataController.class);

    @Autowired
    private ShortUrlService shortUrlService;


    /**
     * @param requestBody
     * @return
     */
    @PostMapping(path = "/data/shorten")
    public ShortUrlDomain generateShortUrl(@RequestBody GenerateShortUrlDomain requestBody) throws UnsupportedEncodingException {


        logger.debug("解码前的longUrl:" + requestBody.getLongUrl());

        requestBody.setLongUrl(URLDecoder.decode(requestBody.getLongUrl(), "UTF-8"));
        logger.debug("解码后的longUrl:" + requestBody.getLongUrl());

        //生成短链URL
        String shortUrl = shortUrlService.generateShortUrl(requestBody);
        logger.debug("生成的短链shortUrl:" + shortUrl);

        ShortUrlDomain response = new ShortUrlDomain();
        response.setShortUrl(shortUrl);

        return response;
    }

}

HomeController.java

package com.caoheyang.controller;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.caoheyang.model.dynamo.TinyUrlItem;
import com.caoheyang.service.ShortUrlService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Shorten重定向处理
 *
 * @author CaoHeYang
 * @date 20191221
 */
@RestController
@EnableWebMvc
public class HomeController {

    private final static Logger logger = LogManager.getLogger(HomeController.class);

    @Autowired
    private ShortUrlService shortUrlService;

    /**
     * 根据加密之后的短链编码永久重定向到长链地址
     *
     * @param resource 加密之后的短链编码
     * @param response HttpServletResponse重定向类
     * @throws IOException
     */
    @GetMapping(path = "/{resource}")
    public void redirect(@PathVariable("resource") String resource, HttpServletResponse response) throws IOException {
        //记录次数,这是主要的商业模式
        logger.debug("从请求中获取到的长链resource:" + resource);

        String redirectUrl = shortUrlService.getRedirectUrl(resource);
        logger.debug("重定向地址:" + redirectUrl);

        //短链不存在,重定向到404页面
        response.setStatus(301);
        response.sendRedirect(redirectUrl);
    }
}

ShortUrlService.java

package com.caoheyang.service;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.caoheyang.model.dynamo.TinyUrlItem;
import com.caoheyang.model.request.GenerateShortUrlDomain;
import com.caoheyang.util.TinyUrlUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * ShortURL业务逻辑处理
 *
 * @author CaoHeYang
 * @date 20191221
 */
@Service
public class ShortUrlService {

    private final static Logger logger = LogManager.getLogger(ShortUrlService.class);

    @Value("${tiny.url.website}")
    private String tinyUrlWebsite;

    @Value("${tiny.url.api}")
    private String tinyUrlApi;


    private DynamoDBMapper mapper;

    /**
     * 无参构造函数。初始化DynamoDB链接
     */
    public ShortUrlService() {
        AmazonDynamoDB amazonDynamoDB = AmazonDynamoDBClientBuilder.defaultClient();
        mapper = new DynamoDBMapper(amazonDynamoDB);
    }

    /**
     * 根据长链URL生成短链URL
     *
     * @param model 长链URL领域模型
     * @return 短链地址
     */
    public String generateShortUrl(GenerateShortUrlDomain model) {

        String shortSourceId = TinyUrlUtil.shortUrl(model.getLongUrl());
        logger.debug("生成的短链shortSourceId:" + shortSourceId);

        TinyUrlItem tinyUrlItem = new TinyUrlItem();

        tinyUrlItem.setLongUrl(model.getLongUrl());
        tinyUrlItem.setShortSourceId(shortSourceId);

        //TODO 查询当前shortSourceId+longUrl是否存在
        //存入DDB
        mapper.save(tinyUrlItem);
        logger.debug("存入DynamoDB成功");

        return tinyUrlApi + '/' + shortSourceId;
    }

    /**
     * 根据加密之后的短链编码永久重定向到长链地址
     *
     * @param resource 加密之后的短链编码
     * @return 重定向地址
     */
    @GetMapping(path = "/{resource}")
    public String getRedirectUrl(String resource) {
        //记录次数,这是主要的商业模式
        TinyUrlItem tinyUrlItem = mapper.load(TinyUrlItem.class, resource);
        //查询到数据重定向到长链,否则重定向到404
        return tinyUrlItem != null ? tinyUrlItem.getLongUrl() : tinyUrlWebsite + "/404.html";
    }
}

TinyUrlItem.java

package com.caoheyang.model.dynamo;


import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;


/**
 * DynamoD TinyUrl_Relation 表的记录项
 *
 * @author CaoHeYang
 * @date 20191221
 */
@DynamoDBTable(tableName = "TinyUrl_Relation")
public class TinyUrlItem {

    private String shortSourceId;
    private String longUrl;

    @DynamoDBHashKey(attributeName = "shortSourceId")
    public String getShortSourceId() {
        return shortSourceId;
    }

    public void setShortSourceId(String shortSourceId) {
        this.shortSourceId = shortSourceId;
    }

    @DynamoDBAttribute(attributeName = "longUrl")
    public String getLongUrl() {
        return longUrl;
    }

    public void setLongUrl(String longUrl) {
        this.longUrl = longUrl;
    }

}

GenerateShortUrlDomain.java

package com.caoheyang.model.request;


/**
 * 生成短链加密编码的入参领域模型
 *
 * @author CaoHeYang
 * @date 20191221
 */
public class GenerateShortUrlDomain {

    //长链
    private String longUrl;

    /**
     * 获取长链
     *
     * @return 长链
     */
    public String getLongUrl() {
        return longUrl;
    }

    /**
     * 设置长链
     *
     * @param longUrl 长链
     */
    public void setLongUrl(String longUrl) {
        this.longUrl = longUrl;
    }

}

ShortUrlDomain.java

package com.caoheyang.model.response;


/**
 * 生成短链加密编码返回给客户端的领域模型
 *
 * @author CaoHeYang
 * @date 20191221
 */
public class ShortUrlDomain {

    //加密之后的短链编码
    private String shortUrl;

    /**
     * 加密之后的短链编码
     *
     * @return
     */
    public String getShortUrl() {
        return shortUrl;
    }

    /**
     * 加密之后的短链编码
     *
     * @param shortUrl 短链地址
     */
    public void setShortUrl(String shortUrl) {
        this.shortUrl = shortUrl;
    }
}

Md5Util.java

package com.caoheyang.util;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;


/**
 * MD5加密帮助类
 *
 * @author CaoHeYang
 * @date 20191221
 */
public class Md5Util {
    /**
     * 对传入的字符串进行MD5加密
     *
     * @param source 需要进行MD5加密的字符串 TODO Exception处理
     * @return 加密完成之后的32位MD5字符串
     */
    public static String getMd5(String source) {
        try {
            // 生成一个MD5加密计算摘要
            MessageDigest md = MessageDigest.getInstance("MD5");
            // 计算md5函数
            md.update(source.getBytes());
            // digest()最后确定返回md5 hash值,返回值为8为字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符
            // BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值
            return new BigInteger(1, md.digest()).toString(16);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5加密出现错误");
        }
    }
}

TinyUrlUtil.java

package com.caoheyang.util;

/**
 * 短链处理工具类
 *
 * @author CaoHeYang
 * @date 20191221
 */
public class TinyUrlUtil {

    /**
     * 对长链进行加密的得到6位短链编码
     *
     * @param url 长链
     * @return 短链编码
     */
    public static String shortUrl(String url) {
        //为防止重复,定义MD5加密的盐
        String key = "@(IfxHG#$";
        // 要使用生成 URL 的字符
        String[] chars = new String[]{"a", "b", "c", "d", "e", "f", "g", "h",
                "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
                "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
                "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
                "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
                "U", "V", "W", "X", "Y", "Z"

        };
        // 对传入网址进行 MD5 加密,并截取前8位,用作计算tinyUrl resourceId的种子
        String sourceUrlInfo = Md5Util.getMd5(url + key).substring(0, 8);
        // 这里需要使用 long 型来转换,因为 Inteper .parseInt() 只能处理 31 位 , 首位为符号位 , 如果不用 long ,则会越界
        long lHexLong = 0x3FFFFFFF & Long.parseLong(sourceUrlInfo, 16);
        String tinyUrl = "";
        for (int j = 0; j < 6; j++) {
            // 把得到的值与 0x0000003D 进行位与运算,取得字符数组 chars 索引
            long index = 0x0000003D & lHexLong;
            // 把取得的字符相加
            tinyUrl += chars[(int) index];
            // 每次循环按位右移 5 位
            lHexLong = lHexLong >> 5;
        }
        return tinyUrl;
    }
}

CorsConfig.java

package com.caoheyang.config;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
 * 服务器端的跨域配置类
 *
 * @author CaoHeYang
 * @date 20191221
 */
@Configuration
public class CorsConfig {

    private final Logger logger = LogManager.getLogger(this.getClass());

    /**
     * 服务器端的允许跨域设置
     * 当不存在 {@link org.springframework.web.filter.CorsFilter}的Bean时,激活用户自定义跨域配置
     *
     * @return 跨域请求bean
     */
    @Bean
    @ConditionalOnMissingBean(CorsFilter.class)
    public CorsFilter corsFilter(@Value("${tiny.url.website}") String tinyUrlWebsite) {

        logger.debug("Cors SET");
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        // 允许cookies跨域
        config.setAllowCredentials(true);
        // 允许向该服务器提交请求的URI,*表示全部允许。。这里尽量限制来源域,比如http://xxxx:8080 ,以降低安全风险。。
        config.addAllowedOrigin(tinyUrlWebsite);
        logger.debug(String.format("AllowedOrigin=%s", tinyUrlWebsite));
        // 允许访问的头信息,*表示全部
        config.addAllowedHeader("Content-Type");
        config.addAllowedHeader("X-Amz-Date");
        config.addAllowedHeader("Authorization");
        config.addAllowedHeader("X-Api-Key");
        config.addAllowedHeader("X-Amz-Security-Token");

        logger.debug(String.format("AllowedHeader=%s", config.getAllowedHeaders()));
        // 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
        config.setMaxAge(18000L);
        // 允许提交请求的方法,*表示全部允许,也可以单独设置GET、PUT等
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        logger.debug("Cors SET End");
        return new CorsFilter(source);
    }

}

StreamLambdaHandler.java

package com.caoheyang.serverless;


import com.amazonaws.serverless.exceptions.ContainerInitializationException;
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler;
import com.amazonaws.serverless.proxy.spring.SpringBootProxyHandlerBuilder;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import com.caoheyang.Application;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.Instant;

/**
 * AWS Lambda 集成代理请求处理类
 *
 * @author CaoHeYang
 * @date 20191221
 */
public class StreamLambdaHandler implements RequestStreamHandler {

    private static final Logger logger = LogManager.getLogger(StreamLambdaHandler.class);

    private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;

    static {
        try {
            handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class);
            logger.warn("For applications that take longer than 10 seconds to start, use the async builder");
            // For applications that take longer than 10 seconds to start, use the async builder:
            long startTime = Instant.now().toEpochMilli();
            handler = new SpringBootProxyHandlerBuilder()
                    .defaultProxy()
                    .asyncInit(startTime)
                    .springBootApplication(Application.class)
                    .buildAndInitialize();
        } catch (ContainerInitializationException e) {
            // if we fail here. We re-throw the exception to force another cold start
            e.printStackTrace();
            throw new RuntimeException("Could not initialize Spring Boot application", e);
        }
    }

    @Override
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
            throws IOException {
        handler.proxyStream(inputStream, outputStream, context);
    }
}

Jenkinsfile

@Library('shared-pipeline-library') _
JavaLambdaCI {
  project = 'tiny-url'
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.caoheyang</groupId>
    <artifactId>tiny-url</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Serverless Spring Boot 2 API</name>
    <url>https://github.com/awslabs/aws-serverless-java-container</url>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
    </parent>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.amazonaws.serverless</groupId>
            <artifactId>aws-serverless-java-container-springboot2</artifactId>
            <version>1.4</version>
        </dependency>

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

        <dependency>
            <artifactId>aws-java-sdk-dynamodb</artifactId>
            <groupId>com.amazonaws</groupId>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-easymock</artifactId>
            <version>1.7.4</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.6</version>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.amazonaws</groupId>
                <artifactId>aws-java-sdk-bom</artifactId>
                <version>1.11.386</version>
                <type>pom</type>
                <scope>import</scope></dependency>
        </dependencies>
    </dependencyManagement>
    <profiles>
        <profile>
            <id>shaded-jar</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-shade-plugin</artifactId>
                        <version>2.3</version>
                        <configuration>
                            <createDependencyReducedPom>false</createDependencyReducedPom>
                        </configuration>
                        <executions>
                            <execution>
                                <phase>package</phase>
                                <goals>
                                    <goal>shade</goal>
                                </goals>
                                <configuration>
                                    <artifactSet>
                                        <excludes>
                                            <exclude>org.apache.tomcat.embed:*</exclude>
                                        </excludes>
                                    </artifactSet>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
        <profile>
            <id>assembly-zip</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <build>
                <plugins>
                    <!-- don't build a jar, we'll use the classes dir -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-jar-plugin</artifactId>
                        <version>3.1.1</version>
                        <executions>
                            <execution>
                                <id>default-jar</id>
                                <phase>none</phase>
                            </execution>
                        </executions>
                        <configuration>
                            <finalName>${project.artifactId}</finalName>
                        </configuration>
                    </plugin>
                    <!-- select and copy only runtime dependencies to a temporary lib folder -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-dependency-plugin</artifactId>
                        <version>3.1.1</version>
                        <executions>
                            <execution>
                                <id>copy-dependencies</id>
                                <phase>package</phase>
                                <goals>
                                    <goal>copy-dependencies</goal>
                                </goals>
                                <configuration>
                                    <outputDirectory>${project.build.directory}${file.separator}lib</outputDirectory>
                                    <includeScope>runtime</includeScope>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-assembly-plugin</artifactId>
                        <version>3.1.0</version>
                        <executions>
                            <execution>
                                <id>zip-assembly</id>
                                <phase>package</phase>
                                <goals>
                                    <goal>single</goal>
                                </goals>
                                <configuration>
                                    <finalName>${project.artifactId}</finalName>
                                    <descriptors>
                                        <descriptor>src${file.separator}assembly${file.separator}bin.xml</descriptor>
                                    </descriptors>
                                    <attach>false</attach>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>

swagger.yaml

openapi: "3.0.1"
info:
  title: "TinyUrlGW"
  version: "2019-12-27T01:21:13Z"
servers:
  - url: "https://fpzox03era.execute-api.us-east-1.amazonaws.com/{basePath}"
    variables:
      basePath:
        default: "/beta"
paths:
  /{resource}:
    options:
      parameters:
        - name: "resource"
          in: "path"
          required: true
          schema:
            type: "string"
            description: "短链编码"
      responses:
        301:
          description: "301 response"
          headers:
            Access-Control-Allow-Origin:
              schema:
                type: "string"
            Access-Control-Allow-Methods:
              schema:
                type: "string"
            Access-Control-Allow-Headers:
              schema:
                type: "string"
          content: {}
      x-amazon-apigateway-integration:
        uri: "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:444668010846:function:tiny-url-lambda/invocations"
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        contentHandling: "CONVERT_TO_TEXT"
        type: "aws_proxy"
    x-amazon-apigateway-any-method:
      parameters:
        - name: "resource"
          in: "path"
          required: true
          schema:
            type: "string"
            description: "短链编码"
      responses:
        301:
          description: "301 response"
          content: {}
      x-amazon-apigateway-integration:
        uri: "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:444668010846:function:tiny-url-lambda/invocations"
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        contentHandling: "CONVERT_TO_TEXT"
        type: "aws_proxy"
  /data/shorten:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GenerateShortUrlDomain"
        required: true
      responses:
        200:
          description: "200 response"
          headers:
            Access-Control-Allow-Origin:
              schema:
                type: "string"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ShortUrlDomain"
      x-amazon-apigateway-integration:
        uri: "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:444668010846:function:tiny-url-lambda/invocations"
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        contentHandling: "CONVERT_TO_TEXT"
        type: "aws_proxy"
    options:
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GenerateShortUrlDomain"
        required: true
      responses:
        200:
          description: "200 response"
          headers:
            Access-Control-Allow-Origin:
              schema:
                type: "string"
            Access-Control-Allow-Methods:
              schema:
                type: "string"
            Access-Control-Allow-Headers:
              schema:
                type: "string"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ShortUrlDomain"
      x-amazon-apigateway-integration:
        uri: "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:444668010846:function:tiny-url-lambda/invocations"
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        httpMethod: "POST"
        contentHandling: "CONVERT_TO_TEXT"
        type: "aws_proxy"
components:
  schemas:
    ShortUrlDomain:
      type: "object"
      properties:
        shortUrl:
          type: "string"
          description: "短链URL"
    GenerateShortUrlDomain:
      type: "object"
      properties:
        longUrl:
          type: "string"
          description: "长链URL"
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容

  • Java复习 Java的基础知识 1.变量和数据类型以及类型的转换 基本数据类型 :byte s...
    友人A_b238阅读 346评论 0 0
  • 异常(Exception)就是java程序运行过程中出现的错误,程序编译通过并不代表着运行时不会出错 异常分类: ...
    胡萝卜的侠客梦all阅读 265评论 0 0
  • 2020.1.2今日感悟:过去的已经过去了,就让它随风远去;活在当下,那才是真正的你;争取明天会更好,那才是你该实行的。
    我命由我不由天CYZ阅读 40评论 0 0
  • 我们走了很长的一段成长路, 听了、看了那么多道理, 也承办了一段的路途, 为何还是没有盘旋上去, 最后还是回到自我...
    尚慧度阅读 230评论 0 3
  • 什么是单页面应用(SPA)? 单页面应用(SPA)是指用户在浏览器加载单一的HTML页面,后续请求都无需再离开此页...
    纹小艾阅读 342评论 0 2