怎么利用Node进行图片压缩

怎么利用node进行图片压缩?下面本篇文章以png图片为例给大家介绍一下进行图片压缩的方法,希望对大家有所帮助!

怎么利用Node进行图片压缩

最近要搞图像处理服务,其中一个是要实现图片压缩功能。以前前端开发的时候只要利用canvas现成的API处理下就能实现,后端可能也有现成的API但我并不知道。仔细想想,我从来没有详细了解过图片压缩原理,那刚好趁这次去调研学习下,所以有了这篇文章来记录。老样子,如有不对的地方,DDDD(带带弟弟)。

我们先把图片上传到后端,看看后端接收了什么样的参数。这里后端我用的是Node.js(Nest),图片我以PNG图片为例。

接口和参数打印如下:

@Post('/compression')
@UseInterceptors(FileInterceptor('file'))
async imageCompression(@UploadedFile() file: Express.Multer.File) {

return {
file
}
}

怎么利用Node进行图片压缩

要进行压缩,我们就需要拿到图像数据。可以看到,唯一能藏匿图像数据的就是这串buffer。那这串buffer描述了什么,就需要先弄清什么是PNG。【相关教程推荐:nodejs视频教程、编程教学】

PNG

这里是PNG的WIKI地址。

阅读之后,我了解到PNG是由一个8 byte的文件头加上多个的块(chunk)组成。示意图如下:

怎么利用Node进行图片压缩

其中:

文件头是由一个被称为magic number的组成。值为 89 50 4e 47 0d 0a 1a 0a(16进制)。它标记了这串数据是PNG格式。

块分为两种,一种叫关键块(Critical chunks),一种叫辅助块(Ancillary chunks)。关键块是必不可少的,没有关键块,解码器将不能正确识别并展示图片。辅助块是可选的,部分软件在处理图片之后就有可能携带辅助块。每个块都是四部分组成:4 byte 描述这个块的内容有多长,4 byte 描述这个块的类型是什么,n byte 描述块的内容(n 就是前面4 byte 值的大小,也就是说,一个块最大长度为28*4),4 byte CRC校验检查块的数据,标记着一个块的结束。其中,块类型的4 byte 的值为4个acsii码,第一个字母大写表示是关键块小写表示是辅助块;第二个字母大写表示是公有小写表示是私有;第三个字母必须是大写,用于PNG后续的扩展;第四个字母表示该块不识别时,能否安全复制,大写表示未修改关键块时才能安全复制,小写表示都能安全复制。PNG官方提供很多定义的块类型,这里只需要知道关键块的类型即可,分别是IHDR,PLTE,IDAT,IEND。

IHDR

PNG要求第一个块必须是IHDR。IHDR的块内容是固定的13 byte,包含了图片的以下信息:

宽度 width (4 byte) & 高度 height (4 byte)

位深 bit depth (1 byte,值为1,2,4,8或者16) & 颜色类型 color type (1 byte,值为0,2,3,4或者6)

压缩方法 compression method (1 byte,值为0) & 过滤方式 filter method (1 byte,值为0)

交错方式 interlace method (1 byte,值为0或者1)

宽度和高度很容易理解,剩下的几个好像都很陌生,接下来我将进行说明。

在说明位深之前,我们先来看颜色类型,颜色类型有5种值:

0 表示灰度(grayscale)它只有一个通道(channel),看成rgb的话,可以理解它的三色通道值是相等的,所以不需要多余两个通道表示。

2 表示真实色彩(rgb)它有三个通道,分别是R(红色),G(绿色),B(蓝色)。

3 表示颜色索引(indexed)它也只有一个通道,表示颜色的索引值。该类型往往配备一组颜色列表,具体的颜色是根据索引值和颜色列表查询得到的。

4 表示灰度和alpha 它有两个通道,除了灰度的通道外,多了一个alpha通道,可以控制透明度。

6 表示真实色彩和alpha 它有四个通道。

之所以要说到通道,是因为它和这里的位深有关。位深的值就定义了每个通道所占的位数(bit)。位深跟颜色类型组合,就能知道图片的颜色格式类型和每个像素所占的内存大小。PNG官方支持的组合如下表:

2023-03-17_180115.png

过滤和压缩是因为PNG中存储的不是图像的原始数据,而是处理后的数据,这也是为什么PNG图片所占内存较小的原因。PNG使用了两步进行了图片数据的压缩转换。

第一步,过滤。过滤的目的是为了让原始图片数据经过该规则后,能进行更大的压缩比。举个例子,如果有一张渐变图片,从左往右,颜色依次为[#000000, #000001, #000002, …, #ffffff],那么我们就可以约定一条规则,右边的像素总是和它前一个左边的像素进行比较,那么处理完的数据就变成了[1, 1, 1, …, 1],这样是不是就能进行更好的压缩。PNG目前只有一种过滤方式,就是基于相邻像素作为预测值,用当前像素减去预测值。过滤的类型一共有五种,(目前我还不知道这个类型值在哪里存储,有可能在IDAT里,找到了再来删除这条括号里的已确定该类型值储存在IDAT数据中)如下表所示:

Type byte Filter name Predicted value

0None不做任何处理1Sub左侧相邻像素2Up上方相邻像素3AverageMath.floor((左侧相邻像素 + 上方相邻像素) / 2)4Paeth取(左侧相邻像素 + 上方相邻像素 – 左上方像素)最接近的值

第二步,压缩。PNG也只有一种压缩算法,使用的是DEFLATE算法。这里不细说,具体看下面的章节。

交错方式,有两种值。0表示不处理,1表示使用Adam7 算法进行处理。我没有去详细了解该算法,简单来说,当值为0时,图片需要所有数据都加载完毕时,图片才会显示。而值为1时,Adam7会把图片划分多个区域,每个区域逐级加载,显示效果会有所优化,但通常会降低压缩效率。加载过程可以看下面这张gif图。

怎么利用Node进行图片压缩

PLTE

PLTE的块内容为一组颜色列表,当颜色类型为颜色索引时需要配置。值得注意的是,颜色列表中的颜色一定是每个通道8bit,每个像素24bit的真实色彩列表。列表的长度,可以比位深约定的少,但不能多。比如位深是2,那么22,最多4种颜色,列表长度可以为3,但不能为5。

IDAT

IDAT的块内容是图片原始数据经过PNG压缩转换后的数据,它可能有多个重复的块,但必须是连续的,并且只有当上一个块填充满时,才会有下一个块。

IEND

IEND的块内容为0 byte,它表示图片的结束。

行者AI 行者AI

行者AI绘图创作,唤醒新的灵感,创造更多可能

行者AI 100 查看详情 行者AI

阅读到这里,我们把上面的接口改造一下,解析这串buffer。

@Post('/compression')
@UseInterceptors(FileInterceptor('file'))
async imageCompression(@UploadedFile() file: Express.Multer.File) {
const buffer = file.buffer;

const result = {
header: buffer.subarray(0, 8).toString('hex'),
chunks: [],
size: file.size,
};

let pointer = 8;
while (pointer < buffer.length) {
let chunk = {};
const length = parseInt(buffer.subarray(pointer, pointer + 4).toString('hex'), 16);
const chunkType = buffer.subarray(pointer + 4, pointer + 8).toString('ascii');
const crc = buffer.subarray(pointer + length, pointer + length + 4).toString('hex');
chunk = {
...chunk,
length,
chunkType,
crc,
};

switch (chunkType) {
case 'IHDR':
const width = parseInt(buffer.subarray(pointer + 8, pointer + 12).toString('hex'), 16);
const height = parseInt(buffer.subarray(pointer + 12, pointer + 16).toString('hex'), 16);
const bitDepth = parseInt(
buffer.subarray(pointer + 16, pointer + 17).toString('hex'),
16,
);
const colorType = parseInt(
buffer.subarray(pointer + 17, pointer + 18).toString('hex'),
16,
);
const compressionMethod = parseInt(
buffer.subarray(pointer + 18, pointer + 19).toString('hex'),
16,
);
const filterMethod = parseInt(
buffer.subarray(pointer + 19, pointer + 20).toString('hex'),
16,
);
const interlaceMethod = parseInt(
buffer.subarray(pointer + 20, pointer + 21).toString('hex'),
16,
);

chunk = {
...chunk,
width,
height,
bitDepth,
colorType,
compressionMethod,
filterMethod,
interlaceMethod,
};
break;
case 'PLTE':
const colorList = [];
const colorListStr = buffer.subarray(pointer + 8, pointer + 8 + length).toString('hex');
for (let i = 0; i < colorListStr.length; i += 6) {
colorList.push(colorListStr.slice(i, i + 6));
}
chunk = {
...chunk,
colorList,
};
break;
default:
break;
}
result.chunks.push(chunk);
pointer = pointer + 4 + 4 + length + 4;
}

return result;
}

怎么利用Node进行图片压缩

这里我测试用的图没有PLTE,刚好我去TinyPNG压缩我那张测试图之后进行上传,发现有PLTE块,可以看一下,结果如下图。

怎么利用Node进行图片压缩

通过比对这两张图,压缩图片的方式我们也能窥探一二。

PNG的压缩

前面说过,PNG使用的是一种叫DEFLATE的无损压缩算法,它是Huffman Coding跟LZ77的结合。除了PNG,我们经常使用的压缩文件,.zip,.gzip也是使用的这种算法(7zip算法有更高的压缩比,也可以了解下)。要了解DEFLATE,我们首先要了解Huffman Coding和LZ77。

Huffman Coding

哈夫曼编码忘记在大学的哪门课接触过了,它是一种根据字符出现频率,用最少的字符替换出现频率最高的字符,最终降低平均字符长度的算法。

举个例子,有字符串”ABCBCABABADA”,如果按照正常空间存储,所占内存大小为12 * 8bit = 96bit,现对它进行哈夫曼编码。

1.统计每个字符出现的频率,得到A 5次 B 4次 C 2次 D 1次

2.对字符按照频率从小到大排序,将得到一个队列D1,C2,B4,A5

3.按顺序构造哈夫曼树,先构造一个空节点,最小频率的字符分给该节点的左侧,倒数第二频率的字符分给右侧,然后将频率相加的值赋值给该节点。接着用赋值后节点的值和倒数第三频率的字符进行比较,较小的值总是分配在左侧,较大的值总是分配在右侧,依次类推,直到队列结束,最后把最大频率和前面的所有值相加赋值给根节点,得到一棵完整的哈夫曼树。

4.对每条路径进行赋值,左侧路径赋值为0,右侧路径赋值为1。从根节点到叶子节点,进行遍历,遍历的结果就是该字符编码后的二进制表示,得到:A(0)B(11)C(101)D(100)。

完整的哈夫曼树如下(忽略箭头,没找到连线- -!):

怎么利用Node进行图片压缩

压缩后的字符串,所占内存大小为5 * 1bit + 4 * 2bit + 2 * 3bit + 1 * 3bit = 22bit。当然在实际传输过程中,还需要把编码表的信息(原始字符和出现频率)带上。因此最终占比大小为 4 * 8bit + 4 * 3bit(频率最大值为5,3bit可以表示)+ 22bit = 66bit(理想状态),小于原有的96bit。

LZ77

LZ77算法还是第一次知道,查了一下是一种基于字典和滑动窗的无所压缩算法。(题外话:因为Lempel和Ziv在1977年提出的算法,所以叫LZ77,哈哈哈?)

我们还是以上面这个字符串”ABCBCABABADA”为例,现假设有一个4 byte的动态窗口和一个2byte的预读缓冲区,然后对它进行LZ77算法压缩,过程顺序从上往下,示意图如下:

怎么利用Node进行图片压缩

总结下来,就是预读缓冲区在动态窗口中找到最长相同项,然后用长度较短的标记来替代这个相同项,从而实现压缩。从上图也可以看出,压缩比跟动态窗口的大小,预读缓冲区的大小和被压缩数据的重复度有关。

DEFLATE

DEFLATE【RFC 1951】是先使用LZ77编码,对编码后的结果在进行哈夫曼编码。我们这里不去讨论具体的实现方法,直接使用其推荐库Zlib,刚好Node.js内置了对Zlib的支持。接下来我们继续改造上面那个接口,如下:

import * as zlib from 'zlib';

@Post('/compression')
@UseInterceptors(FileInterceptor('file'))
async imageCompression(@UploadedFile() file: Express.Multer.File) {
const buffer = file.buffer;

const result = {
header: buffer.subarray(0, 8).toString('hex'),
chunks: [],
size: file.size,
};

// 因为可能有多个IDAT的块 需要个数组缓存最后拼接起来
const fileChunkDatas = [];
let pointer = 8;
while (pointer < buffer.length) {
let chunk = {};
const length = parseInt(buffer.subarray(pointer, pointer + 4).toString('hex'), 16);
const chunkType = buffer.subarray(pointer + 4, pointer + 8).toString('ascii');
const crc = buffer.subarray(pointer + length, pointer + length + 4).toString('hex');
chunk = {
...chunk,
length,
chunkType,
crc,
};

switch (chunkType) {
case 'IHDR':
const width = parseInt(buffer.subarray(pointer + 8, pointer + 12).toString('hex'), 16);
const height = parseInt(buffer.subarray(pointer + 12, pointer + 16).toString('hex'), 16);
const bitDepth = parseInt(
buffer.subarray(pointer + 16, pointer + 17).toString('hex'),
16,
);
const colorType = parseInt(
buffer.subarray(pointer + 17, pointer + 18).toString('hex'),
16,
);
const compressionMethod = parseInt(
buffer.subarray(pointer + 18, pointer + 19).toString('hex'),
16,
);
const filterMethod = parseInt(
buffer.subarray(pointer + 19, pointer + 20).toString('hex'),
16,
);
const interlaceMethod = parseInt(
buffer.subarray(pointer + 20, pointer + 21).toString('hex'),
16,
);

chunk = {
...chunk,
width,
height,
bitDepth,
colorType,
compressionMethod,
filterMethod,
interlaceMethod,
};
break;
case 'PLTE':
const colorList = [];
const colorListStr = buffer.subarray(pointer + 8, pointer + 8 + length).toString('hex');
for (let i = 0; i < colorListStr.length; i += 6) {
colorList.push(colorListStr.slice(i, i + 6));
}
chunk = {
...chunk,
colorList,
};
break;
case 'IDAT':
fileChunkDatas.push(buffer.subarray(pointer + 8, pointer + 8 + length));
break;
default:
break;
}
result.chunks.push(chunk);
pointer = pointer + 4 + 4 + length + 4;
}

const originFileData = zlib.unzipSync(Buffer.concat(fileChunkDatas));

// 这里原图片数据太长了 我就只打印了长度
return {
...result,
originFileData: originFileData.length,
};
}

怎么利用Node进行图片压缩

最终打印的结果,我们需要注意红框的那几个部分。可以看到上图,位深和颜色类型决定了每个像素由4 byte组成,然后由于过滤方式的存在,会在每行的第一个字节进行标记。因此该图的原始数据所占大小为:707 * 475 * 4 byte + 475 * 1 byte = 1343775 byte。正好是我们打印的结果。

我们也可以试试之前TinyPNG压缩后的图,如下:

怎么利用Node进行图片压缩

可以看到位深为8,索引颜色类型的图每像素占1 byte。计算得到:707 * 475 * 1 byte + 475 * 1 byte = 336300 byte。结果也正确。

总结

现在再看如何进行图片压缩,你可能很容易得到下面几个结论:

1.减少不必要的辅助块信息,因为辅助块对PNG图片而言并不是必须的。

2.减少IDAT的块数,因为每多一个IDAT的块,就多余了12 byte。

3.降低每个像素所占的内存大小,比如当前是4通道8位深的图片,可以统计整个图片色域,得到色阶表,设置索引颜色类型,降低通道从而降低每个像素的内存大小。

4.等等….

至于JPEG,WEBP等等格式图片,有机会再看。溜了溜了~(还是使用现成的库处理压缩吧)。

好久没写文章,写完才发现语雀不能免费共享,发在这里吧。

更多node相关知识,请访问:nodejs 教程!

以上就是怎么利用Node进行图片压缩的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/542566.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
苹果16怎么设置自定义来电铃声_苹果16更换个性化来电铃声教程
上一篇 2025年11月9日 15:59:49
《烟雨江湖》云台峰怎么跳-跳云台峰详细方法介绍
下一篇 2025年11月9日 15:59:49

相关推荐

  • VSCode的扩展推荐是怎么工作的?

    VSCode的扩展推荐基于用户行为和项目环境智能生成,当你打开.py文件时会推荐Python相关工具,打开.ts、.vue等文件则触发对应语言插件;系统通过分析package.json、requirements.txt等依赖文件识别技术栈,推荐Docker、ESLint等匹配扩展;同时记录常用操作如…

    2026年9月20日
    000
  • VSCode的侧边栏图标代表什么?

    资源管理器(文件夹图标)用于管理项目文件结构,支持新建、重命名、删除和拖拽操作;2. 搜索(放大镜图标)实现全局文本查找与替换,支持正则表达式及范围筛选;3. 源代码管理(分支图标)集成Git功能,可查看变更、提交代码并同步远程仓库;4. 运行和调试(虫子图标)支持断点调试、变量监控及多语言启动配置…

    2026年9月20日
    000
  • 如何为VSCode添加自定义主题?

    可通过安装扩展、手动添加JSON文件或使用Yeoman生成器创建主题。首先安装现成主题扩展并从颜色主题列表中选择应用;其次将自定义主题JSON文件放入用户themes目录后在命令面板中启用;最后可用yo code生成项目开发发布主题,需编辑配色文件并预览效果。 为 VSCode 添加自定义主题有几种…

    2026年9月20日
    000
  • edge浏览器无法打开本地HTML文件或显示空白怎么办_Edge浏览器打开本地HTML文件失败解决方法

    1、检查文件路径并选择Edge打开,确保路径为纯英文;2、在edge://flags中启用“Allow file access from files”;3、使用开发者工具排查资源加载错误;4、通过命令行启动Edge绕过安全限制;5、推荐使用npx live-server搭建本地服务器运行HTML文件…

    2026年9月20日
    000
  • VSCode的扩展推荐是基于什么?

    答案是VSCode根据文件类型、项目配置、用户行为和社区流行度智能推荐扩展。打开.py文件时提示Python扩展,存在package.json则推荐JS工具,频繁格式化代码会推荐Prettier,安装ESLint后可能推荐Prettier,高评分和官方扩展优先展示。 VSCode的扩展推荐主要基于你…

    2026年9月20日
    000
  • mysql如何实现用户注册功能

    答案:通过MySQL创建用户表并结合后端逻辑实现注册功能。首先在MySQL中创建包含用户名、密码、邮箱等字段的users表,确保唯一性约束;后端接收%ignore_a_1%提交的注册数据,对密码加密(如SHA256或bcrypt),使用参数化查询将数据安全存入数据库;前端通过表单或AJAX发送注册请…

    2026年9月13日
    200
  • 如何利用VSCode的配置文件(Launch.json)环境变量来管理不同环境的配置?

    通过配置launch.json中的多个调试环境并结合.env文件,可高效管理开发、测试、生产等不同环境的运行参数。 在使用 VSCode 进行开发时,launch.json 文件是调试配置的核心。通过合理利用环境变量和配置文件分离,可以轻松管理不同环境(如开发、测试、生产)的运行参数。 理解 lau…

    2026年9月13日
    000
  • 如何用ChatGPT来重构优化一段旧代码_提升代码质量与性能的步骤

    明确目标并提供上下文,分步引导ChatGPT优化代码结构、算法和语言特性,最后验证建议并本地测试,确保重构有效且安全。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ 用ChatGPT来重构和优化旧代码是一种高效提升代码质量与性能的方式。关键…

    2026年9月12日
    100
  • 如何让VSCode自动识别项目类型?

    VSCode通过项目中的文件和配置自动识别类型,需提供线索如package.json、requirements.txt等标志性文件,结合安装对应语言扩展、配置.vscode/settings.json及使用.code-workspace文件,即可让编辑器正确识别并支持Node.js、Python、R…

    2026年9月12日
    300
  • 如何配置VSCode以附加(Attach)模式调试一个正在运行的Node.js或Python进程?

    要使用VSCode附加模式调试Node.js或Python进程,需先以可调试方式启动程序。对于Node.js,通过–inspect系列参数开启调试端口(如9229),并在launch.json中配置”request”: “attach”及对…

    2026年9月12日
    100
  • 如何找到并安装适合你编程语言的扩展?

    首先确认编程语言及项目需求,再根据编辑器选择合适扩展。以VS Code为例,通过扩展面板搜索语言名称,优先选择官方推荐、高下载量和评分的扩展,如Microsoft提供的Python扩展。安装后检查语法高亮与智能提示,并在设置中配置解释器路径、格式化工具等选项。若使用框架或特定运行时,可额外安装对应增…

    2026年9月12日
    200
  • 如何通过扩展让VSCode支持一门新的编程语言?

    要让VSCode支持新编程语言需通过扩展实现。1. 使用yo code生成扩展模板,包含package.json、语法高亮和语言配置文件。2. 在.tmLanguage.json中用正则定义语法高亮规则,注册到package.json。3. 通过language-configuration.json…

    2026年9月12日
    100
  • 除了内置的终端,VSCode有哪些插件能极大提升命令行的工作效率?

    Code Runner可一键运行多语言代码并显示结果,适合快速测试;2. Terminal Tabs通过标签页管理多个终端会话,支持重命名和快捷切换;3. Shell Command允许对文件或选中文本执行自定义shell命令,提升重复操作效率;4. Task Explorer可视化运行项目脚本,自…

    2026年9月12日
    100
  • 全栈应用中X-Frame-Options防护策略的正确实施

    本教程探讨了在全栈应用中配置`x-frame-options` http响应头以防止点击劫持(clickjacking)的常见误区。我们发现,在spring security等后端框架中配置此头部仅对后端api端点有效,而前端应用(如由nginx、tomcat等服务器托管的单页应用)仍可能面临风险。…

    2026年9月12日
    000
  • 打开DeepSeek官网 deepseek在线版立即使用

    DeepSeek官网在线版可通过https://www.deepseek.com访问,用户点击“开始对话”即可使用智能对话、代码生成、长文本处理等功能,开发者还可申请API密钥集成至自有系统,支持多语言调用与安全配置。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepS…

    2026年9月12日
    100
  • 如何在mysql中开发任务分配管理系统

    答案:基于MySQL设计任务分配系统需构建用户表和任务表,通过assignee_id关联实现任务分配;利用SQL完成任务的增删改查操作;结合角色字段控制权限,管理员可管理所有任务,普通成员仅限个人任务;支持项目分类、评论、日志等扩展功能,并在关键字段创建索引以提升查询性能。 在 MySQL 中开发任…

    2026年9月12日
    100
  • 如何使用mysql开发项目管理任务表

    首先设计包含任务基本信息的主表tasks,包含id、title、status、priority等字段,并通过project_id和assignee_id关联项目与用户;为支持多对多关系创建user_tasks中间表;常用操作包括按项目或负责人查询任务、筛选高优先级任务、更新状态及查找即将到期任务;结…

    2026年9月12日
    100
  • 如何在VSCode中运行和调试Node.js应用?

    答案:在VSCode中调试Node.js需先确认Node环境正常,通过集成终端运行脚本,并创建launch.json配置调试参数,设置断点后启动调试,可查看变量、执行语句或附加到运行进程。 在VSCode中运行和调试Node.js应用非常方便,借助内置的调试功能和集成终端,可以快速启动、设置断点并排…

    2026年9月11日
    100
  • 怎样为开源项目贡献代码?从克隆、调试到提交VSCode扩展的完整流程是怎样的?

    首先克隆仓库并配置环境,安装依赖后在VSCode中调试扩展;接着修改代码并编写测试用例确保质量;最后提交更改至分支并发起PR,描述改动内容与验证方式,等待维护者审查并迭代反馈。 为开源项目贡献代码是提升技术能力、参与社区协作的重要方式。以 VSCode 扩展为例,从克隆仓库到提交代码的完整流程其实并…

    2026年9月11日
    200
  • 如何在mysql中开发简单社交平台项目

    答案是设计合理的数据库结构并实现基础功能。首先创建用户、动态、评论、点赞和关注关系表,定义主外键关联;接着通过SQL完成发帖、评论、点赞、关注等操作;再结合Python等后端语言连接MySQL执行参数化查询防止注入;最后为常用字段添加索引、使用UTF8MB4字符集并定期备份数据以优化性能与安全。 要…

    2026年9月11日
    100

发表回复

登录后才能评论
关注微信