JavaScript系列(94)--Serverless实践

news/2025/3/1 12:24:59/

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-websiteprovider:name: awsruntime: nodejs14.xstage: ${opt:stage, 'dev'}region: ${opt:region, 'us-east-1'}plugins:- serverless-finch- serverless-single-page-app-plugincustom:client:bucketName: my-website-${self:provider.stage}distributionFolder: buildindexDocument: index.htmlerrorDocument: index.htmlspa:website: truecertificate: ${self:custom.domain.certificate}dns: trueresources:Resources:ClientBucket:Type: AWS::S3::BucketProperties:BucketName: ${self:custom.client.bucketName}WebsiteConfiguration:IndexDocument: ${self:custom.client.indexDocument}ErrorDocument: ${self:custom.client.errorDocument}ClientBucketPolicy:Type: AWS::S3::BucketPolicyProperties:Bucket: !Ref ClientBucketPolicyDocument:Statement:- Effect: AllowPrincipal: '*'Action: s3:GetObjectResource: !Join ['/', [!GetAtt ClientBucket.Arn, '*']]

自动部署配置

# github-actions-deploy.yml
name: Deploy Websiteon:push:branches:- mainjobs:deploy:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v2- name: Setup Node.jsuses: actions/setup-node@v2with:node-version: '14'- name: Install dependenciesrun: npm ci- name: Build websiterun: npm run build- name: Configure AWS credentialsuses: aws-actions/configure-aws-credentials@v1with: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 S3run: |aws s3 sync build/ s3://my-website-${GITHUB_REF##*/} \--delete \--cache-control "max-age=31536000"- name: Invalidate CloudFrontrun: |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: 3000lambdaPort: 3002websocketPort: 3001plugins:- 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. 保持代码的可维护性

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

终身学习,共同成长。

咱们下一期见

💻


http://www.ppmy.cn/news/1575765.html

相关文章

【Git】解决 GitLab “Ensure URL is HTTPs“ 拉取的问题

问题背景 在使用 Git 拉取代码时&#xff0c;您可能遇到过以下错误信息&#xff1a; git pull origin dev_2.4.6 fatal: Unencrypted HTTP is not supported for GitHub. Ensure the repository remote URL is using HTTPS.这个错误提示实际上反映了一个重要的安全更新&#…

【多线程-第三天-NSOperation的练习-tableView异步下载网络图片-重构代码-自定义cell Objective-C语言】

一、到现在为止,我们解决了很多很多问题 1.现在基本的问题我们已经解决完了,解决完成之后,代码有点儿多,而且刚刚我们也说过,我在这儿返回cell的时候, 这个方法,代码是不是特别多,两屏幕的代码,关键是这段代码做了两件事情,一件事情是返回cell,还有一件事情是,下载…

Python----PyQt开发(PyQt高级:手搓一个音乐播放器)

一、效果展示 二、设计PyQt界面 本次ui界面设置用到了水平和垂直布局 2.1、设置ui窗口显示大小与位置 self.setWindowTitle(音乐播放器) # 设置窗口标题self.setGeometry(800, 300, 800, 800) # 设置窗口大小和位置 2.2、创建显示歌曲列表控件 # 创建显示歌曲列表的控件 …

Deepseek开源周第四天:从 DualPipe 到 EPLB

Deepseek开源周第四天&#xff1a;从 DualPipe 到 EPLB 前言 上周deepseek宣布&#xff0c;将在本周陆续发布五个开源项目&#xff0c;这些库已经在生产环境中经过了记录、部署和实战测试。 今天是deepseek开源周的第四天&#xff0c;deepseek发布了三个开源项目&#xff0c;…

网络安全清单

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 移除(Deprovisioning) 移除&#xff08;Deprovisioning&#xff09;是一个除去现存用户帐户的过程&#xff0c;其包括用户帐户登记和设备失效。 分布式编码规则…

计算机毕业设计Python+DeepSeek-R1大模型期货价格预测分析 期货价格数据分析可视化预测系 统 量化交易大数据 机器学习 深度学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

【计网】物理层

物理层 2.1 物理层的基本概念2.2 物理层下面的传输媒体2.2.1 引导型2.2.2 非引导型 2.3 传输方式2.3.1 串行/并行2.3.2 同步/异步2.3.3 单工/半双工/全双工 2.4 编码与调制2.4.1 常用编码2.4.2 基本调制方法 2.5 信道的极限容量2.5.1 奈氏准则2.5.2 香农公式 2.1 物理层的基本概…

力扣 划分字母区间

贪心算法&#xff0c;存状态&#xff0c;合并区间。 题目 同一字母最多出现在一个片段中&#xff0c;因此要找到相同字母的上界跟下界。由于是对字符串进行划分&#xff0c;在一个片段内&#xff0c;从前往后遍历&#xff0c;找到每个字母的最后一个下标即是可能的划分点了&am…