一天一个知识点 - HTTPSTATUS 100

HTTP 100 Continue 信息型状态响应码表示目前为止一切正常,客户端应该继续请求,如果已完成请求则忽略。

为了让服务器检查请求的首部,客户端必须在发送请求实体前,在初始化请求中发送 Expect: 100-continue 首部并接收 100 Continue 响应状态码。

✅ 使用场景:

当客户端准备发送大请求体 (比如上传大文件),但不确定服务器是否愿意处理,就可以先只发请求头,等服务器同意后再发请求体。

✅ 使用时机:

  • 客户端希望避免不必要地上传大数据(服务器可能会拒绝)
  • 常用于:PUTPOST 请求,带有大量 body 数据

🧠 通信流程(前后端交互)

👉 步骤如下:

  1. 客户端先发送请求头(带 Expect: 100-continue
makefile 复制代码
POST /upload HTTP/1.1  
Host: example.com  
Content-Length: 12345678  
Expect: 100-continue  
Content-Type: application/json
  1. 服务器返回:

    • 100 Continue:你可以继续发送 body
    • 417 Expectation Failed:我不想处理你的请求(拒绝)
  2. 客户端再发送请求体(如文件数据)


💡 前端怎么用?

前端一般不会手动构造 HTTP 请求头,但可以通过一些高级配置方式实现:

在 JavaScript(Fetch API)中 不能直接控制 Expect,但在 Node.js、curl、Postman 等工具中可以。


💻 后端如何支持(以 Node.js / NestJS 为例)

后端需要支持 Expect 请求头 并手动判断是否返回 100 Continue

Node.js 示例:

javascript 复制代码
const http = require('http');

const server = http.createServer((req, res) => {
  if (req.headers['expect'] === '100-continue') {
    // 条件判断通过,通知客户端继续上传
    res.writeContinue();

    req.on('data', chunk => { /* 处理上传数据 */ });
    req.on('end', () => {
      res.writeHead(200);
      res.end('Upload complete');
    });
  } else {
    // 正常请求逻辑
  }
});

server.listen(3000);

✅ 场景总结

场景 是否推荐使用 100 Continue
⬆️ 上传大文件(>10MB) ✅ 非常合适,节省资源
🚀 普通 JSON 请求 ❌ 没必要,用 200 就够
⛔ 拦截非法用户或权限检查 ✅ 如果你在 header 中校验权限

🚀 Tips:

  • 在实际开发中,很多现代框架(如 Express/NestJS)默认不会对 100 Continue 做处理。
  • 要使用它,需要较底层的 HTTP 处理,或特定中间件支持。

🧠 背后原理详解:

🔁 TCP 连接建立:

前端建立一个 TCP连接(就一次),然后:

🥇 第一步:发送请求头(带 Expect: 100-continue

makefile 复制代码
http
POST /upload HTTP/1.1
Host: example.com
Content-Length: 12345678
Expect: 100-continue
Content-Type: application/json

请求体没有发出去 ,浏览器或 HTTP 客户端在"等服务器回应"

(也就是 "我可以上传吗?")


🥈 第二步:服务端判断是否允许上传

服务端返回:

vbnet 复制代码
http
HTTP/1.1 100 Continue

✅ 这时客户端收到信号:"可以继续",才会发出请求体部分


🥉 第三步:客户端发送请求体(如文件数据)

json 复制代码
json
CopyEdit
{
  "file": "base64内容或者字节流......"
}

整个过程都是在同一个 TCP 连接里完成的!只是客户端"分两次 flush 数据流"。


📊 数据流结构(可视化)

css 复制代码
[TCP连接建立]
    ↓
[客户端发送 请求头 + Expect]
    ↓
[服务端返回 100 Continue]
    ↓
[客户端再发 请求体]
    ↓
[服务端处理后返回 200/201/...]

📌 小结

问题 答案
建立了几次连接? ✅ 只建立 一次 TCP 连接
发了几次 HTTP 请求? ✅ 只发了 一次请求(只是分段传输)
请求体什么时候发? ✅ 收到 100 Continue 之后才发送
哪些库自动处理这个过程? curlNode.jsaxios 等高级 HTTP 客户端都支持(浏览器原生 Fetch 不暴露这么低层)

NestJS 自定义监听 100 Continue 请求的中间件/拦截器 示例

🛠️ NestJS 自定义中间件:continue.middleware.ts

typescript 复制代码
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';

@Injectable()
export class ContinueMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: Function) {
    if (req.headers['expect'] === '100-continue') {
      console.log('⚠️ Received Expect: 100-continue');

      // 你可以在这里判断是否愿意接收请求体,比如鉴权、IP、限制类型等
      const allowUpload = true;

      if (allowUpload) {
        // 告诉客户端可以继续上传 body
        res.writeContinue(); // 等同于 res.writeHead(100)
      } else {
        // 拒绝继续上传
        res.status(417).end('Expectation Failed');
        return;
      }
    }

    next();
  }
}

🧩 在模块中启用中间件

在对应模块(如 AppModule)中添加这个中间件:

typescript 复制代码
ts

import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { ContinueMiddleware } from './continue.middleware';
import { UploadController } from './upload.controller';

@Module({
  controllers: [UploadController],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(ContinueMiddleware)
      .forRoutes('upload'); // 仅对 /upload 生效
  }
}

📥 控制器处理请求体(upload.controller.ts

less 复制代码
ts
import { Controller, Post, Req, Res } from '@nestjs/common';
import { Request, Response } from 'express';

@Controller('upload')
export class UploadController {
  @Post()
  handleUpload(@Req() req: Request, @Res() res: Response) {
    let body = '';

    req.on('data', chunk => {
      body += chunk;
    });

    req.on('end', () => {
      console.log('📦 Upload complete:', body.slice(0, 100));
      res.status(200).send('Upload complete');
    });
  }
}

🧪 测试(用 curl

json 复制代码
bash

curl -v -X POST http://localhost:3000/upload \
  -H "Expect: 100-continue" \
  -H "Content-Type: application/json" \
  --data '{"large": "data block..."}'

你会看到类似:

kotlin 复制代码
http

> Expect: 100-continue
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK

📌 总结

功能 实现
检查 Expect: 100-continue 自定义中间件
允许/拒绝请求体上传 res.writeContinue() or 417
处理请求体数据 控制器里监听 req.on('data')

结合 multer 来处理大文件上传,也可以在 100 Continue 响应后再挂载 multer

📦 什么是 multer

multer 是一个 用于 Node.js(Express/NestJS)的中间件库 ,专门用来处理 multipart/form-data 格式 的请求,也就是:

✅ 前端表单中上传文件的场景

比如:

ini 复制代码
html

<form action="/upload" method="POST" enctype="multipart/form-data">
  <input type="file" name="avatar" />
  <button type="submit">上传</button>
</form>

后台接收到的是带有文件的请求,普通 body-parser 解析不了,而 multer 可以自动把这些文件处理成可用对象,你就能像读取 JSON 那样方便操作上传的文件。


🌟 主要功能

功能 示例
处理文件上传 图片、视频、音频、PDF 等
自动保存文件 保存到磁盘指定目录
限制文件大小 防止上传超大文件
限制上传类型 限制只允许图片等 MIME

📦 在 NestJS 中如何使用 multer

NestJS 已经内置了 multer 的支持,我们只需要配合 @UseInterceptors()FileInterceptor() 即可。

🌰 示例:上传单个文件

less 复制代码
ts

import {
  Controller,
  Post,
  UploadedFile,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';

@Controller('upload')
export class UploadController {
  @Post()
  @UseInterceptors(FileInterceptor('avatar'))
  upload(@UploadedFile() file: Express.Multer.File) {
    console.log('收到文件:', file.originalname);
    return { filename: file.filename };
  }
}

🛠️ 存储配置(本地或内存)

python 复制代码
ts

import { diskStorage } from 'multer';

@UseInterceptors(
  FileInterceptor('avatar', {
    storage: diskStorage({
      destination: './uploads',
      filename: (req, file, cb) => {
        const uniqueName = Date.now() + '-' + file.originalname;
        cb(null, uniqueName);
      },
    }),
  }),
)

💡 常见上传字段类型

拦截器 场景
FileInterceptor('field') 单文件上传
FilesInterceptor('field') 多文件(同一字段)上传
AnyFilesInterceptor() 任意字段、任意数量文件
FileFieldsInterceptor([{ name: 'avatar' }, { name: 'cv' }]) 多字段、多文件

🚧 与 100 Continue 配合

当你使用 Expect: 100-continue 控制上传开始时,可以先用你前面写的中间件确认是否允许上传,再交给 multer 来解析上传内容。


⚠️ 注意事项

  • 上传路径必须存在(或使用 fs.mkdir 动态创建)
  • 如果你想存到云服务(如 S3),需要配合 multer-s3
  • 上传接口要设好文件大小限制,防止攻击或误上传大文件

前端上传大文件到你刚才的 NestJS + S3 接口 的完整示例

✅ 方法一:用 axios(推荐,支持进度条)

javascript 复制代码
ts

import axios from 'axios';

const uploadFile = async (file: File) => {
  const formData = new FormData();
  formData.append('file', file);

  const response = await axios.post('http://localhost:3000/upload', formData, {
    headers: {
      'Content-Type': 'multipart/form-data',
      'Expect': '100-continue', // 添加这个头部
    },
    onUploadProgress: (progressEvent) => {
      const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
      console.log(`上传进度:${percent}%`);
    },
  });

  console.log('✅ 上传成功:', response.data);
};

✅ 方法二:原生 fetch(不支持进度)

javascript 复制代码
ts

const uploadFile = async (file: File) => {
  const formData = new FormData();
  formData.append('file', file);

  const response = await fetch('http://localhost:3000/upload', {
    method: 'POST',
    headers: {
      'Expect': '100-continue',
    },
    body: formData,
  });

  const result = await response.json();
  console.log('✅ 上传成功:', result);
};

🧪 HTML 示例:快速测试界面

你可以在一个 HTML 页面中写这样一个上传按钮来测试:

typescript 复制代码
html

<input type="file" id="fileInput" />
<button onclick="upload()">上传</button>
<script>
  async function upload() {
    const file = document.getElementById('fileInput').files[0];
    const formData = new FormData();
    formData.append('file', file);

    const res = await fetch('http://localhost:3000/upload', {
      method: 'POST',
      headers: {
        'Expect': '100-continue',
      },
      body: formData,
    });

    const data = await res.json();
    alert('上传成功:' + data.url);
  }
</script>

🧠 温馨提示:

注意点 说明
CORS 确保后端设置了允许跨域上传(加 @Header('Access-Control-Allow-Origin', '*')
大文件 上传大文件建议设置最大限制,避免卡住服务
文件类型 可在前端或 multer 设置白名单,例如只允许 .zip, .jpg
上传进度 建议用 axiosfetch 更方便控制上传进度
文件夹分类 后端可以根据文件类型或上传时间自动存储到 S3 子目录中

扩展阅读

🧩 你现在的上传(multer + S3)是什么?

这是 一次性上传,也叫"普通表单上传":

  • 前端构造 FormData,整个文件作为一个 file 字段

  • 后端(NestJS)接收整个请求后,用 multer 直接转存到 AWS S3

  • 文件太大会:

    • 客户端上传过程中容易失败
    • 服务端可能超时或内存飙高

🔁 什么是断点上传 / 分片上传?

断点上传是指把一个大文件切成多个小块(chunk)每块单独上传,上传失败的块可以重传。

优点:

普通上传 分片上传
网络异常恢复 ❌ 整个失败重传 ✅ 支持断点续传
大文件支持 🚨 容易卡住 ✅ 稳定
并发 ❌ 一次一个请求 ✅ 多块并发上传
S3 支持 ✅(直接上传) ✅(叫 Multipart Upload)

🧪 举个例子:阿里 OSS / AWS S3 的分片上传流程

  1. 前端把文件分成 5MB 一块
  2. 调后端 API 生成 multipart uploadId
  3. 每个 chunk 上传时携带 uploadIdpartNumber
  4. 所有 chunk 上传完后,再调一个 API 合并文件

🧠 总结对比

上传方式 说明 是否支持断点
你当前的 multer + S3 表单上传(一次性) ❌ 不支持断点续传
分片上传(multipart upload) 每块单独上传 ✅ 支持断点和大文件稳定上传
前端直传 S3 前端自己上传,每块带签名 ✅ 高性能但安全控制更复杂

支持断点续传的分片上传系统 🎬

✅ 功能目标:S3 分片上传系统

模块 功能
🧠 后端(NestJS) - 初始化 multipart upload(生成 UploadId) - 获取每个分片的签名 URL - 完成合并
📦 前端(React/Vue/HTML) - 分割大文件为 chunk - 并发上传每个 chunk - 上传失败可重试 - 上传进度可视化
☁️ 存储 AWS S3,原生支持分片上传

🚧 搭建路线图(分 3 步)

第 1 步:NestJS 后端接口设计

接口 功能
POST /upload/initiate 创建 multipart upload,返回 uploadId
GET /upload/signed-url 获取分片上传用的签名 URL
POST /upload/complete 合并所有分片

👉 这部分我可以帮你全写好,包括 AWS SDK 的配置。


第 2 步:前端分片上传逻辑

步骤 内容
1 选择文件,读取并分片(建议每块 5MB+)
2 发请求拿到 uploadId
3 循环每块,拿到带签名的 PUT URL,上传
4 所有 chunk 上传完后,发 complete 请求
5 显示上传成功链接 or 回调处理

第 3 步:进阶功能(可以选做)

  • ✅ 进度条可视化
  • 🔁 自动断点续传(失败部分重传)
  • 🔒 设置上传限制(如文件类型、大小)
  • 🗂️ 存储到指定子目录 / 用户私有 bucket
  • 🌪️ 上传中断自动恢复(前端保存上传进度)

✅ 项目结构预览(NestJS)

css 复制代码
css
src/
├── upload/
│   ├── upload.controller.ts  ← 所有接口定义
│   ├── upload.service.ts     ← 核心逻辑:生成签名、完成上传
│   └── dto/
│       └── complete-upload.dto.ts
└── main.ts

📦 安装依赖

bash 复制代码
bash
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

🔧 第一步:配置 AWS S3

🔐 .env

ini 复制代码
env
AWS_REGION=ap-southeast-1
AWS_ACCESS_KEY_ID=你的accessKey
AWS_SECRET_ACCESS_KEY=你的secret
AWS_BUCKET=my-upload-bucket

✨ 第二步:NestJS 后端实现

📁 upload/upload.service.ts

typescript 复制代码
ts
import { Injectable } from '@nestjs/common';
import { S3Client, CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class UploadService {
  private s3: S3Client;
  private bucket: string;

  constructor(private configService: ConfigService) {
    this.s3 = new S3Client({
      region: configService.get('AWS_REGION'),
      credentials: {
        accessKeyId: configService.get('AWS_ACCESS_KEY_ID'),
        secretAccessKey: configService.get('AWS_SECRET_ACCESS_KEY'),
      },
    });
    this.bucket = configService.get('AWS_BUCKET');
  }

  async initiateMultipartUpload(filename: string) {
    const command = new CreateMultipartUploadCommand({
      Bucket: this.bucket,
      Key: filename,
    });
    const res = await this.s3.send(command);
    return res.UploadId;
  }

  async generatePresignedUrl(filename: string, partNumber: number, uploadId: string) {
    const command = new UploadPartCommand({
      Bucket: this.bucket,
      Key: filename,
      PartNumber: partNumber,
      UploadId: uploadId,
    });

    const url = await getSignedUrl(this.s3, command, { expiresIn: 3600 });
    return url;
  }

  async completeUpload(filename: string, uploadId: string, parts: { PartNumber: number; ETag: string }[]) {
    const command = new CompleteMultipartUploadCommand({
      Bucket: this.bucket,
      Key: filename,
      UploadId: uploadId,
      MultipartUpload: {
        Parts: parts.sort((a, b) => a.PartNumber - b.PartNumber),
      },
    });

    const res = await this.s3.send(command);
    return res;
  }
}

🎯 upload/upload.controller.ts

less 复制代码
ts
import { Controller, Post, Body, Get, Query } from '@nestjs/common';
import { UploadService } from './upload.service';

@Controller('upload')
export class UploadController {
  constructor(private readonly uploadService: UploadService) {}

  @Post('initiate')
  async initiateUpload(@Body() body: { filename: string }) {
    const uploadId = await this.uploadService.initiateMultipartUpload(body.filename);
    return { uploadId };
  }

  @Get('signed-url')
  async getSignedUrl(
    @Query('filename') filename: string,
    @Query('partNumber') partNumber: number,
    @Query('uploadId') uploadId: string,
  ) {
    const url = await this.uploadService.generatePresignedUrl(filename, partNumber, uploadId);
    return { url };
  }

  @Post('complete')
  async completeUpload(@Body() body: { filename: string; uploadId: string; parts: { ETag: string; PartNumber: number }[] }) {
    const res = await this.uploadService.completeUpload(body.filename, body.uploadId, body.parts);
    return { message: 'Upload complete', url: res.Location };
  }
}

upload.module.ts

python 复制代码
ts
import { Module } from '@nestjs/common';
import { UploadService } from './upload.service';
import { UploadController } from './upload.controller';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [ConfigModule],
  providers: [UploadService],
  controllers: [UploadController],
})
export class UploadModule {}

app.module.ts

python 复制代码
ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { UploadModule } from './upload/upload.module';

@Module({
  imports: [ConfigModule.forRoot({ isGlobal: true }), UploadModule],
})
export class AppModule {}

🚀 接口使用流程图

  1. POST /upload/initiate

    { filename }

    → 返回:uploadId

  2. GET /upload/signed-url?filename=xx&partNumber=1&uploadId=xxx

    → 返回:S3 签名上传链接

  3. 前端用 PUT 把 chunk 上传到这个链接(带上 Content-Type

  4. 所有块上传后
    POST /upload/complete

    { filename, uploadId, parts: [{ PartNumber, ETag }] }

    → 返回合并结果和最终文件 URL

🧩 前端上传逻辑函数(React + Hooks)

  1. 分割文件为多个块
  2. 获取每个分片的签名 URL
  3. 上传分片
  4. 重试失败的分片
  5. 上传完成后调用合并接口

代码结构:

typescript 复制代码
tsx
import React, { useState } from 'react';

const CHUNK_SIZE = 5 * 1024 * 1024;  // 每个分片 5MB
const MAX_RETRIES = 3;  // 最多重试次数

const useUploadFile = () => {
  const [uploading, setUploading] = useState(false);
  const [progress, setProgress] = useState(0);
  const [uploadId, setUploadId] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  const initiateUpload = async (file: File) => {
    try {
      const res = await fetch('http://localhost:3000/upload/initiate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ filename: file.name }),
      });
      const data = await res.json();
      setUploadId(data.uploadId);
    } catch (err) {
      console.error('Error initiating upload:', err);
      setError('Failed to initiate upload.');
    }
  };

  const getSignedUrl = async (partNumber: number, file: File) => {
    try {
      const res = await fetch(
        `http://localhost:3000/upload/signed-url?filename=${file.name}&partNumber=${partNumber}&uploadId=${uploadId}`,
      );
      const data = await res.json();
      return data.url;
    } catch (err) {
      console.error('Error getting signed URL:', err);
      throw new Error('Failed to get signed URL.');
    }
  };

  const uploadChunk = async (chunk: Blob, url: string, retries: number = 0): Promise<boolean> => {
    try {
      const res = await fetch(url, {
        method: 'PUT',
        body: chunk,
        headers: { 'Content-Type': 'application/octet-stream' },
      });
      if (res.ok) return true;
      else throw new Error('Failed to upload chunk.');
    } catch (err) {
      if (retries < MAX_RETRIES) {
        return uploadChunk(chunk, url, retries + 1); // 重试
      }
      return false;
    }
  };

  const completeUpload = async (file: File, parts: { PartNumber: number; ETag: string }[]) => {
    try {
      const res = await fetch('http://localhost:3000/upload/complete', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          filename: file.name,
          uploadId,
          parts,
        }),
      });
      const data = await res.json();
      return data.url; // 返回 S3 文件的最终 URL
    } catch (err) {
      console.error('Error completing upload:', err);
      setError('Failed to complete upload.');
    }
  };

  const uploadFile = async (file: File) => {
    setUploading(true);
    setProgress(0);
    setError(null);

    try {
      // 1. 初始化上传
      await initiateUpload(file);
      if (!uploadId) return;

      const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
      const parts: { PartNumber: number; ETag: string }[] = [];

      // 2. 上传每个分片
      for (let partNumber = 1; partNumber <= totalChunks; partNumber++) {
        const start = (partNumber - 1) * CHUNK_SIZE;
        const end = Math.min(partNumber * CHUNK_SIZE, file.size);
        const chunk = file.slice(start, end);

        // 获取签名 URL
        const signedUrl = await getSignedUrl(partNumber, file);

        // 上传分片
        const success = await uploadChunk(chunk, signedUrl);
        if (!success) {
          throw new Error(`Failed to upload part ${partNumber}`);
        }

        // 记录分片信息(ETag)
        parts.push({ PartNumber: partNumber, ETag: 'dummyETag' }); // AWS S3 会自动生成 ETag,模拟一下
        setProgress(Math.round((partNumber / totalChunks) * 100));
      }

      // 3. 完成上传
      const fileUrl = await completeUpload(file, parts);
      console.log('File uploaded successfully:', fileUrl);
      setUploading(false);
    } catch (err) {
      setUploading(false);
      setError(err.message || 'Unknown error occurred.');
    }
  };

  return { uploadFile, uploading, progress, error };
};

const FileUpload = () => {
  const { uploadFile, uploading, progress, error } = useUploadFile();

  const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files ? event.target.files[0] : null;
    if (file) {
      uploadFile(file);
    }
  };

  return (
    <div>
      <input type="file" onChange={handleFileChange} />
      {uploading && (
        <div>
          <progress value={progress} max={100} />
          <span>{progress}%</span>
        </div>
      )}
      {error && <div style={{ color: 'red' }}>{error}</div>}
    </div>
  );
};

export default FileUpload;

🧠 解释

  1. useUploadFile:这个自定义 Hook 包含了所有上传逻辑,包括:

    • 初始化上传(initiateUpload
    • 获取签名 URL(getSignedUrl
    • 上传分片(uploadChunk
    • 完成上传(completeUpload
  2. uploadFile

    • 将文件分块,每块最大 5MB。
    • 上传每个块,上传完成后更新进度条。
    • 上传完成后,调用合并接口(completeUpload)合并文件。
  3. FileUpload

    • 用于处理用户上传文件的界面。
    • 显示上传进度条。

🧠 注意事项

  • 签名 URL :需要确保后端 GET /upload/signed-url 返回的是有效的上传链接。
  • ETag :这里为了简化,假设返回的 ETag'dummyETag',你可以从 S3 直接获取真实的 ETag
  • 重试逻辑:为了避免上传失败,设置了重试机制(最大重试次数:3次)。
相关推荐
why1514 小时前
腾讯(QQ浏览器)后端开发
开发语言·后端·golang
浪裡遊4 小时前
跨域问题(Cross-Origin Problem)
linux·前端·vue.js·后端·https·sprint
声声codeGrandMaster4 小时前
django之优化分页功能(利用参数共存及封装来实现)
数据库·后端·python·django
呼Lu噜5 小时前
WPF-遵循MVVM框架创建图表的显示【保姆级】
前端·后端·wpf
bing_1585 小时前
为什么选择 Spring Boot? 它是如何简化单个微服务的创建、配置和部署的?
spring boot·后端·微服务
学c真好玩5 小时前
Django创建的应用目录详细解释以及如何操作数据库自动创建表
后端·python·django
Asthenia04125 小时前
GenericObjectPool——重用你的对象
后端
Piper蛋窝5 小时前
Go 1.18 相比 Go 1.17 有哪些值得注意的改动?
后端
excel5 小时前
招幕技术人员
前端·javascript·后端
盖世英雄酱581366 小时前
什么是MCP
后端·程序员