工程化与框架系列(11)--Serverless实践

Serverless实践 ☁️

Serverless(无服务器)架构是云计算的一种新范式,它让开发者专注于业务逻辑而无需关心服务器运维。本文将详细介绍前端开发中的Serverless实践方案。

Serverless概述 🌟

💡 小知识:Serverless并不是真的没有服务器,而是将服务器管理的职责转移给了云服务提供商,开发者只需要关注业务代码的编写。

为什么选择Serverless

在现代前端开发中,Serverless带来以下优势:

  1. 降低运维成本

    • 无需管理服务器
    • 自动扩缩容
    • 按使用付费
    • 降低维护成本
  2. 提高开发效率

    • 专注业务逻辑
    • 快速部署上线
    • 简化开发流程
    • 减少基础设施代码
  3. 灵活扩展能力

    • 自动伸缩
    • 高可用性
    • 全球部署
    • 按需使用资源
  4. 成本优化

    • 按量计费
    • 无闲置资源
    • 精确计量
    • 成本可控

函数计算实践 ⚡

基于AWS Lambda的实现

// aws-lambda.ts
import { APIGatewayProxyHandler } from 'aws-lambda';
import * as AWS from 'aws-sdk';

const dynamoDB = new AWS.DynamoDB.DocumentClient();

export const createUser: APIGatewayProxyHandler = async (event) => {
    try {
        const requestBody = JSON.parse(event.body || '{}');
        const { username, email } = requestBody;

        const params = {
            TableName: 'Users',
            Item: {
                userId: Date.now().toString(),
                username,
                email,
                createdAt: new Date().toISOString()
            }
        };

        await dynamoDB.put(params).promise();

        return {
            statusCode: 201,
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                message: 'User created successfully',
                user: params.Item
            })
        };
    } catch (error) {
        return {
            statusCode: 500,
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                message: 'Failed to create user',
                error: error.message
            })
        };
    }
};

export const getUser: APIGatewayProxyHandler = async (event) => {
    try {
        const userId = event.pathParameters?.userId;

        const params = {
            TableName: 'Users',
            Key: {
                userId
            }
        };

        const result = await dynamoDB.get(params).promise();

        if (!result.Item) {
            return {
                statusCode: 404,
                body: JSON.stringify({
                    message: 'User not found'
                })
            };
        }

        return {
            statusCode: 200,
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(result.Item)
        };
    } catch (error) {
        return {
            statusCode: 500,
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                message: 'Failed to get user',
                error: error.message
            })
        };
    }
};

基于Vercel的实现

// vercel-api.ts
import { VercelRequest, VercelResponse } from '@vercel/node';
import { connectToDatabase } from '../utils/database';

export default async function handler(
    req: VercelRequest,
    res: VercelResponse
) {
    if (req.method === 'POST') {
        try {
            const { username, email } = req.body;
            const db = await connectToDatabase();
            
            const result = await db.collection('users').insertOne({
                username,
                email,
                createdAt: new Date()
            });

            return res.status(201).json({
                message: 'User created successfully',
                userId: result.insertedId
            });
        } catch (error) {
            return res.status(500).json({
                message: 'Failed to create user',
                error: error.message
            });
        }
    }

    if (req.method === 'GET') {
        try {
            const { userId } = req.query;
            const db = await connectToDatabase();
            
            const user = await db.collection('users').findOne({
                _id: userId
            });

            if (!user) {
                return res.status(404).json({
                    message: 'User not found'
                });
            }

            return res.status(200).json(user);
        } catch (error) {
            return res.status(500).json({
                message: 'Failed to get user',
                error: error.message
            });
        }
    }

    return res.status(405).json({
        message: 'Method not allowed'
    });
}

静态网站部署 🚀

Serverless Framework配置

# serverless.yml
service: my-static-website

provider:
  name: aws
  runtime: nodejs14.x
  stage: ${opt:stage, 'dev'}
  region: ${opt:region, 'us-east-1'}

plugins:
  - serverless-finch
  - serverless-single-page-app-plugin

custom:
  client:
    bucketName: my-website-${self:provider.stage}
    distributionFolder: build
    indexDocument: index.html
    errorDocument: index.html
  spa:
    website: true
    certificate: ${self:custom.domain.certificate}
    dns: true

resources:
  Resources:
    ClientBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:custom.client.bucketName}
        WebsiteConfiguration:
          IndexDocument: ${self:custom.client.indexDocument}
          ErrorDocument: ${self:custom.client.errorDocument}
        
    ClientBucketPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket: !Ref ClientBucket
        PolicyDocument:
          Statement:
            - Effect: Allow
              Principal: '*'
              Action: s3:GetObject
              Resource: !Join ['/', [!GetAtt ClientBucket.Arn, '*']]

自动部署配置

# github-actions-deploy.yml
name: Deploy Website

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v2
      
      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '14'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Build website
        run: npm run build
        
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1
          
      - name: Deploy to S3
        run: |
          aws s3 sync build/ s3://my-website-${GITHUB_REF##*/} \
            --delete \
            --cache-control "max-age=31536000"
          
      - name: Invalidate CloudFront
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
            --paths "/*"

数据存储方案 💾

基于DynamoDB的实现

// dynamodb-service.ts
import { DynamoDB } from 'aws-sdk';
import { DocumentClient } from 'aws-sdk/clients/dynamodb';

export class DynamoDBService {
    private readonly client: DocumentClient;
    private readonly tableName: string;

    constructor(tableName: string) {
        this.client = new DynamoDB.DocumentClient();
        this.tableName = tableName;
    }

    async create<T extends { id: string }>(item: T): Promise<T> {
        const params = {
            TableName: this.tableName,
            Item: {
                ...item,
                createdAt: new Date().toISOString()
            }
        };

        await this.client.put(params).promise();
        return item;
    }

    async get<T>(id: string): Promise<T | null> {
        const params = {
            TableName: this.tableName,
            Key: { id }
        };

        const result = await this.client.get(params).promise();
        return (result.Item as T) || null;
    }

    async update<T extends { id: string }>(
        id: string,
        updates: Partial<T>
    ): Promise<T> {
        const updateExpressions: string[] = [];
        const expressionAttributeNames: Record<string, string> = {};
        const expressionAttributeValues: Record<string, any> = {};

        Object.entries(updates).forEach(([key, value]) => {
            if (key !== 'id') {
                const attributeName = `#${key}`;
                const attributeValue = `:${key}`;
                updateExpressions.push(`${attributeName} = ${attributeValue}`);
                expressionAttributeNames[attributeName] = key;
                expressionAttributeValues[attributeValue] = value;
            }
        });

        const params = {
            TableName: this.tableName,
            Key: { id },
            UpdateExpression: `SET ${updateExpressions.join(', ')}`,
            ExpressionAttributeNames: expressionAttributeNames,
            ExpressionAttributeValues: expressionAttributeValues,
            ReturnValues: 'ALL_NEW'
        };

        const result = await this.client.update(params).promise();
        return result.Attributes as T;
    }

    async delete(id: string): Promise<void> {
        const params = {
            TableName: this.tableName,
            Key: { id }
        };

        await this.client.delete(params).promise();
    }

    async query<T>(
        indexName: string,
        keyCondition: string,
        expressionAttributeValues: Record<string, any>
    ): Promise<T[]> {
        const params = {
            TableName: this.tableName,
            IndexName: indexName,
            KeyConditionExpression: keyCondition,
            ExpressionAttributeValues: expressionAttributeValues
        };

        const result = await this.client.query(params).promise();
        return (result.Items as T[]) || [];
    }
}

// 使用示例
const userService = new DynamoDBService('Users');

// 创建用户
const user = await userService.create({
    id: 'user123',
    name: 'John Doe',
    email: 'john@example.com'
});

// 查询用户
const result = await userService.query(
    'EmailIndex',
    'email = :email',
    { ':email': 'john@example.com' }
);

身份认证实现 🔐

基于Cognito的认证

// auth-service.ts
import { CognitoIdentityServiceProvider } from 'aws-sdk';

export class AuthService {
    private readonly cognito: CognitoIdentityServiceProvider;
    private readonly userPoolId: string;
    private readonly clientId: string;

    constructor(userPoolId: string, clientId: string) {
        this.cognito = new CognitoIdentityServiceProvider();
        this.userPoolId = userPoolId;
        this.clientId = clientId;
    }

    async signUp(
        username: string,
        password: string,
        email: string
    ): Promise<string> {
        const params = {
            ClientId: this.clientId,
            Username: username,
            Password: password,
            UserAttributes: [
                {
                    Name: 'email',
                    Value: email
                }
            ]
        };

        const result = await this.cognito.signUp(params).promise();
        return result.UserSub;
    }

    async confirmSignUp(
        username: string,
        code: string
    ): Promise<void> {
        const params = {
            ClientId: this.clientId,
            Username: username,
            ConfirmationCode: code
        };

        await this.cognito.confirmSignUp(params).promise();
    }

    async signIn(
        username: string,
        password: string
    ): Promise<{
        accessToken: string;
        refreshToken: string;
        idToken: string;
    }> {
        const params = {
            AuthFlow: 'USER_PASSWORD_AUTH',
            ClientId: this.clientId,
            AuthParameters: {
                USERNAME: username,
                PASSWORD: password
            }
        };

        const result = await this.cognito.initiateAuth(params).promise();
        const authResult = result.AuthenticationResult!;

        return {
            accessToken: authResult.AccessToken!,
            refreshToken: authResult.RefreshToken!,
            idToken: authResult.IdToken!
        };
    }

    async refreshToken(
        refreshToken: string
    ): Promise<{
        accessToken: string;
        idToken: string;
    }> {
        const params = {
            AuthFlow: 'REFRESH_TOKEN_AUTH',
            ClientId: this.clientId,
            AuthParameters: {
                REFRESH_TOKEN: refreshToken
            }
        };

        const result = await this.cognito.initiateAuth(params).promise();
        const authResult = result.AuthenticationResult!;

        return {
            accessToken: authResult.AccessToken!,
            idToken: authResult.IdToken!
        };
    }

    async forgotPassword(username: string): Promise<void> {
        const params = {
            ClientId: this.clientId,
            Username: username
        };

        await this.cognito.forgotPassword(params).promise();
    }

    async confirmForgotPassword(
        username: string,
        code: string,
        newPassword: string
    ): Promise<void> {
        const params = {
            ClientId: this.clientId,
            Username: username,
            ConfirmationCode: code,
            Password: newPassword
        };

        await this.cognito.confirmForgotPassword(params).promise();
    }
}

// 使用示例
const authService = new AuthService(
    'us-east-1_xxxxxx',
    'xxxxxxxxxxxxxxxxxx'
);

// 注册用户
const userId = await authService.signUp(
    'johndoe',
    'Password123!',
    'john@example.com'
);

// 登录
const tokens = await authService.signIn(
    'johndoe',
    'Password123!'
);

最佳实践建议 ⭐

开发原则

  1. 函数设计

    • 单一职责
    • 无状态设计
    • 适当超时设置
    • 错误处理完善
  2. 性能优化

    • 冷启动优化
    • 资源复用
    • 并发控制
    • 缓存策略
  3. 安全考虑

    • 最小权限原则
    • 密钥管理
    • 输入验证
    • 日志审计

开发流程建议

  1. 本地开发环境
# 安装Serverless Framework
npm install -g serverless

# 创建新项目
serverless create --template aws-nodejs-typescript
cd my-serverless-project

# 安装依赖
npm install

# 本地测试
serverless offline start
  1. 调试和测试
// 本地调试配置
// serverless.yml
custom:
  serverless-offline:
    httpPort: 3000
    lambdaPort: 3002
    websocketPort: 3001

plugins:
  - serverless-offline
  - serverless-webpack
  - serverless-dotenv-plugin

# 单元测试示例
describe('User API', () => {
    it('should create user', async () => {
        const event = {
            body: JSON.stringify({
                username: 'test',
                email: 'test@example.com'
            })
        };

        const result = await createUser(event as any);
        expect(result.statusCode).toBe(201);
    });
});

结语 📝

Serverless架构为前端开发提供了一种全新的开发模式,它能够显著提高开发效率并降低运维成本。通过本文,我们学习了:

  1. Serverless的基本概念和优势
  2. 函数计算的实践方案
  3. 静态网站的部署策略
  4. 数据存储的实现方式
  5. 身份认证的解决方案

💡 学习建议:

  1. 从简单的API开始实践
  2. 熟悉云服务提供商的产品
  3. 注重安全性和性能优化
  4. 建立完善的监控体系
  5. 保持代码的可维护性

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/979445.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

olmOCR:使用VLM解析PDF

在PDF解析中&#xff0c;目前主流的开源工具包括Minuer、GOT OCR等。主要都是通过飞桨等OCR套件组装的一套pipeline&#xff0c;或者直接通过VLM解析图像。 #一、 olmOCR是使用VLM进行的端到端的PDF文档解析 二、document-anchoring 与上述的不同在于&#xff0c;olmOCR使用…

Android ObjectBox数据库使用与集成指南

ObjectBox其核心特点ObjectBox与 SQLite 和 Realm 的对比Android集成ObjectBox创建ObjectBox实体对象创建ObjectBox操作管理类OBManager在Application初始化ObjectBox插入或更新数据查询数据统计数据分页数据查询删除数据总结今天分享一套Android另一个数据库ObjectBox。Object…

【深入理解JWT】从认证授权到网关安全

最近的项目学习中&#xff0c;在进行登陆模块的用户信息验证这一部分又用到了JWT的一些概念和相关知识&#xff0c;特在此写了这篇文章、方便各位笔者理解JWT相关概念 目录 先来理解JWT是什么&#xff1f; 区分有状态认证和无状态认证 有状态认证 VS 无状态认证 JWT令牌的…

使用write函数

使用open命令打开文件后&#xff0c;要往里面写入数据&#xff0c;使用write命令&#xff0c;把buf中count字节的数据写入fd中 关键是&#xff0c;写文件的时候要在这个文件的哪一个位置去写 假如写得时候&#xff0c;文件为空&#xff0c;指针指向最开始的位置&#xff0c;执…

我的世界1.20.1forge模组开发进阶物品(7)——具有动画、3D立体效果的物品

基础的物品大家都会做了对吧?包括武器的释放技能,这次来点难度,让物品的贴图呈现动画效果和扔出后显示3D立体效果,这个3D立体效果需要先学习blockbench,学习如何制作贴图。 Blockbench Blockbench是一个用于创建和编辑三维模型的免费软件,特别适用于Minecraft模型的设计…

GitHub 语析 - 基于大模型的知识库与知识图谱问答平台

语析 - 基于大模型的知识库与知识图谱问答平台 GitHub 地址&#xff1a;https://github.com/xerrors/Yuxi-Know &#x1f4dd; 项目概述 语析是一个强大的问答平台&#xff0c;结合了大模型 RAG 知识库与知识图谱技术&#xff0c;基于 Llamaindex VueJS FastAPI Neo4j 构…

活在AI原生时代的05后,开始用AI创业

大家好&#xff0c;我是Shelly&#xff0c;一个专注于输出AI工具和科技前沿内容的AI应用教练&#xff0c;体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具&#xff0c;拥抱AI时代的到来。 人工智能&AIGC术语100条 Shelly聊AI-重…

【Maui】系统找不到指定的文件Xamarin.Android.Aapt2.targets

文章目录 前言一、问题描述二、解决方案三、软件开发&#xff08;源码&#xff09;四、项目展示 前言 .NET 多平台应用 UI (.NET MAUI) 是一个跨平台框架&#xff0c;用于使用 C# 和 XAML 创建本机移动和桌面应用。 使用 .NET MAUI&#xff0c;可从单个共享代码库开发可在 And…

2020 年英语(一)考研真题 笔记(更新中)

Section I Use of English&#xff08;完型填空&#xff09; 原题 Directions&#xff1a;Read the following text. Choose the best word (s) for each numbered blank and mark A, B, C or D on the ANSWER SHEET. (10 points) Even if families are less likely to si…

React实现无缝滚动轮播图

实现效果&#xff1a; 由于是演示代码&#xff0c;我是直接写在了App.tsx里面在 文件位置如下&#xff1a; App.tsx代码如下&#xff1a; import { useState, useEffect, useCallback, useRef } from "react"; import { ImageContainer } from "./view/ImageC…

如何修改安全帽/反光衣检测AI边缘计算智能分析网关V4的IP地址?

TSINGSEE青犀推出的智能分析网关V4&#xff0c;是一款集成了BM1684芯片的高性能AI边缘计算智能硬件。其内置的高性能8核ARM A53处理器&#xff0c;主频可高达2.3GHz&#xff0c;INT8峰值算力更是达到了惊人的17.6Tops。此外&#xff0c;该硬件还预装了近40种AI算法模型&#xf…

一周学会Flask3 Python Web开发-Jinja2模板过滤器使用

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 在Jinja2中&#xff0c;过滤器(filter)是一些可以用来修改和过滤变量值的特殊函数&#xff0c;过滤器和变量用一个竖线 | &a…

华为 VRP 系统简介配置SSH,TELNET远程登录

华为 VRP 系统简介&配置SSH/TELNET远程登录 1.华为 VRP 系统概述 1.1 什么是 VRP VRP&#xff08;Versatile Routing Platform 华为数通设备操作系统&#xff09;是华为公司数据通信产品的通用操作系统平台&#xff0c;从低端到核心的全系列路由器、以太网交换机、业务网…

OAK相机的抗震性测试

在工业环境中&#xff0c;双目视觉相机必须具备与工作环境同等的坚固性。鉴于部分客户会将我们的相机应用于恶劣环境&#xff08;例如安装在重型机械上&#xff09;&#xff0c;我们依据EN 60068-2-6:2008标准对相机进行了振动耐受性测试。 测试涉及的相机型号包括&#xff1a…

【jira】用到几张表

jira用到的几张表 测试计划&#xff0c;测试周期&#xff0c;测试用例&#xff0c;问题记录 1. 测试计划 # 记录表&#xff0c;查计划详情 SELECT ID,issuenum,SUMMARY FROM jiraissue where issuenum 22871# 测试计划下&#xff0c;测试周期&#xff0c;查测试周期id&…

Python PDF文件拆分-详解

目录 使用工具 将PDF按页数拆分 将PDF的每一页拆分为单独的文件 将PDF按指定页数拆分 根据页码范围拆分PDF 根据指定内容拆分PDF 将PDF的一页拆分为多页 在日常生活中&#xff0c;我们常常会遇到大型的PDF文件&#xff0c;这些文件可能难以发送、管理和查阅。将PDF拆分成…

机器学习--(随机森林,线性回归)

一、集成学习方法之随机森林 集成学习的基本思想就是将多个分类器组合&#xff0c;从而实现一个预测效果更好的集成分类器。集成算法可以说从一方面验证了中国的一句老话&#xff1a;三个臭皮匠&#xff0c;赛过诸葛亮。集成算法大致可以分为&#xff1a;Bagging&#xff0c;B…

STM32【3】芯片的底层组成概论

关于单片机的组成 单片机的意思是&#xff0c;小小计算电脑&#xff0c;麻雀虽小&#xff0c;五脏俱全&#xff0c;里面包含了CPU&#xff0c;ROM&#xff0c;RAM&#xff0c;各种外设。 CPU地位最高&#xff0c;可以访问ROM和RAM&#xff0c;Flash&#xff0c;GPIO等外设&…

Elasticsearch:过滤 HNSW 搜索,快速模式

作者&#xff1a;来自 Elastic Benjamin Trent 通过我们的 ACORN-1 算法实现&#xff0c;探索我们对 Apache Lucene 中的 HNSW 向量搜索所做的改进。 多年来&#xff0c;Apache Lucene 和 Elasticsearch 一直支持使用 kNN 查询的过滤搜索&#xff0c;允许用户检索符合指定元数据…

golang安装(1.23.6)

1&#xff0e;切换到安装目录 cd /usr/local 2&#xff0e;下载安装包 wget https://go.dev/dl/go1.23.6.linux-amd64.tar.gz 3&#xff0e;解压安装包 sudo tar -C /usr/local -xzf go1.23.6.linux-amd64.tar.gz 4&#xff0e;配置环境变量 vi /etc/profile export PATH$…