OpenHarmony PC端引入ohos-signpost自动签名工具,实现三方库adm-zip 库超详指南,无报错批量压缩解压方案
欢迎加入开源鸿蒙PC社区: https://harmonypc.csdn.net/
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
AtomGit 仓库地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_node_vue_ts
本文完整梳理了ARM64架构鸿蒙PC(HarmonyOS/OpenHarmony6.1及以上),基于CodeArts IDE搭建Vite+Vue前端项目的全流程与疑难解决方案。项目实操中,Vite启动会出现rolldown原生模块权限拒绝报错,根源是鸿蒙系统拦截未签名二进制文件,最终解决方案为引入ohos-signpost自动签名工具,配置npm后置钩子,在依赖安装完成后自动为所有.node文件添加系统合法签名,消除权限校验拦截,最终实现CodeArts IDE内Vue+TS项目正常启动、调试。
这里有一篇比较详细的攻略:
OpenHarmony 鸿蒙 PC + CodeArts IDE 前端 Vite+Vue 完整开发环境搭建指南
一、adm-zip 库介绍
1. 作用
adm-zip 纯JS压缩解压库,无需系统zip/unzip命令、无二进制依赖,Node.js/浏览器通用,专门处理 .zip 压缩包。
原生Node没有内置zip处理模块,adm-zip 提供全套压缩、解压、读取包内文件、新增/删除包内文件、加密压缩等能力。
核心能力
- 解压本地zip文件到指定目录;
- 动态创建zip压缩包,添加文件、文件夹、文本内容;
- 读取压缩包内所有文件列表、单文件内容、文件大小、时间;
- 从内存文本/Buffer直接写入压缩包,不用生成本地临时文件;
- 删除压缩包内指定文件、重命名包内文件;
- 支持设置压缩等级、设置zip解压密码(加密压缩包);
- 跨平台自动适配鸿蒙PC、Windows、macOS、Linux路径;
- 同步+异步双套API,脚本/服务均可使用。
业务场景
- 前端构建脚本:打包静态资源生成zip部署包;
- 后台文件处理:用户上传zip批量解压导入资源;
- 模板导出:将多个配置文件、模板打包下载;
- 批量备份:把日志、缓存目录压缩备份;
- 自动化脚本解压资源包、读取压缩包内配置文件。
安装命令
npm i adm-zip
# TS类型提示
npm i -D @types/adm-zip

二、完整大篇幅示例代码 admZipDemo.js
const AdmZip = require('adm-zip');
const fs = require('fs-extra');
const path = require('path');
// 全局工作目录
const WORKSPACE = path.resolve(__dirname, './zipTestSpace');
// 测试压缩包路径
const ZIP_FILE = path.join(WORKSPACE, 'demo-bundle.zip');
// 解压输出目录
const EXTRACT_OUT = path.join(WORKSPACE, 'extract-output');
// 源素材目录(用来打包)
const SOURCE_DIR = path.join(WORKSPACE, 'source-files');
/**
* 前置:自动生成测试素材文件,用来打包演示
*/
async function initSourceFiles() {
// 多层测试文件
const filePaths = [
path.join(SOURCE_DIR, 'readme.md'),
path.join(SOURCE_DIR, 'config.json'),
path.join(SOURCE_DIR, 'static', 'logo.png'),
path.join(SOURCE_DIR, 'static', 'css', 'main.css'),
path.join(SOURCE_DIR, 'utils', 'helper.js'),
];
for (const fp of filePaths) {
await fs.ensureFile(fp);
// 写入一点测试文本
await fs.writeFile(fp, `自动生成文件内容:${fp}`, 'utf8');
}
console.log('✅ 测试素材文件自动创建完成\n');
}
/**
* 辅助工具:打印目录全部文件
*/
async function printDirList(dir) {
const exist = await fs.pathExists(dir);
if (!exist) {
console.log(`目录不存在:${dir}\n`);
return;
}
const entries = await fs.readdir(dir, { withFileTypes: true });
const list = entries.map(e => e.name);
console.log(`【${path.basename(dir)}】文件列表:`, list, '\n');
}
/**
* 示例1:读取本地已有zip,打印包内所有文件信息(同步)
*/
function demoReadZipEntriesSync() {
console.log('===== 示例1:同步读取zip包内全部文件信息 =====');
const zip = new AdmZip(ZIP_FILE);
// 获取所有条目
const entries = zip.getEntries();
console.log(`压缩包内总文件数量:${entries.length}`);
entries.forEach(entry => {
console.log(`文件名:${entry.entryName} | 大小:${entry.size}字节 | 文件夹:${entry.isDirectory}`);
});
console.log('');
}
/**
* 示例2:解压整个zip到指定目录(异步推荐写法)
*/
async function demoExtractAllAsync() {
console.log('===== 示例2:异步完整解压全部文件到目录 =====');
const zip = new AdmZip(ZIP_FILE);
// 自动创建输出目录
await fs.ensureDir(EXTRACT_OUT);
// 异步解压全部
await new Promise((resolve, reject) => {
zip.extractAllToAsync(EXTRACT_OUT, /*overwrite*/ true, err => {
if (err) return reject(err);
resolve();
});
});
console.log('全部文件解压完成');
await printDirList(EXTRACT_OUT);
}
/**
* 示例3:只解压包内单个指定文件,读取内容不落地磁盘
*/
async function demoExtractSingleFileToBuffer() {
console.log('===== 示例3:提取包内单个文件,获取内存Buffer不生成本地文件 =====');
const zip = new AdmZip(ZIP_FILE);
const entryName = 'config.json';
const entry = zip.getEntry(entryName);
if (!entry) {
console.log('未找到文件:', entryName, '\n');
return;
}
// 获取文件二进制buffer
const buf = entry.getData();
const text = buf.toString('utf8');
console.log(`文件${entryName}文本内容:\n`, text, '\n');
}
/**
* 示例4:从零新建zip包,添加本地整个文件夹打包
*/
async function demoCreateZipFromFolder() {
console.log('===== 示例4:新建压缩包,打包整个source-files目录 =====');
const zip = new AdmZip();
// 添加整个目录,保留内部层级
zip.addLocalFolder(SOURCE_DIR);
// 写入本地zip文件
zip.writeZip(ZIP_FILE);
console.log('文件夹打包完成,生成zip:', ZIP_FILE, '\n');
}
/**
* 示例5:向压缩包内新增单个本地文件
*/
async function demoAddSingleLocalFile() {
console.log('===== 示例5:向已有zip追加本地单个文件 =====');
const zip = new AdmZip(ZIP_FILE);
const addFile = path.join(WORKSPACE, 'extra-note.txt');
await fs.writeFile(addFile, '额外新增的备注文件', 'utf8');
// 把本地文件加入压缩包,包内路径为 note/extra-note.txt
zip.addLocalFile(addFile, 'note');
zip.writeZip(ZIP_FILE);
console.log('追加本地文件完成\n');
}
/**
* 示例6:直接写入内存文本,不生成本地文件,存入zip
*/
function demoAddTextContentInMemory() {
console.log('===== 示例6:内存文本直接写入压缩包(无需本地临时文件) =====');
const zip = new AdmZip(ZIP_FILE);
// 参数:包内路径,文本内容
zip.addFile('memo/memory-text.txt', Buffer.from('这是纯内存生成的文本,无本地文件'));
zip.writeZip(ZIP_FILE);
console.log('内存文本写入zip完成\n');
}
/**
* 示例7:删除压缩包内指定文件
*/
function demoDeleteEntryInZip() {
console.log('===== 示例7:删除压缩包内指定文件 =====');
const zip = new AdmZip(ZIP_FILE);
const delPath = 'note/extra-note.txt';
zip.deleteFile(delPath);
zip.writeZip(ZIP_FILE);
console.log(`已删除包内文件:${delPath}\n`);
}
/**
* 示例8:创建带解压密码的加密zip包(新版正确写法)
*/
function demoCreateEncryptedZip() {
console.log('===== 示例8:创建带解压密码的加密zip包 =====');
const encryptZipPath = path.join(WORKSPACE, 'secret-bundle.zip');
const zip = new AdmZip();
zip.addFile('secret.txt', Buffer.from('私密配置数据123456'));
// 新版:写入时传入 password 配置,不再使用 setPassword()
zip.writeZip(encryptZipPath, undefined, opts => {
opts.password = 'MySecret123!';
});
console.log('加密压缩包生成完成,密码:MySecret123! 路径:', encryptZipPath, '\n');
}
/**
* 示例9:同步解压全部文件(脚本初始化同步流程用)
*/
function demoExtractAllSync() {
console.log('===== 示例9:同步解压全部文件 =====');
const syncOut = path.join(WORKSPACE, 'sync-extract');
const zip = new AdmZip(ZIP_FILE);
zip.extractAllTo(syncOut, true);
console.log('同步解压完成');
printDirList(syncOut);
}
/**
* 示例10:自定义压缩等级打包(0不压缩 ~ 9最高压缩)
*/
function demoCustomCompressionLevel() {
console.log('===== 示例10:自定义压缩等级打包 =====');
const levelZip = path.join(WORKSPACE, 'high-compress.zip');
const zip = new AdmZip();
zip.addLocalFolder(SOURCE_DIR);
// 最高压缩等级9
zip.writeZip(levelZip, undefined, (options) => {
options.compression = 9;
});
console.log('高压缩等级zip生成完成\n');
}
/**
* 统一入口执行全部演示
*/
async function runAllDemo() {
// 1. 生成测试素材
await initSourceFiles();
// 2. 先打包生成基础zip包
await demoCreateZipFromFolder();
// 3. 依次执行所有示例
demoReadZipEntriesSync();
await demoExtractAllAsync();
await demoExtractSingleFileToBuffer();
await demoAddSingleLocalFile();
demoAddTextContentInMemory();
demoDeleteEntryInZip();
demoCreateEncryptedZip();
demoExtractAllSync();
demoCustomCompressionLevel();
console.log('🎉 adm-zip 全部功能示例执行完毕');
}
// 全局捕获异常
runAllDemo().catch(err => {
console.error('脚本运行异常:', err);
});

一、头部依赖与路径常量定义
const AdmZip = require('adm-zip');
const fs = require('fs-extra');
const path = require('path');
// 全局工作目录
const WORKSPACE = path.resolve(__dirname, './zipTestSpace');
// 测试压缩包路径
const ZIP_FILE = path.join(WORKSPACE, 'demo-bundle.zip');
// 解压输出目录
const EXTRACT_OUT = path.join(WORKSPACE, 'extract-output');
// 源素材目录(用来打包)
const SOURCE_DIR = path.join(WORKSPACE, 'source-files');
- adm-zip:纯JS压缩解压核心库,处理zip包;
- fs-extra:用来自动创建多层文件、判断目录是否存在,简化文件操作;
- path:跨系统路径拼接工具,自动适配鸿蒙、Windows、macOS正反斜杠;
- 统一抽离所有测试路径常量,方便统一修改,避免代码中重复写路径字符串;
path.resolve(__dirname)转为绝对路径,防止运行时找不到文件。
二、前置工具函数 initSourceFiles 自动生成测试素材
async function initSourceFiles() {
// 多层测试文件
const filePaths = [
path.join(SOURCE_DIR, 'readme.md'),
path.join(SOURCE_DIR, 'config.json'),
path.join(SOURCE_DIR, 'static', 'logo.png'),
path.join(SOURCE_DIR, 'static', 'css', 'main.css'),
path.join(SOURCE_DIR, 'utils', 'helper.js'),
];
for (const fp of filePaths) {
await fs.ensureFile(fp);
// 写入一点测试文本
await fs.writeFile(fp, `自动生成文件内容:${fp}`, 'utf8');
}
console.log('✅ 测试素材文件自动创建完成\n');
}
fs.ensureFile(fp)特性:路径中不存在的各级父目录会一次性自动创建;- 模拟真实前端工程多层目录结构(静态资源、工具脚本、配置文件),为后续打包提供素材;
- 运行脚本无需手动新建文件夹,全自动生成测试环境。
三、辅助工具 printDirList 打印目录文件列表
async function printDirList(dir) {
const exist = await fs.pathExists(dir);
if (!exist) {
console.log(`目录不存在:${dir}\n`);
return;
}
const entries = await fs.readdir(dir, { withFileTypes: true });
const list = entries.map(e => e.name);
console.log(`【${path.basename(dir)}】文件列表:`, list, '\n');
}
删除、解压前后打印目录内容,直观观察文件变化,方便调试查看操作效果。
四、示例1 demoReadZipEntriesSync 同步读取压缩包内部文件清单
function demoReadZipEntriesSync() {
console.log('===== 示例1:同步读取zip包内全部文件信息 =====');
const zip = new AdmZip(ZIP_FILE);
// 获取所有条目
const entries = zip.getEntries();
console.log(`压缩包内总文件数量:${entries.length}`);
entries.forEach(entry => {
console.log(`文件名:${entry.entryName} | 大小:${entry.size}字节 | 文件夹:${entry.isDirectory}`);
});
console.log('');
}
new AdmZip(zip路径):加载本地已存在的压缩包;zip.getEntries():获取压缩包内所有资源条目,返回数组;- entry 对象常用属性:
- entry.entryName:包内相对路径;
- entry.size:文件字节大小;
- entry.isDirectory:判断当前条目是文件夹还是文件;
- 同步写法,适合脚本初始化、预校验压缩包内容。
五、示例2 demoExtractAllAsync 异步完整解压全部文件
async function demoExtractAllAsync() {
console.log('===== 示例2:异步完整解压全部文件到目录 =====');
const zip = new AdmZip(ZIP_FILE);
// 自动创建输出目录
await fs.ensureDir(EXTRACT_OUT);
// 异步解压全部
await new Promise((resolve, reject) => {
zip.extractAllToAsync(EXTRACT_OUT, /*overwrite*/ true, err => {
if (err) return reject(err);
resolve();
});
});
console.log('全部文件解压完成');
await printDirList(EXTRACT_OUT);
}
extractAllToAsync(输出目录, 是否覆盖, 回调)异步解压API,非阻塞Node事件循环;- 第二个参数
true:允许覆盖已存在的同名文件,不会抛出冲突报错; - 用 Promise 封装回调,搭配 await 消除回调嵌套,后端接口、构建脚本推荐使用异步。
六、示例3 demoExtractSingleFileToBuffer 提取单个文件到内存,不落地磁盘
async function demoExtractSingleFileToBuffer() {
console.log('===== 示例3:提取包内单个文件,获取内存Buffer不生成本地文件 =====');
const zip = new AdmZip(ZIP_FILE);
const entryName = 'config.json';
const entry = zip.getEntry(entryName);
if (!entry) {
console.log('未找到文件:', entryName, '\n');
return;
}
// 获取文件二进制buffer
const buf = entry.getData();
const text = buf.toString('utf8');
console.log(`文件${entryName}文本内容:\n`, text, '\n');
}
zip.getEntry(包内路径)精准定位单个文件,不用完整解压整个压缩包;entry.getData()返回文件二进制 Buffer,仅存于内存;- 业务场景:只读取压缩包内配置json,无需解压全部资源,大幅减少磁盘IO。
七、示例4 demoCreateZipFromFolder 打包本地整个文件夹
async function demoCreateZipFromFolder() {
console.log('===== 示例4:新建压缩包,打包整个source-files目录 =====');
const zip = new AdmZip();
// 添加整个目录,保留内部层级
zip.addLocalFolder(SOURCE_DIR);
// 写入本地zip文件
zip.writeZip(ZIP_FILE);
console.log('文件夹打包完成,生成zip:', ZIP_FILE, '\n');
}
new AdmZip()不传参数代表创建全新空白压缩包;addLocalFolder(本地目录路径):递归读取目录下所有文件,完整保留原目录层级;zip.writeZip(输出路径):将内存中的压缩结构持久化写入本地zip文件,最核心打包API。
八、示例5 demoAddSingleLocalFile 向已有压缩包追加外部本地文件
async function demoAddSingleLocalFile() {
console.log('===== 示例5:向已有zip追加本地单个文件 =====');
const zip = new AdmZip(ZIP_FILE);
const addFile = path.join(WORKSPACE, 'extra-note.txt');
await fs.writeFile(addFile, '额外新增的备注文件', 'utf8');
// 把本地文件加入压缩包,包内路径为 note/extra-note.txt
zip.addLocalFile(addFile, 'note');
zip.writeZip(ZIP_FILE);
console.log('追加本地文件完成\n');
}
addLocalFile(本地文件路径, 包内存放子目录);- 打开已存在的zip,新增文件后重新覆盖写入,实现增量追加资源。
九、示例6 demoAddTextContentInMemory 内存文本直接写入压缩包
function demoAddTextContentInMemory() {
console.log('===== 示例6:内存文本直接写入压缩包(无需本地临时文件) =====');
const zip = new AdmZip(ZIP_FILE);
// 参数:包内路径,文本内容
zip.addFile('memo/memory-text.txt', Buffer.from('这是纯内存生成的文本,无本地文件'));
zip.writeZip(ZIP_FILE);
console.log('内存文本写入zip完成\n');
}
addFile(包内路径, Buffer):直接传入二进制数据,不需要先生成本地txt/json临时文件;- 优势:全程内存操作,减少磁盘读写损耗,适合动态生成配置、说明文档。
十、示例7 demoDeleteEntryInZip 删除压缩包内指定文件
function demoDeleteEntryInZip() {
console.log('===== 示例7:删除压缩包内指定文件 =====');
const zip = new AdmZip(ZIP_FILE);
const delPath = 'note/extra-note.txt';
zip.deleteFile(delPath);
zip.writeZip(ZIP_FILE);
console.log(`已删除包内文件:${delPath}\n`);
}
zip.deleteFile(包内文件路径):修改已有压缩包,删除冗余文件后重新输出,用于清理打包不需要的临时资源。
十一、示例8 demoCreateEncryptedZip 创建带解压密码的加密压缩包
function demoCreateEncryptedZip() {
console.log('===== 示例8:创建带解压密码的加密zip包 =====');
const encryptZipPath = path.join(WORKSPACE, 'secret-bundle.zip');
const zip = new AdmZip();
zip.addFile('secret.txt', Buffer.from('私密配置数据123456'));
// 新版:写入时传入 password 配置,不再使用 setPassword()
zip.writeZip(encryptZipPath, undefined, opts => {
opts.password = 'MySecret123!';
});
console.log('加密压缩包生成完成,密码:MySecret123! 路径:', encryptZipPath, '\n');
}
关键知识点
新版 adm-zip 废弃了 zip.setPassword() 单独实例方法,加密密码必须在 writeZip 的配置回调中传入 opts.password,否则会报函数不存在;
适用场景:打包密钥、私密业务配置,分发时需要密码才能解压查看。
十二、示例9 demoExtractAllSync 同步阻塞解压
function demoExtractAllSync() {
console.log('===== 示例9:同步解压全部文件 =====');
const syncOut = path.join(WORKSPACE, 'sync-extract');
const zip = new AdmZip(ZIP_FILE);
zip.extractAllTo(syncOut, true);
console.log('同步解压完成');
printDirList(syncOut);
}
extractAllTo(输出目录, 覆盖) 同步阻塞API,会暂停代码执行直到解压完成,适合项目启动、一次性初始化脚本,不适合后端接口服务。
十三、示例10 demoCustomCompressionLevel 自定义压缩等级
function demoCustomCompressionLevel() {
console.log('===== 示例10:自定义压缩等级打包 =====');
const levelZip = path.join(WORKSPACE, 'high-compress.zip');
const zip = new AdmZip();
zip.addLocalFolder(SOURCE_DIR);
// 最高压缩等级9
zip.writeZip(levelZip, undefined, (options) => {
options.compression = 9;
});
console.log('高压缩等级zip生成完成\n');
}
压缩等级取值范围 0 ~ 9:
- 0:不压缩,打包速度最快,文件体积最大;
- 9:最高压缩率,文件最小,打包耗时更长;
可根据业务需求平衡打包速度与压缩体积。
十四、统一执行入口 runAllDemo
async function runAllDemo() {
// 1. 生成测试素材
await initSourceFiles();
// 2. 先打包生成基础zip包
await demoCreateZipFromFolder();
// 3. 依次执行所有示例
demoReadZipEntriesSync();
await demoExtractAllAsync();
await demoExtractSingleFileToBuffer();
await demoAddSingleLocalFile();
demoAddTextContentInMemory();
demoDeleteEntryInZip();
demoCreateEncryptedZip();
demoExtractAllSync();
demoCustomCompressionLevel();
console.log('🎉 adm-zip 全部功能示例执行完毕');
}
// 全局捕获异常
runAllDemo().catch(err => {
console.error('脚本运行异常:', err);
});
- 执行顺序约束:必须先创建素材、生成基础zip包,后面读取、追加、删除、解压示例才有压缩包可操作;
- 顶层 catch 捕获所有异步、同步报错,打印完整错误堆栈,防止Node进程直接闪退。
整体业务价值总结
- 纯JS无二进制依赖,鸿蒙PC Node环境无需签名,直接运行无权限报错;
- 一套库完整覆盖打包、解压、增量增删文件、加密、自定义压缩等级全套zip操作;
- 支持内存读写Buffer,减少磁盘IO,性能优于调用系统zip/unzip命令;
- 同步/异步两套API,兼顾一次性脚本、后端Web服务两种开发场景;
- 无需依赖系统解压工具,Windows、macOS、鸿蒙、Linux跨平台行为完全一致;
- 工程化常用:前端构建打包部署包、后台处理用户上传压缩资源、模板文件批量导出备份。
TS 精简版 admZipDemo.ts
import AdmZip from 'adm-zip';
import path from 'path';
import fs from 'fs-extra';
async function tsZipTest() {
const zip = new AdmZip();
zip.addFile('hello.txt', Buffer.from('TS测试压缩'));
zip.writeZip(path.resolve(__dirname, './test.zip'));
console.log('TS压缩包生成成功');
}
tsZipTest().catch(e => console.error(e));
运行命令
# 安装依赖
npm i adm-zip fs-extra
# 执行脚本
node admZipDemo.js
代码逐段功能说明
1. 前置初始化 initSourceFiles
自动生成多层目录+测试文件,用来做打包素材,不用手动创建文件夹,运行脚本自动生成 zipTestSpace 完整测试环境。
2. 读取压缩包文件列表 demoReadZipEntriesSync
zip.getEntries() 获取压缩包内所有条目对象,包含:文件名、大小、是否文件夹、修改时间等元信息;适合预检查压缩包内容。
3. 异步完整解压 extractAllToAsync
异步非阻塞解压,适合后端接口、Vite构建脚本;第二个参数 true 代表覆盖已存在文件,不会报错。
4. 仅提取单个文件到内存Buffer
不用完整解压到磁盘,直接读取包内单文件二进制,适合读取压缩包内config.json配置,减少IO操作。
5. 打包本地整个文件夹 addLocalFolder
最常用打包API,自动递归读取目录内所有文件,完整保留原目录层级结构,用于批量导出静态资源包。
6. 追加本地单个文件 addLocalFile
向已存在的zip包新增外部文件,第二个参数可指定在压缩包内存放的子目录路径。
7. 内存文本直接写入 addFile
无需在本地生成临时txt/json,直接把字符串转Buffer存入压缩包,高性能,减少磁盘读写。
8. 删除包内文件 deleteFile
修改已有压缩包,删除不需要的文件再重新输出zip,适配清理冗余资源场景。
9. 加密压缩 setPassword
给压缩包设置解压密码,分发私密配置、密钥文件时使用,解压必须传入对应密码。
10. 同步解压 extractAllTo
同步阻塞API,适合项目启动、初始化一次性解压资源简单脚本。
11. 自定义压缩等级
压缩等级范围0~9:
- 0:不压缩,打包速度最快;
- 9:最高压缩率,文件体积最小,耗时略长;
可根据打包场景平衡速度与体积。
鸿蒙PC Node环境适配说明
- adm-zip 纯JS实现,无.node二进制原生模块;
- 不需要
ohos-signpost签名,直接 node 运行不会出现 permission denied; - 路径自动兼容鸿蒙ARM64系统正反斜杠,不用手动处理路径分隔符;
- 无系统zip/unzip命令依赖,脱离系统工具独立运行。
工程化常用package.json脚本
{
"scripts": {
"zip:build": "node admZipDemo.js",
"zip:extract": "node admZipDemo.js extract",
"zip:secret": "node admZipDemo.js encrypt"
}
}
核心开发优势总结
- 一套代码同时支持打包、解压、增删包内文件、加密;
- 内存操作减少磁盘IO,性能优于调用系统zip命令;
- 同步/异步双API适配脚本、Web服务两种场景;
- 跨平台无依赖,鸿蒙、Windows、macOS、Linux行为完全一致;
- 支持读取单文件不完整解压,适合配置读取、轻量化解析场景。
更多推荐



所有评论(0)