Electron for 鸿蒙PC - 文件系统权限处理与createDir降级策略完整方案
摘要: 本文针对Abricotine在鸿蒙PC平台适配时遇到的files.createDir()权限问题(EPERM错误),提出系统化解决方案。通过分析HarmonyOS严格的权限模型(沙箱目录完全访问/用户目录需授权/系统目录禁止),优化原函数逻辑:新增权限检查工具canCreateDirectory(),实现静默失败、备选目录降级、功能降级等策略。最终方案在权限不足时返回null而非抛出异常,
前言
在将 Abricotine 适配到鸿蒙 PC 平台时,文件系统操作遇到了严重的权限问题:files.createDir() 函数在用户数据目录创建文件夹时失败,抛出 EPERM(权限不足)错误,导致文件操作失败,应用功能受限。
本文将深入分析 HarmonyOS 文件系统权限模型,提供完整的权限检查和降级策略,确保应用在权限受限的情况下仍能正常工作,实现优雅的降级处理。
关键词:鸿蒙PC、Electron适配、文件系统权限、createDir、权限检查、降级策略、错误处理
目录
欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/
问题现象与错误分析
1.1 错误信息
文件操作时,控制台出现以下错误:
[HarmonyOS Files] Permission denied creating directory: /storage/emulated/0/Documents/export_files
Error: EPERM: operation not permitted, mkdir '/storage/emulated/0/Documents/export_files'
错误特征:
- ❌
files.createDir()调用失败 - ❌ 错误代码:
EPERM(权限不足) - ❌ 无法创建文件夹
- ❌ 依赖文件夹创建的功能失败
1.2 问题影响
这个错误会导致:
- ❌ 导出功能失败:无法创建导出文件夹
- ❌ 文件操作失败:依赖文件夹创建的操作失败
- ❌ 用户体验差:功能受限,用户无法正常使用
- ❌ 应用崩溃风险:如果错误处理不当,可能导致应用崩溃
根本原因深度分析
2.1 HarmonyOS 权限模型
根据 HarmonyOS 文件访问权限文档,HarmonyOS 采用严格的权限模型:
权限级别:
- ✅ 应用沙箱目录:完全访问权限
- ⚠️ 用户数据目录:需要用户授权
- ❌ 系统目录:完全禁止
文件夹创建权限:
- ✅ 可以在应用沙箱目录创建文件夹
- ⚠️ 在用户数据目录创建文件夹需要权限
- ❌ 在系统目录创建文件夹完全禁止
2.2 createDir 函数分析
原代码:
// files.js
createDir: function (target) {
var parsedPath = parsePath(target)
var destDir = parsedPath.extname ? parsedPath.dirname : target
try {
mkdirp.sync(destDir) // ❌ 可能抛出 EPERM 错误
} catch (e) {
throw e // ❌ 直接抛出错误,导致应用崩溃
}
return destDir
}
问题分析:
- 直接调用
mkdirp.sync(),不检查权限 - 权限错误时直接抛出异常
- 没有降级处理机制
HarmonyOS 文件系统权限模型详解
3.1 目录权限级别
| 目录类型 | 路径示例 | 创建文件夹权限 | 说明 |
|---|---|---|---|
| 应用沙箱 | /data/storage/.../files/ |
✅ 允许 | 应用完全控制 |
| 用户文档 | /storage/emulated/0/Documents/ |
⚠️ 需要权限 | 用户数据目录 |
| 下载目录 | /storage/emulated/0/Download/ |
⚠️ 需要权限 | 用户数据目录 |
| 系统目录 | /system/ |
❌ 禁止 | 系统保护目录 |
3.2 权限申请机制
根据 HarmonyOS 权限申请文档,应用需要申请权限:
// module.json5
{
"requestPermissions": [
{
"name": "ohos.permission.READ_MEDIA",
"reason": "读取用户文件",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.WRITE_MEDIA",
"reason": "写入用户文件",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
但即使申请了权限,某些操作仍可能失败。
权限检查与降级策略
4.1 权限检查函数
// utils/permission-checker.js
const fs = require('fs')
const path = require('path')
/**
* 检查是否有权限创建目录
*/
function canCreateDirectory(dirPath) {
try {
// 规范化路径
const normalizedPath = path.normalize(dirPath)
// 检查父目录是否存在且可写
const parentDir = path.dirname(normalizedPath)
if (!fs.existsSync(parentDir)) {
// 父目录不存在,尝试创建父目录
return canCreateDirectory(parentDir)
}
// 尝试创建测试目录
const testDir = path.join(normalizedPath, '.permission-test-' + Date.now())
fs.mkdirSync(testDir, { recursive: true })
// 清理测试目录
fs.rmdirSync(testDir)
return true
} catch (error) {
if (error.code === 'EPERM' || error.code === 'EACCES') {
return false
}
// 其他错误(如目录已存在)也返回 true
return true
}
}
module.exports = { canCreateDirectory }
4.2 降级策略设计
策略1:静默失败
- 创建失败时不抛出错误
- 返回
null或false - 调用方检查返回值
策略2:使用备选目录
- 如果用户目录失败,使用应用沙箱目录
- 提示用户文件保存在应用目录
策略3:功能降级
- 如果无法创建文件夹,跳过相关功能
- 确保其他功能正常
完整实现方案
5.1 createDir 函数优化
// files.js
const { canCreateDirectory } = require('./utils/permission-checker')
createDir: function (target) {
var parsedPath = parsePath(target)
var destDir = parsedPath.extname ? parsedPath.dirname : target
// ⚠️ HarmonyOS: 检查目录是否已存在
if (fs.existsSync && fs.existsSync(destDir)) {
try {
var stat = fs.statSync(destDir)
if (stat.isDirectory()) {
return destDir // 目录已存在,直接返回
}
} catch (e) {
// 如果检查失败,继续尝试创建
}
}
try {
// ⚠️ HarmonyOS: 检查权限
if (!canCreateDirectory(destDir)) {
console.warn('[HarmonyOS Files] No permission to create directory:', destDir)
return null // 返回 null,不抛出错误
}
// 尝试创建目录
mkdirp.sync(destDir)
return destDir
} catch (e) {
// ⚠️ HarmonyOS: 权限错误时,不抛出异常,而是返回 null
if (e.code === "EEXIST") {
// 目录已存在,正常情况
return destDir
} else if (e.code === "EPERM" || e.errno === -1) {
// HarmonyOS 权限错误,记录但不抛出异常
console.error('[HarmonyOS Files] Permission denied creating directory:', destDir, e.message)
return null
} else {
// 其他错误,记录但不抛出异常(避免阻止导出)
console.error('[HarmonyOS Files] Error creating directory:', destDir, e.message)
return null
}
}
}
5.2 调用方适配
// export-html.js
// 创建导出文件夹
var createdExportFolder = files.createDir(exportFolder)
if (!createdExportFolder) {
console.warn('[HarmonyOS Export] ⚠️ Failed to create export folder (permission denied)')
console.warn('[HarmonyOS Export] Using fallback strategy')
// 降级策略:使用应用沙箱目录
const appDataDir = app.getPath('userData')
const fallbackExportFolder = path.join(appDataDir, 'exports', folderBasename + '_files')
createdExportFolder = files.createDir(fallbackExportFolder)
if (createdExportFolder) {
console.log('[HarmonyOS Export] Using fallback export folder:', fallbackExportFolder)
exportFolder = fallbackExportFolder
} else {
console.error('[HarmonyOS Export] Fallback folder creation also failed')
// 继续执行,但不复制图片
}
}
5.3 错误处理最佳实践
// files.js
createDir: function (target, options = {}) {
const { silent = true, fallback = null } = options
var parsedPath = parsePath(target)
var destDir = parsedPath.extname ? parsedPath.dirname : target
try {
// 检查是否已存在
if (fs.existsSync && fs.existsSync(destDir)) {
const stat = fs.statSync(destDir)
if (stat.isDirectory()) {
return destDir
}
}
// 检查权限
if (!canCreateDirectory(destDir)) {
if (fallback) {
// 使用备选目录
return this.createDir(fallback, options)
}
if (silent) {
return null
} else {
throw new Error(`Permission denied: cannot create directory ${destDir}`)
}
}
// 创建目录
mkdirp.sync(destDir)
return destDir
} catch (e) {
if (e.code === "EEXIST") {
return destDir
}
if (silent) {
console.error('[HarmonyOS Files] Error creating directory:', destDir, e.message)
return null
} else {
throw e
}
}
}
最佳实践与注意事项
6.1 权限检查最佳实践
推荐做法:
// ✅ 好:检查权限后再操作
if (canCreateDirectory(dir)) {
files.createDir(dir)
} else {
// 降级处理
}
// ❌ 不好:直接操作,可能失败
files.createDir(dir) // 可能抛出异常
6.2 错误处理最佳实践
推荐做法:
// ✅ 好:静默失败,返回 null
const result = files.createDir(dir)
if (!result) {
// 降级处理
}
// ❌ 不好:直接抛出异常
files.createDir(dir) // 可能抛出异常,导致应用崩溃
6.3 降级策略最佳实践
推荐做法:
// ✅ 好:多重降级策略
let exportFolder = userSelectedFolder
if (!files.createDir(exportFolder)) {
// 降级1:使用应用沙箱目录
exportFolder = app.getPath('userData')
if (!files.createDir(exportFolder)) {
// 降级2:跳过文件夹创建,直接导出
exportFolder = null
}
}
常见问题解答
Q1: 为什么不能创建文件夹?
A: HarmonyOS 文件系统权限限制,应用不能在用户数据目录直接创建文件夹,需要用户授权或使用应用沙箱目录。
Q2: 如何提高创建成功率?
A:
- 检查权限后再操作
- 使用备选目录
- 实现降级策略
- 完善的错误处理
Q3: 降级策略会影响用户体验吗?
A: 不会。降级策略确保功能可用,即使权限不足也能部分工作,用户体验更好。
总结与展望
8.1 核心要点总结
通过本文的深入分析,我们了解到:
- 权限检查的重要性:检查权限后再操作,避免不必要的错误
- 降级策略的价值:确保功能在权限受限时仍能部分工作
- 错误处理的最佳实践:静默失败,返回 null,调用方处理
8.2 技术价值
这个解决方案不仅解决了文件系统权限问题,还带来了以下好处:
- ✅ 更好的兼容性:支持各种权限情况
- ✅ 更完善的错误处理:降级策略确保功能可用
- ✅ 更好的用户体验:即使权限不足也能部分工作
相关资源
Node.js 官方文档:
HarmonyOS 官方文档:
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)