前言在上一篇《鸿蒙 PC Electron 的权限管理深度解析:从应用权限申请到鸿蒙系统权限校验的全流程》中,我们拆解了权限管理的基础链路(静态声明→动态申请→系统校验),解决了 “权限如何合法获取” 的核心问题。但在实际开发中,Electron 应用适配鸿蒙 PC 及全场景鸿蒙设备时,还会面临更复杂的权限场景:

  • 权限动态调整:用户在鸿蒙 PC 上运行时撤销权限后,应用如何优雅降级而非崩溃?
  • 分布式权限协同:跨设备场景下(如鸿蒙 PC 发起→手机 / 平板执行文件读写,或手机发起→鸿蒙 PC 展示视频),权限如何跨设备同步校验?
  • 合规审计要求:鸿蒙应用市场及 PC 端软件合规标准强制要求的权限使用日志、敏感操作审计如何实现?
  • 细粒度权限控制:如何实现鸿蒙 PC 端 “仅允许访问特定目录文件”“临时授权单次使用摄像头 / 麦克风” 等精细化管控?
  • 鸿蒙 PC 专属适配:PC 端窗口化管理、多任务并发场景下,权限与进程生命周期的协同如何优化?

这些进阶场景直接决定了应用在鸿蒙 PC 端的稳定性、安全性与上架通过率。本文将聚焦鸿蒙 PC Electron 权限管理的 “进阶能力”,从动态管控、分布式协同、合规审计三个核心维度,结合鸿蒙 PC 端专属适配要点,附完整实战案例与可复用代码,深度解析复杂场景下的权限适配方案,同时附上鸿蒙应用市场及 PC 端合规要求与踩坑指南,帮助开发者实现权限管理 “从能用” 到 “好用、合规” 的升级。

一、权限动态管控:鸿蒙 PC 端全生命周期管理优化

鸿蒙系统允许用户在应用运行时随时撤销权限(如通过鸿蒙 PC“设置→应用→权限管理” 关闭文件读写权限),而 Electron 原生代码未处理该场景 —— 一旦权限被撤销,后续调用相关 API 会直接抛出异常,导致应用崩溃。尤其在鸿蒙 PC 端,多任务并发、窗口切换频繁的特性,更需要构建 “权限检测→动态申请→撤销处理→优雅降级” 的全生命周期管控机制,同时适配 PC 端的交互逻辑。

1.1 核心设计思路(新增鸿蒙 PC 适配要点)

  • 权限预检测:每次调用需权限的 API 前,先检测权限状态(已授权 / 未授权 / 已撤销),针对鸿蒙 PC 端文件系统(如本地磁盘分区访问、共享目录权限)做特殊适配;
  • 动态申请重试:未授权时触发申请,结合鸿蒙 PC 端大屏交互特点,优化权限申请弹窗的位置与交互逻辑,用户拒绝后提供 “去设置页开启” 的引导;
  • 撤销监听与降级:通过鸿蒙权限监听 API 实时感知权限撤销事件,针对鸿蒙 PC 端窗口化特性,自动隐藏需权限的功能模块而非直接关闭窗口,并给出清晰提示;
  • 临时权限支持:针对鸿蒙 PC 端高频使用的摄像头、麦克风(如视频会议场景),实现 “单次授权”“限时授权” 等精细化管控,适配 PC 端多场景切换需求;
  • 多窗口权限协同:鸿蒙 PC 端支持多窗口并发,需保证同一应用不同窗口的权限状态实时同步,避免 “一个窗口授权、另一个窗口权限缺失” 的问题。

1.2 权限状态枚举与检测工具类封装(新增鸿蒙 PC 适配)

首先封装权限状态枚举与通用检测工具,统一处理权限状态判断逻辑,新增鸿蒙 PC 端设备类型识别与专属权限检测:

// src/utils/permission-constants.js
/** 鸿蒙权限状态枚举(对应鸿蒙 PermissionStatus 常量) */
export const PermissionStatus = {
  GRANTED: 'granted', // 已授权
  DENIED: 'denied', // 未授权(用户未选择/拒绝)
  REVOKED: 'revoked', // 已撤销(用户运行时关闭)
  NEVER_ASK_AGAIN: 'never_ask_again', // 永久拒绝(用户勾选“不再询问”)
  TEMPORARY_GRANTED: 'temporary_granted' // 临时授权(单次/限时)
};

// src/utils/permission-detector.js
import { abilityAccessCtrl, bundleManager } from '@ohos.abilityAccessCtrl';
import { PermissionStatus } from './permission-constants';
import { logger } from './logger'; // 自定义日志工具(下文会实现)

/** 权限检测工具类(封装鸿蒙权限查询API,新增鸿蒙PC适配) */
class PermissionDetector {
  constructor() {
    this.abilityContext = globalThis.abilityContext; // 鸿蒙Ability上下文(需在Ability中初始化)
    this.acl = abilityAccessCtrl.createAtManager(); // 权限控制管理器
    this.isHarmonyPC = this.abilityContext.deviceType === 'pc'; // 识别是否为鸿蒙PC设备
  }

  /**
   * 查询单个权限状态(新增鸿蒙PC专属权限检测逻辑)
   * @param {string} permission 权限标识(如 ohos.permission.READ_USER_STORAGE)
   * @returns {Promise<PermissionStatus>} 权限状态
   */
  async checkSinglePermission(permission) {
    try {
      // 鸿蒙PC端特殊处理:本地文件读写权限需区分系统盘与数据盘
      if (this.isHarmonyPC && (permission === 'ohos.permission.READ_USER_STORAGE' || permission === 'ohos.permission.WRITE_USER_STORAGE')) {
        const result = await this.acl.checkPermission(
          abilityAccessCtrl.createAtManager().AUTHORIZATION,
          this.abilityContext.bundleName,
          permission
        );
        // 若PC端已授权,额外检测是否有系统盘访问权限(鸿蒙PC系统盘权限需单独校验)
        if (result === bundleManager.PermissionState.PERMISSION_GRANTED) {
          const systemDiskPermission = await this.checkSystemDiskPermission();
          if (!systemDiskPermission) {
            logger.warn(`鸿蒙PC端已授权${permission},但无系统盘访问权限`);
            // 此处可返回自定义状态,用于后续功能降级(仅允许访问数据盘)
          }
        }
        // 映射为自定义状态枚举
        switch (result) {
          case bundleManager.PermissionState.PERMISSION_GRANTED:
            return PermissionStatus.GRANTED;
          case bundleManager.PermissionState.PERMISSION_DENIED:
            // 进一步判断是否为永久拒绝(通过查询是否允许再次申请)
            const canRequest = await this.canRequestPermission(permission);
            return canRequest ? PermissionStatus.DENIED : PermissionStatus.NEVER_ASK_AGAIN;
          case bundleManager.PermissionState.PERMISSION_REVOKED:
            return PermissionStatus.REVOKED;
          default:
            return PermissionStatus.DENIED;
        }
      }

      // 非PC设备或非PC专属权限,执行原有逻辑
      const result = await this.acl.checkPermission(
        abilityAccessCtrl.createAtManager().AUTHORIZATION,
        this.abilityContext.bundleName,
        permission
      );

      switch (result) {
        case bundleManager.PermissionState.PERMISSION_GRANTED:
          return PermissionStatus.GRANTED;
        case bundleManager.PermissionState.PERMISSION_DENIED:
          const canRequest = await this.canRequestPermission(permission);
          return canRequest ? PermissionStatus.DENIED : PermissionStatus.NEVER_ASK_AGAIN;
        case bundleManager.PermissionState.PERMISSION_REVOKED:
          return PermissionStatus.REVOKED;
        default:
          return PermissionStatus.DENIED;
      }
    } catch (error) {
      logger.error(`查询权限 ${permission} 状态失败:`, error);
      return PermissionStatus.DENIED;
    }
  }

  /**
   * 鸿蒙PC端系统盘访问权限检测(新增)
   * @returns {Promise<boolean>} 是否拥有系统盘访问权限
   */
  async checkSystemDiskPermission() {
    try {
      if (!this.isHarmonyPC) return true; // 非PC设备无需检测
      // 调用鸿蒙PC专属API检测系统盘权限(需鸿蒙SDK 10+支持)
      const systemDiskResult = await this.acl.checkPermission(
        abilityAccessCtrl.createAtManager().AUTHORIZATION,
        this.abilityContext.bundleName,
        'ohos.permission.READ_SYSTEM_DISK' // 鸿蒙PC系统盘读取权限(仅PC端存在)
      );
      return systemDiskResult === bundleManager.PermissionState.PERMISSION_GRANTED;
    } catch (error) {
      logger.error('鸿蒙PC端系统盘权限检测失败:', error);
      return false;
    }
  }

  /**
   * 查询多个权限状态
   * @param {string[]} permissions 权限标识数组
   * @returns {Promise<Record<string, PermissionStatus>>} 键为权限标识,值为状态
   */
  async checkMultiplePermissions(permissions) {
    const result = {};
    for (const permission of permissions) {
      result[permission] = await this.checkSinglePermission(permission);
    }
    return result;
  }

  /**
   * 判断权限是否可再次申请(用户未勾选“不再询问”)
   * @param {string} permission 权限标识
   * @returns {Promise<boolean>} 是否可申请
   */
  async canRequestPermission(permission) {
    try {
      const bundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_PERMISSION);
      const reqPermission = bundleInfo.appInfo.reqPermissions.find(p => p.name === permission);
      if (!reqPermission) return false; // 未在配置文件中声明的权限,不可申请
      // 鸿蒙API判断是否允许再次请求
      return await this.acl.canRequestPermission(this.abilityContext, permission);
    } catch (error) {
      logger.error(`判断权限 ${permission} 是否可申请失败:`, error);
      return false;
    }
  }

  /**
   * 监听权限状态变化(如用户撤销权限),新增鸿蒙PC多窗口同步逻辑
   * @param {string[]} permissions 需监听的权限
   * @param {Function} callback 状态变化回调(参数:{permission: 权限标识, status: 新状态})
   */
  watchPermissionChanges(permissions, callback) {
    // 注册鸿蒙权限变化监听
    this.acl.on('permissionChange', (result) => {
      const { permission, status } = result;
      if (permissions.includes(permission)) {
        let mappedStatus = PermissionStatus.DENIED;
        switch (status) {
          case bundleManager.PermissionState.PERMISSION_GRANTED:
            mappedStatus = PermissionStatus.GRANTED;
            break;
          case bundleManager.PermissionState.PERMISSION_REVOKED:
            mappedStatus = PermissionStatus.REVOKED;
            break;
          case bundleManager.PermissionState.PERMISSION_DENIED:
            mappedStatus = this.canRequestPermission(permission)
              ? PermissionStatus.DENIED
              : PermissionStatus.NEVER_ASK_AGAIN;
            break;
        }
        callback({ permission, status: mappedStatus });

        // 鸿蒙PC端多窗口同步:通知应用所有窗口更新权限状态(新增)
        if (this.isHarmonyPC && globalThis.windowManager) {
          globalThis.windowManager.broadcast({
            type: 'PERMISSION_CHANGE',
            permission,
            status: mappedStatus
          });
        }
      }
    });
  }
}

// 导出单例(全局复用)
export const permissionDetector = new PermissionDetector();

1.3 动态权限申请与降级处理工具类(新增鸿蒙 PC 适配)

基于权限检测工具,封装动态申请逻辑,处理 “用户拒绝”“权限撤销” 后的降级策略,适配鸿蒙 PC 端交互与窗口管理特性:

// src/utils/permission-manager.js
import { abilityAccessCtrl } from '@ohos.abilityAccessCtrl';
import { permissionDetector } from './permission-detector';
import { PermissionStatus } from './permission-constants';
import { dialog } from '@ohos.ui.dialog'; // 鸿蒙系统弹窗API
import { logger } from './logger';

/** 权限管理工具类(动态申请+降级处理,新增鸿蒙PC适配) */
class PermissionManager {
  constructor() {
    this.acl = abilityAccessCtrl.createAtManager();
    this.abilityContext = globalThis.abilityContext;
    this.isHarmonyPC = this.abilityContext.deviceType === 'pc';
  }

  /**
   * 申请单个权限(带降级处理,新增鸿蒙PC弹窗优化)
   * @param {string} permission 权限标识
   * @param {Object} options 配置:{rationale: 申请理由, fallback: 降级函数}
   * @returns {Promise<boolean>} 是否申请成功
   */
  async requestSinglePermission(permission, { rationale = '需要该权限以提供对应功能', fallback }) {
    // 1. 先检测当前权限状态
    const status = await permissionDetector.checkSinglePermission(permission);

    switch (status) {
      case PermissionStatus.GRANTED:
      case PermissionStatus.TEMPORARY_GRANTED:
        return true; // 已授权,直接返回成功

      case PermissionStatus.DENIED:
        // 2. 未授权,显示申请理由弹窗(鸿蒙PC端优化弹窗位置与尺寸)
        const agree = await this.showRationaleDialog(rationale);
        if (!agree) {
          fallback?.(); // 用户不同意,执行降级
          return false;
        }

        // 3. 发起权限申请
        const requestResult = await this.acl.requestPermissionsFromUser(
          this.abilityContext,
          [permission]
        );

        const result = requestResult[0];
        if (result.granted) {
          return true;
        } else {
          // 4. 申请失败,判断是否为永久拒绝
          const canRequestAgain = await permissionDetector.canRequestPermission(permission);
          if (!canRequestAgain) {
            // 永久拒绝,引导用户去设置页开启(鸿蒙PC端跳转至应用权限详情页)
            await this.showGoToSettingsDialog(permission);
          }
          fallback?.();
          return false;
        }

      case PermissionStatus.REVOKED:
      case PermissionStatus.NEVER_ASK_AGAIN:
        // 权限已撤销或永久拒绝,引导去设置页
        await this.showGoToSettingsDialog(permission);
        fallback?.();
        return false;

      default:
        fallback?.();
        return false;
    }
  }

  /**
   * 申请多个权限(批量处理)
   * @param {string[]} permissions 权限标识数组
   * @param {Object} options 配置:{rationale: 申请理由, fallback: 降级函数}
   * @returns {Promise<Record<string, boolean>>} 每个权限的申请结果
   */
  async requestMultiplePermissions(permissions, { rationale, fallback }) {
    const result = {};
    let hasGranted = false;

    // 先过滤已授权的权限
    for (const permission of permissions) {
      const status = await permissionDetector.checkSinglePermission(permission);
      if (status === PermissionStatus.GRANTED) {
        result[permission] = true;
        hasGranted = true;
      } else {
        result[permission] = false;
      }
    }

    // 收集未授权的权限,批量申请
    const needRequestPermissions = permissions.filter(p => !result[p]);
    if (needRequestPermissions.length === 0) {
      return result;
    }

    // 显示申请理由弹窗(鸿蒙PC端适配大屏显示)
    const agree = await this.showRationaleDialog(rationale);
    if (!agree) {
      fallback?.();
      return result;
    }

    // 批量申请
    const requestResult = await this.acl.requestPermissionsFromUser(
      this.abilityContext,
      needRequestPermissions
    );

    requestResult.forEach(item => {
      result[item.permissionName] = item.granted;
      if (item.granted) hasGranted = true;
    });

    // 若所有权限都被拒绝,引导去设置页
    if (!hasGranted) {
      await this.showGoToSettingsDialog('所需权限');
      fallback?.();
    }

    return result;
  }

  /**
   * 显示权限申请理由弹窗(新增鸿蒙PC端优化)
   * @param {string} rationale 申请理由
   * @returns {Promise<boolean>} 用户是否同意申请
   */
  async showRationaleDialog(rationale) {
    return new Promise((resolve) => {
      const dialogConfig = {
        title: '权限申请',
        message: rationale,
        buttons: [
          { text: '取消', onClick: () => resolve(false) },
          { text: '允许', onClick: () => resolve(true) }
        ],
        cancel: () => resolve(false)
      };

      // 鸿蒙PC端优化:弹窗位置居中,尺寸适配大屏(新增)
      if (this.isHarmonyPC) {
        dialogConfig.position = { x: '50%', y: '50%' };
        dialogConfig.size = { width: 400, height: 200 }; // PC端弹窗尺寸
      }

      dialog.showAlertDialog(dialogConfig);
    });
  }

  /**
   * 显示“去设置页开启权限”弹窗(新增鸿蒙PC端适配)
   * @param {string} permission 权限标识(或描述)
   */
  async showGoToSettingsDialog(permission) {
    return new Promise((resolve) => {
      const dialogConfig = {
        title: '权限已关闭',
        message: `当前功能需要“${this.getPermissionDisplayName(permission)}”权限,请前往设置页开启`,
        buttons: [
          { text: '取消', onClick: () => resolve(false) },
          { text: '去设置', onClick: () => {
            // 跳转至应用权限设置页(鸿蒙PC端跳转至专属设置页面)
            if (this.isHarmonyPC) {
              this.abilityContext.startAbility({
                action: 'ohos.settings.applications.pcAppInfo', // 鸿蒙PC应用详情页action
                parameters: { bundleName: this.abilityContext.bundleName }
              });
            } else {
              this.abilityContext.startAbility({
                action: 'ohos.settings.applications.appInfo',
                parameters: { bundleName: this.abilityContext.bundleName }
              });
            }
            resolve(true);
          }}
        ],
        cancel: () => resolve(false)
      };

      // 鸿蒙PC端弹窗优化
      if (this.isHarmonyPC) {
        dialogConfig.position = { x: '50%', y: '50%' };
        dialogConfig.size = { width: 450, height: 220 };
      }

      dialog.showAlertDialog(dialogConfig);
    });
  }

  /**
   * 将权限标识转换为用户易懂的名称(新增鸿蒙PC专属权限名称映射)
   * @param {string} permission 权限标识
   * @returns {string} 显示名称
   */
  getPermissionDisplayName(permission) {
    const permissionMap = {
      'ohos.permission.READ_USER_STORAGE': '本地文件读取',
      'ohos.permission.WRITE_USER_STORAGE': '本地文件写入',
      'ohos.permission.READ_SYSTEM_DISK': '系统盘访问(鸿蒙PC)', // 新增PC专属权限名称
      'ohos.permission.CAMERA': '摄像头',
      'ohos.permission.MICROPHONE': '麦克风',
      'ohos.permission.DISTRIBUTED_FILE_ACCESS': '跨设备文件访问',
      'ohos.permission.INTERNET': '网络访问'
    };
    return permissionMap[permission] || permission;
  }

  /**
   * 临时授权(单次使用后自动撤销),适配鸿蒙PC端多场景切换
   * @param {string} permission 权限标识(仅支持dangerous级)
   * @param {Function} callback 授权后的回调函数
   */
  async temporaryAuthorize(permission, callback) {
    const isGranted = await this.requestSinglePermission(permission, {
      rationale: `需要临时使用“${this.getPermissionDisplayName(permission)}”,仅本次有效`,
      fallback: () => logger.warn(`临时授权${permission}失败`)
    });

    if (isGranted) {
      try {
        await callback(); // 执行单次操作
      } finally {
        // 操作完成后,主动释放权限(仅撤销临时授权,不影响用户手动授予的永久权限)
        const status = await permissionDetector.checkSinglePermission(permission);
        if (status === PermissionStatus.TEMPORARY_GRANTED) {
          await this.acl.revokePermission(this.abilityContext, permission);
          logger.info(`临时授权${permission}已自动撤销`);
        }
      }
    }
  }
}

export const permissionManager = new PermissionManager();

1.4 实战:鸿蒙 PC 端权限动态管控完整流程

以 Electron 应用的 “PC 端文件导出至本地 / 跨设备” 功能为例,演示权限全生命周期管控(适配鸿蒙 PC 多窗口、系统盘权限等特性):

// src/renderer/features/file-export.js
import { ipcRenderer } from 'electron';
import { permissionManager } from '../../utils/permission-manager';
import { PermissionStatus } from '../../utils/permission-constants';
import { logger } from '../../utils/logger';

// 初始化鸿蒙PC端多窗口权限同步监听(新增)
if (globalThis.windowManager) {
  globalThis.windowManager.on('broadcast', (msg) => {
    if (msg.type === 'PERMISSION_CHANGE') {
      const { permission, status } = msg;
      const requiredPermissions = [
        'ohos.permission.WRITE_USER_STORAGE',
        'ohos.permission.DISTRIBUTED_FILE_ACCESS',
        'ohos.permission.READ_SYSTEM_DISK'
      ];
      if (requiredPermissions.includes(permission)) {
        // 同步更新当前窗口的功能状态
        updateExportButtonStatus();
      }
    }
  });
}

// 导出文件按钮点击事件
document.getElementById('export-btn').addEventListener('click', async () => {
  // 1. 定义所需权限(文件写入+跨设备文件访问+鸿蒙PC系统盘权限)
  const requiredPermissions = [
    'ohos.permission.WRITE_USER_STORAGE',
    'ohos.permission.DISTRIBUTED_FILE_ACCESS'
  ];
  // 鸿蒙PC端额外添加系统盘权限(如需导出至系统盘)
  if (permissionManager.isHarmonyPC && document.getElementById('export-to-system-disk').checked) {
    requiredPermissions.push('ohos.permission.READ_SYSTEM_DISK');
  }

  // 2. 申请权限(带降级处理)
  const permissionResult = await permissionManager.requestMultiplePermissions(requiredPermissions, {
    rationale: permissionManager.isHarmonyPC 
      ? '需要文件写入权限以保存导出文件(系统盘导出需额外授权),跨设备文件访问权限以支持导出到其他鸿蒙设备'
      : '需要文件写入权限以保存导出文件,跨设备文件访问权限以支持导出到其他鸿蒙设备',
    fallback: () => {
      // 权限申请失败,降级处理:禁用导出功能,提示用户(PC端不关闭窗口)
      document.getElementById('export-btn').disabled = true;
      document.getElementById('export-tip').textContent = '权限不足,无法导出文件';
    }
  });

  // 3. 权限申请成功,执行导出逻辑
  const hasWritePermission = permissionResult['ohos.permission.WRITE_USER_STORAGE'];
  if (hasWritePermission) {
    try {
      const exportData = collectExportData(); // 收集要导出的数据
      // 调用主进程执行文件导出(主进程需适配鸿蒙文件系统API)
      const exportPath = await ipcRenderer.invoke('file:export', {
        data: exportData,
        format: 'xlsx',
        useDistributed: permissionResult['ohos.permission.DISTRIBUTED_FILE_ACCESS'],
        exportToSystemDisk: permissionManager.isHarmonyPC && permissionResult['ohos.permission.READ_SYSTEM_DISK']
      });
      showSuccessToast(`文件已导出至:${exportPath}`);
    } catch (error) {
      logger.error('文件导出失败:', error);
      showErrorToast('导出失败,请检查权限或存储空间');
    }
  }
});

// 4. 监听权限撤销事件(用户运行时关闭权限)
permissionManager.watchPermissionChanges([
  'ohos.permission.WRITE_USER_STORAGE',
  'ohos.permission.DISTRIBUTED_FILE_ACCESS',
  'ohos.permission.READ_SYSTEM_DISK'
], ({ permission, status }) => {
  if (status === PermissionStatus.REVOKED) {
    logger.warn(`权限${permission}已被撤销`);
    // 权限撤销后,自动降级(PC端保持窗口打开,仅禁用功能)
    document.getElementById('export-btn').disabled = true;
    document.getElementById('export-tip').textContent = `“${permissionManager.getPermissionDisplayName(permission)}”权限已关闭,无法导出文件`;
  } else if (status === PermissionStatus.GRANTED) {
    // 权限重新开启,恢复功能
    document.getElementById('export-btn').disabled = false;
    document.getElementById('export-tip').textContent = '';
  }
});

// 辅助函数:收集导出数据
function collectExportData() {
  const todoList = document.querySelectorAll('.todo-item');
  return Array.from(todoList).map(item => ({
    content: item.querySelector('span').textContent,
    done: item.querySelector('input').checked,
    createTime: item.dataset.createTime
  }));
}

// 辅助函数:显示提示弹窗(鸿蒙PC端优化弹窗样式)
function showSuccessToast(message) {
  const toastConfig = { message, duration: 2000, type: 'success' };
  if (permissionManager.isHarmonyPC) {
    toastConfig.position = { x: '90%', y: '10%' }; // PC端 toast 显示在右上角
  }
  dialog.showToast(toastConfig);
}

function showErrorToast(message) {
  const toastConfig = { message, duration: 2000, type: 'error' };
  if (permissionManager.isHarmonyPC) {
    toastConfig.position = { x: '90%', y: '10%' };
  }
  dialog.showToast(toastConfig);
}

// 辅助函数:更新导出按钮状态(适配PC端多窗口同步)
function updateExportButtonStatus() {
  permissionManager.checkMultiplePermissions([
    'ohos.permission.WRITE_USER_STORAGE',
    'ohos.permission.DISTRIBUTED_FILE_ACCESS',
    'ohos.permission.READ_SYSTEM_DISK'
  ]).then(status => {
    const hasRequired = status['ohos.permission.WRITE_USER_STORAGE'];
    document.getElementById('export-btn').disabled = !hasRequired;
    if (!hasRequired) {
      document.getElementById('export-tip').textContent = '权限不足,无法导出文件';
    } else {
      document.getElementById('export-tip').textContent = '';
    }
  });
}

延伸阅读:鸿蒙 PC 应用权限动态调整后的崩溃防护实践(CSDN 博客,详解 PC 端权限撤销后的异常处理技巧)

二、分布式权限协同:鸿蒙 PC 与多设备的权限同步校验

鸿蒙的核心特性是 “分布式协同”,在鸿蒙 PC 作为核心终端的场景下(如 PC 发起跨设备会议、文件同步),Electron 原生权限模型的 “本地局限” 问题更为突出。鸿蒙的分布式权限协同机制要求:跨设备操作的权限需 “主设备授权 + 从设备校验” 双重确认,且权限状态需跨设备同步。针对鸿蒙 PC,需重点适配 “PC 为主设备发起请求”“PC 为从设备响应请求” 两种核心场景。

2.1 分布式权限协同的核心原理(新增鸿蒙 PC 场景)

  • 权限归属:鸿蒙 PC 作为主设备时,需具备对应的分布式权限(如 ohos.permission.DISTRIBUTED_FILE_ACCESS),从设备(手机 / 平板)需具备本地权限;PC 作为从设备时,需校验本地权限并响应主设备请求;
  • 权限同步:鸿蒙 PC 发起跨设备请求时,需将自身的分布式权限状态同步至从设备,从设备校验本地权限后返回结果;
  • 临时授权传递:若从设备未授权,可通过鸿蒙 PC 的大屏弹窗引导用户在从设备上授权,授权结果同步至 PC 端;
  • 鸿蒙 PC 专属优化:PC 端网络稳定性更强,支持大文件跨设备传输,权限校验需适配 “长连接场景”,避免频繁授权弹窗。

2.2 分布式权限协同的技术实现(主从设备通信,新增 PC 适配)

基于鸿蒙 DistributedDeviceManagerDistributedDataManager,实现跨设备权限同步与校验,重点适配鸿蒙 PC 作为主 / 从设备的场景:

2.2.1 主设备(鸿蒙 PC):权限发起与同步逻辑
// main/distributed/permission-master.js
import { DistributedDeviceManager } from '@ohos.distributedDeviceManager';
import { DistributedDataManager } from '@ohos.distributedDataManager';
import { permissionManager } from '../src/utils/permission-manager';
import { permissionDetector } from '../src/utils/permission-detector';
import { logger } from '../src/utils/logger';

// 分布式设备管理器(主设备)
const deviceManager = new DistributedDeviceManager();
// 分布式数据管理器(用于同步权限状态)
const dataManager = new DistributedDataManager();

/**
 * 主设备(优先适配鸿蒙PC)发起跨设备权限请求
 * @param {string} targetDeviceId 从设备ID
 * @param {string[]} requiredPermissions 从设备需具备的本地权限
 * @param {string} distributedPermission 主设备需具备的分布式权限
 * @returns {Promise<boolean>} 从设备是否授权
 */
export async function requestDistributedPermission(
  targetDeviceId,
  requiredPermissions,
  distributedPermission
) {
  try {
    // 1. 主设备先校验自身分布式权限
    const masterPermissionStatus = await permissionDetector.checkSinglePermission(distributedPermission);
    if (masterPermissionStatus !== PermissionStatus.GRANTED) {
      // 主设备无分布式权限,发起申请(鸿蒙PC端优化申请理由)
      const rationale = permissionManager.isHarmonyPC
        ? `需要跨设备协同权限以操作${targetDeviceId}上的资源,适配PC端多设备协同场景`
        : `需要跨设备协同权限以操作${targetDeviceId}上的资源`;
      const isGranted = await permissionManager.requestSinglePermission(distributedPermission, {
        rationale,
        fallback: () => logger.error(`主设备分布式权限${distributedPermission}申请失败`)
      });
      if (!isGranted) return false;
    }

    // 2. 鸿蒙PC端特殊处理:建立长连接以保障大文件传输时的权限稳定性(新增)
    if (permissionManager.isHarmonyPC) {
      await deviceManager.establishPersistentConnection(targetDeviceId);
      logger.info(`鸿蒙PC与从设备${targetDeviceId}建立长连接`);
    }

    // 3. 向从设备发送权限校验请求(通过分布式数据同步)
    const requestId = `distributed-perm-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`;
    // 存储请求状态(用于接收从设备响应)
    await dataManager.put({
      key: `perm_request_${requestId}`,
      value: JSON.stringify({
        masterDeviceId: deviceManager.getLocalDeviceId(),
        masterDeviceType: permissionManager.isHarmonyPC ? 'pc' : 'mobile', // 新增设备类型标识
        requiredPermissions,
        timestamp: Date.now()
      }),
      deviceIds: [targetDeviceId], // 仅同步至目标从设备
      syncMode: 'realTime' // 实时同步
    });

    // 4. 等待从设备响应(鸿蒙PC端超时时间延长至15秒,适配大文件场景)
    const timeout = permissionManager.isHarmonyPC ? 15000 : 10000;
    return new Promise((resolve) => {
      let timeoutTimer = setTimeout(() => {
        logger.error(`跨设备权限请求超时(设备ID:${targetDeviceId})`);
        // 鸿蒙PC端断开长连接
        if (permissionManager.isHarmonyPC) {
          deviceManager.closePersistentConnection(targetDeviceId);
        }
        resolve(false);
      }, timeout);

      // 监听从设备的响应
      dataManager.onDataChange(`perm_response_${requestId}`, async (response) => {
        clearTimeout(timeoutTimer);
        const responseData = JSON.parse(response);
        if (responseData.allowed) {
          logger.info(`从设备${targetDeviceId}已授权所需权限`);
          resolve(true);
        } else {
          logger.warn(`从设备${targetDeviceId}拒绝权限请求:${responseData.reason}`);
          resolve(false);
        }
        // 清理临时数据
        await dataManager.delete(`perm_request_${requestId}`);
        await dataManager.delete(`perm_response_${requestId}`);
        // 鸿蒙PC端断开长连接(若无需持续通信)
        if (permissionManager.isHarmonyPC) {
          deviceManager.closePersistentConnection(targetDeviceId);
        }
      });
    });
  } catch (error) {
    logger.error(`跨设备权限请求失败:`, error);
    // 异常时断开PC端长连接
    if (permissionManager.isHarmonyPC && targetDeviceId) {
      deviceManager.closePersistentConnection(targetDeviceId);
    }
    return false;
  }
}
2.2.2 从设备(含鸿蒙 PC):权限校验与响应逻辑
// main/distributed/permission-slave.js
import { DistributedDataManager } from '@ohos.distributedDataManager';
import { permissionManager } from '../src/utils/permission-manager';
import { permissionDetector } from '../src/utils/permission-detector';
import { logger } from '../src/utils/logger';

const dataManager = new DistributedDataManager();

/**
 * 从设备(含鸿蒙PC)监听并处理主设备的权限请求
 */
export function initSlavePermissionListener() {
  // 监听主设备发送的权限请求
  dataManager.onDataChange('perm_request_*', async (data, key) => {
    const requestId = key.split('_')[2]; // 提取请求ID
    const requestData = JSON.parse(data);
    const { masterDeviceId, masterDeviceType, requiredPermissions } = requestData;

    logger.info(`收到主设备${masterDeviceId}(${masterDeviceType})的权限请求:`, requiredPermissions);

    // 1. 从设备校验本地权限
    const permissionStatus = await permissionDetector.checkMultiplePermissions(requiredPermissions);
    const missingPermissions = requiredPermissions.filter(perm => !permissionStatus[perm]);

    let allowed = false;
    let reason = '';

    if (missingPermissions.length === 0) {
      // 2. 所有权限已具备,直接同意
      allowed = true;
    } else {
      // 3. 部分权限缺失,发起动态申请(鸿蒙PC端优化申请理由,适配大屏交互)
      const rationale = masterDeviceType === 'pc'
        ? `鸿蒙PC设备${masterDeviceId}发起跨设备协同,需要以下权限:${missingPermissions.map(p => permissionManager.getPermissionDisplayName(p)).join('、')}`
        : `主设备${masterDeviceId}发起跨设备协同,需要以下权限:${missingPermissions.map(p => permissionManager.getPermissionDisplayName(p)).join('、')}`;

      const requestResult = await permissionManager.requestMultiplePermissions(missingPermissions, {
        rationale,
        fallback: () => {
          reason = `缺失权限:${missingPermissions.join(',')}`;
        }
      });

      // 检查是否所有缺失权限都已申请成功
      allowed = missingPermissions.every(perm => requestResult[perm]);
      if (!allowed) {
        reason = `部分权限申请失败:${missingPermissions.filter(perm => !requestResult[perm]).join(',')}`;
      }
    }

    // 4. 向主设备发送响应
    await dataManager.put({
      key: `perm_response_${requestId}`,
      value: JSON.stringify({ allowed, reason, timestamp: Date.now() }),
      deviceIds: [masterDeviceId],
      syncMode: 'realTime'
    });

    logger.info(`向主设备${masterDeviceId}返回权限响应:`, { allowed, reason });
  });
}

2.3 实战:鸿蒙 PC 作为主设备的跨设备文件导出权限协同流程

结合前文的文件导出功能,实现 “鸿蒙 PC(主设备)→ 手机 / 平板(从设备)” 的跨设备导出:

// main/ipc-handlers/file-handler.js
import { ipcMain } from 'electron';
import { fileSystem } from '@ohos.file.fs'; // 鸿蒙文件系统API
import { requestDistributedPermission } from '../distributed/permission-master';
import { initSlavePermissionListener } from '../distributed/permission-slave';
import { logger } from '../src/utils/logger';

// 从设备初始化权限监听(所有设备都需执行,适配主从角色切换,含鸿蒙PC)
initSlavePermissionListener();

// 注册文件导出IPC处理函数
ipcMain.handle('file:export', async (event, { data, format, useDistributed, exportToSystemDisk }) => {
  if (!useDistributed) {
    // 本地导出:直接使用本地文件权限(鸿蒙PC端区分系统盘与数据盘)
    if (permissionManager.isHarmonyPC && exportToSystemDisk) {
      return exportToSystemDiskStorage(data, format);
    } else {
      return exportToLocalStorage(data, format);
    }
  }

  // 跨设备导出:获取已配对的从设备(如手机/平板)
  const pairedDevices = await deviceManager.getPairedDevices();
  if (pairedDevices.length === 0) {
    throw new Error('无已配对的跨设备,请先通过鸿蒙多屏协同配对');
  }

  const targetDeviceId = pairedDevices[0].deviceId; // 取第一个配对设备
  // 1. 发起跨设备权限请求:从设备需具备文件写入权限
  const isPermissionGranted = await requestDistributedPermission(
    targetDeviceId,
    ['ohos.permission.WRITE_USER_STORAGE'], // 从设备本地权限
    'ohos.permission.DISTRIBUTED_FILE_ACCESS' // 主设备(PC)分布式权限
  );

  if (!isPermissionGranted) {
    throw new Error('跨设备权限申请失败,无法导出文件');
  }

  // 2. 权限通过,执行跨设备文件写入(鸿蒙PC端支持大文件分片传输)
  return exportToDistributedStorage(targetDeviceId, data, format);
});

/**
 * 本地文件导出(数据盘)
 * @param {Object[]} data 导出数据
 * @param {string} format 格式(xlsx/csv)
 * @returns {string} 导出路径
 */
async function exportToLocalStorage(data, format) {
  const fileName = `todo_export_${Date.now()}.${format}`;
  // 鸿蒙应用沙箱目录(无需额外权限,仅应用可访问)
  const sandboxPath = `${globalThis.abilityContext.filesDir}/${fileName}`;
  // 写入文件(鸿蒙文件系统API)
  await fileSystem.writeFile(sandboxPath, JSON.stringify(data), 'utf-8');
  return sandboxPath;
}

/**
 * 鸿蒙PC端系统盘文件导出(新增)
 * @param {Object[]} data 导出数据
 * @param {string} format 格式(xlsx/csv)
 * @returns {string} 导出路径
 */
async function exportToSystemDiskStorage(data, format) {
  const fileName = `todo_export_${Date.now()}.${format}`;
  // 鸿蒙PC系统盘默认导出目录(需系统盘权限)
  const systemDiskPath = `/mnt/system_disk/Documents/${fileName}`;
  try {
    await fileSystem.writeFile(systemDiskPath, JSON.stringify(data), 'utf-8');
    return systemDiskPath;
  } catch (error) {
    logger.error('鸿蒙PC端系统盘导出失败:', error);
    // 降级导出至数据盘
    return exportToLocalStorage(data, format);
  }
}

/**
 * 跨设备文件导出
 * @param {string} targetDeviceId 从设备ID
 * @param {Object[]} data 导出数据
 * @param {string} format 格式
 * @returns {string} 从设备上的导出路径
 */
async function exportToDistributedStorage(targetDeviceId, data, format) {
  const fileName = `todo_export_${Date.now()}.${format}`;
  // 从设备的共享目录路径(需从设备文件写入权限)
  const distributedPath = `distributed://${targetDeviceId}/storage/emulated/0/Documents/${fileName}`;
  
  // 鸿蒙PC端大文件处理:分片传输(新增)
  if (permissionManager.isHarmonyPC && JSON.stringify(data).length > 1024 * 1024 * 10) { // 10MB以上为大文件
    await fileSystem.writeFileWithChunk(distributedPath, JSON.stringify(data), 'utf-8', { chunkSize: 1024 * 1024 });
  } else {
    await fileSystem.writeFile(distributedPath, JSON.stringify(data), 'utf-8');
  }
  
  return distributedPath;
}

官方参考:鸿蒙分布式权限协同开发指南(含 PC 端适配)(详细说明主从设备权限交互逻辑)

三、权限合规审计:鸿蒙 PC 端上架合规要求

鸿蒙应用市场及 PC 端软件合规标准对权限使用有严格要求,核心包括:

  • 权限使用日志:需记录敏感权限的调用时间、操作内容、权限状态,鸿蒙 PC 端需额外记录设备型号、操作系统版本;
  • 敏感操作审计:涉及用户隐私的权限(如摄像头、文件读写),需留存操作日志至少 7 天,PC 端支持日志本地导出;
  • 权限申请合理性:禁止 “过度申请权限”“申请与功能无关的权限”,PC 端需明确区分系统盘与数据盘权限,不默认申请系统盘权限;
  • PC 端专属合规:需提供 “权限管理中心” 入口,用户可在应用内快速开关权限,无需跳转至系统设置;支持权限使用记录查询。

3.1 权限审计日志工具类实现(新增鸿蒙 PC 适配)

封装日志收集工具,按鸿蒙 PC 端合规要求记录权限相关操作,支持日志本地导出:

// src/utils/permission-audit.js
import { fileSystem } from '@ohos.file.fs';
import { abilityContext } from '@ohos.ability';
import { logger } from './logger';
import { PermissionStatus } from './permission-constants';
import { permissionManager } from './permission-manager';

/** 权限审计日志工具类(满足鸿蒙应用市场及PC端合规要求) */
class PermissionAuditor {
  constructor() {
    // 日志存储路径(鸿蒙PC端支持自定义存储目录,默认沙箱+本地文档目录)
    this.baseLogDir = `${abilityContext.filesDir}/permission_audit_logs`;
    this.pcLogDir = permissionManager.isHarmonyPC 
      ? `${abilityContext.dataDir}/Documents/permission_audit_logs` // PC端额外存储至文档目录
      : this.baseLogDir;
    this.initLogDir(); // 初始化日志目录
  }

  /** 初始化日志目录(不存在则创建) */
  async initLogDir() {
    try {
      // 初始化沙箱目录
      const baseExists = await fileSystem.access(this.baseLogDir);
      if (!baseExists) {
        await fileSystem.mkdir(this.baseLogDir, { recursive: true });
      }
      // 鸿蒙PC端额外初始化文档目录
      if (permissionManager.isHarmonyPC) {
        const pcExists = await fileSystem.access(this.pcLogDir);
        if (!pcExists) {
          await fileSystem.mkdir(this.pcLogDir, { recursive: true });
        }
      }
    } catch (error) {
      logger.error('初始化权限审计日志目录失败:', error);
    }
  }

  /**
   * 记录权限申请日志(新增鸿蒙PC端设备信息)
   * @param {string} permission 权限标识
   * @param {string} status 申请结果(成功/失败)
   * @param {string} reason 申请理由/失败原因
   */
  async logPermissionRequest(permission, status, reason) {
    const logData = this.generateLogData({
      type: 'PERMISSION_REQUEST',
      permission,
      permissionName: permissionManager.getPermissionDisplayName(permission),
      status,
      reason,
      operation: '申请权限'
    });
    await this.writeLog(logData);
  }

  /**
   * 记录权限使用日志(敏感权限调用时,新增PC端操作详情)
   * @param {string} permission 权限标识
   * @param {string} operation 操作内容(如“读取文件”“调用摄像头”)
   * @param {string} detail 操作详情(如文件路径、操作结果)
   */
  async logPermissionUsage(permission, operation, detail) {
    const logData = this.generateLogData({
      type: 'PERMISSION_USAGE',
      permission,
      permissionName: permissionManager.getPermissionDisplayName(permission),
      operation,
      detail,
      status: 'SUCCESS'
    });
    await this.writeLog(logData);
  }

  /**
   * 记录权限状态变更日志(如撤销、重新授权)
   * @param {string} permission 权限标识
   * @param {PermissionStatus} oldStatus 旧状态
   * @param {PermissionStatus} newStatus 新状态
   */
  async logPermissionChange(permission, oldStatus, newStatus) {
    const logData = this.generateLogData({
      type: 'PERMISSION_CHANGE',
      permission,
      permissionName: permissionManager.getPermissionDisplayName(permission),
      oldStatus,
      newStatus,
      operation: '权限状态变更'
    });
    await this.writeLog(logData);
  }

  /**
   * 生成统一格式的日志数据(新增鸿蒙PC端设备信息)
   * @param {Object} customData 自定义日志字段
   * @returns {Object} 完整日志数据
   */
  generateLogData(customData) {
    const baseData = {
      timestamp: new Date().toISOString(), // 时间戳(ISO格式)
      deviceId: abilityContext.deviceId, // 设备ID
      deviceType: permissionManager.isHarmonyPC ? 'pc' : 'mobile', // 设备类型
      bundleName: abilityContext.bundleName, // 应用包名
      processId: process.pid, // 进程ID
      appVersion: abilityContext.appInfo.versionName, // 应用版本
      ...customData
    };

    // 鸿蒙PC端额外添加设备信息(新增)
    if (permissionManager.isHarmonyPC) {
      return {
        ...baseData,
        osVersion: abilityContext.osInfo.version, // 鸿蒙PC系统版本
        deviceModel: abilityContext.deviceInfo.model, // PC设备型号
        windowId: globalThis.windowId || 'main_window', // 当前窗口ID(PC端多窗口)
      };
    }

    return baseData;
  }

  /**
   * 写入日志到文件(按日期拆分日志文件,鸿蒙PC端双目录存储)
   * @param {Object} logData 日志数据
   */
  async writeLog(logData) {
    try {
      const date = new Date().toLocaleDateString().replace(/\//g, '-');
      const logFileName = `audit_${date}.log`;
      const baseLogFilePath = `${this.baseLogDir}/${logFileName}`;
      const logLine = JSON.stringify(logData) + '\n';

      // 写入沙箱目录(所有设备通用)
      await fileSystem.appendFile(baseLogFilePath, logLine, 'utf-8');

      // 鸿蒙PC端额外写入文档目录(方便用户导出查看)
      if (permissionManager.isHarmonyPC) {
        const pcLogFilePath = `${this.pcLogDir}/${logFileName}`;
        await fileSystem.appendFile(pcLogFilePath, logLine, 'utf-8');
      }

      // 日志轮转:保留最近30天的日志
      await this.cleanOldLogs();
    } catch (error) {
      logger.error('写入权限审计日志失败:', error);
    }
  }

  /** 清理30天前的日志文件(鸿蒙PC端双目录清理) */
  async cleanOldLogs() {
    try {
      const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;

      // 清理沙箱目录日志
      const baseFiles = await fileSystem.readdir(this.baseLogDir);
      for (const file of baseFiles) {
        await this.cleanLogFile(this.baseLogDir, file, thirtyDaysAgo);
      }

      // 鸿蒙PC端清理文档目录日志
      if (permissionManager.isHarmonyPC) {
        const pcFiles = await fileSystem.readdir(this.pcLogDir);
        for (const file of pcFiles) {
          await this.cleanLogFile(this.pcLogDir, file, thirtyDaysAgo);
        }
      }
    } catch (error) {
      logger.error('清理过期日志失败:', error);
    }
  }

  /** 清理单个过期日志文件 */
  async cleanLogFile(dir, file, threshold) {
    const match = file.match(/audit_(\d{4}-\d{2}-\d{2})\.log/);
    if (!match) return;

    const logDate = new Date(match[1]);
    if (logDate.getTime() < threshold) {
      const filePath = `${dir}/${file}`;
      await fileSystem.unlink(filePath);
      logger.info(`清理过期日志文件:${filePath}`);
    }
  }

  /**
   * 导出审计日志(鸿蒙PC端支持导出至本地文档,供用户/审核人员查看)
   * @returns {string} 日志导出路径
   */
  async exportAuditLogs() {
    const exportFileName = `permission_audit_export_${Date.now()}.log`;
    let exportPath = '';

    if (permissionManager.isHarmonyPC) {
      // 鸿蒙PC端导出至文档目录(用户可直接访问)
      exportPath = `${abilityContext.dataDir}/Documents/${exportFileName}`;
    } else {
      exportPath = `${abilityContext.filesDir}/${exportFileName}`;
    }

    // 读取所有日志文件并合并导出
    const logDir = permissionManager.isHarmonyPC ? this.pcLogDir : this.baseLogDir;
    const files = await fileSystem.readdir(logDir);

    for (const file of files) {
      const filePath = `${logDir}/${file}`;
      const content = await fileSystem.readFile(filePath, 'utf-8');
      await fileSystem.appendFile(exportPath, content, 'utf-8');
    }

    return exportPath;
  }

  /**
   * 鸿蒙PC端专属:查询权限使用记录(供应用内权限管理中心展示)
   * @param {number} days 查询最近天数(默认7天)
   * @returns {Object[]} 权限使用记录列表
   */
  async queryPermissionUsageRecords(days = 7) {
    if (!permissionManager.isHarmonyPC) return [];

    const records = [];
    const threshold = Date.now() - days * 24 * 60 * 60 * 1000;
    const files = await fileSystem.readdir(this.pcLogDir);

    for (const file of files) {
      const match = file.match(/audit_(\d{4}-\d{2}-\d{2})\.log/);
      if (!match) continue;

      const logDate = new Date(match[1]);
      if (logDate.getTime() < threshold) continue;

      const filePath = `${this.pcLogDir}/${file}`;
      const content = await fileSystem.readFile(filePath, 'utf-8');
      const lines = content.split('\n').filter(line => line);

      lines.forEach(line => {
        const record = JSON.parse(line);
        if (record.type === 'PERMISSION_USAGE') {
          records.push(record);
        }
      });
    }

    // 按时间戳降序排序
    return records.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
  }
}

export const permissionAuditor = new PermissionAuditor();

3.2 审计日志的集成使用(新增鸿蒙 PC 端权限管理中心)

在权限申请、使用、状态变更的关键节点插入审计日志,并为鸿蒙 PC 端添加 “权限管理中心” 功能:

// 改造permission-manager.js,添加日志记录
import { permissionAuditor } from './permission-audit';

// 在requestSinglePermission方法中,申请结果返回后添加:
permissionAuditor.logPermissionRequest(
  permission,
  isGranted ? 'SUCCESS' : 'FAIL',
  isGranted ? '用户同意授权' : reason
);

// 在watchPermissionChanges回调中添加:
permissionAuditor.logPermissionChange(permission, oldStatus, status);

// 改造文件导出逻辑,添加权限使用日志:
// src/renderer/features/file-export.js
async function exportToLocalStorage(data, format) {
  // ... 原有逻辑 ...
  // 记录文件写入权限的使用日志
  permissionAuditor.logPermissionUsage(
    'ohos.permission.WRITE_USER_STORAGE',
    '导出文件',
    `文件路径:${sandboxPath},数据量:${JSON.stringify(data).length}字节`
  );
  return sandboxPath;
}

// 鸿蒙PC端添加权限管理中心入口(新增)
if (permissionManager.isHarmonyPC) {
  // 渲染权限管理中心UI
  renderPermissionCenter();

  // 绑定日志导出按钮事件
  document.getElementById('export-audit-logs').addEventListener('click', async () => {
    const exportPath = await permissionAuditor.exportAuditLogs();
    showSuccessToast(`权限审计日志已导出至:${exportPath}`);
  });

  // 绑定权限使用记录查询事件
  document.getElementById('query-permission-usage').addEventListener('click', async () => {
    const records = await permissionAuditor.queryPermissionUsageRecords(7);
    renderPermissionUsageRecords(records);
  });
}

// 鸿蒙PC端渲染权限管理中心UI(新增)
function renderPermissionCenter() {
  const permissionCenterHtml = `
    <div class="permission-center" style="margin: 20px; padding: 20px; border: 1px solid #eee; border-radius: 8px;">
      <h3>权限管理中心(鸿蒙PC专属)</h3>
      <div class="permission-item">
        <label>本地文件读取权限</label>
        <button id="check-read-storage">查看状态</button>
        <button id="goto-read-storage-settings">去设置</button>
      </div>
      <div class="permission-item">
        <label>系统盘访问权限</label>
        <button id="check-system-disk">查看状态</button>
        <button id="goto-system-disk-settings">去设置</button>
      </div>
      <div class="permission-item">
        <label>跨设备协同权限</label>
        <button id="check-distributed">查看状态</button>
        <button id="goto-distributed-settings">去设置</button>
      </div>
      <div style="margin-top: 20px;">
        <button id="query-permission-usage">查询7天权限使用记录</button>
        <button id="export-audit-logs">导出审计日志</button>
      </div>
      <div id="permission-usage-records" style="margin-top: 20px; max-height: 300px; overflow-y: auto;"></div>
    </div>
  `;
  document.body.insertAdjacentHTML('beforeend', permissionCenterHtml);

  // 绑定权限状态查看按钮事件
  document.getElementById('check-read-storage').addEventListener('click', async () => {
    const status = await permissionDetector.checkSinglePermission('ohos.permission.READ_USER_STORAGE');
    showSuccessToast(`本地文件读取权限状态:${status}`);
  });

  document.getElementById('check-system-disk').addEventListener('click', async () => {
    const status = await permissionDetector.checkSinglePermission('ohos.permission.READ_SYSTEM_DISK');
    showSuccessToast(`系统盘访问权限状态:${status}`);
  });

  document.getElementById('check-distributed').addEventListener('click', async () => {
    const status = await permissionDetector.checkSinglePermission('ohos.permission.DISTRIBUTED_FILE_ACCESS');
    showSuccessToast(`跨设备协同权限状态:${status}`);
  });

  // 绑定跳转设置按钮事件
  document.getElementById('goto-read-storage-settings').addEventListener('click', () => {
    permissionManager.showGoToSettingsDialog('ohos.permission.READ_USER_STORAGE');
  });

  document.getElementById('goto-system-disk-settings').addEventListener('click', () => {
    permissionManager.showGoToSettingsDialog('ohos.permission.READ_SYSTEM_DISK');
  });

  document.getElementById('goto-distributed-settings').addEventListener('click', () => {
    permissionManager.showGoToSettingsDialog('ohos.permission.DISTRIBUTED_FILE_ACCESS');
  });
}

// 渲染权限使用记录(新增)
function renderPermissionUsageRecords(records) {
  const recordsContainer = document.getElementById('permission-usage-records');
  if (records.length === 0) {
    recordsContainer.innerHTML = '<p>暂无权限使用记录</p>';
    return;
  }

  const recordsHtml = records.map(record => `
    <div style="padding: 10px; border-bottom: 1px solid #eee;">
      <p>时间:${record.timestamp}</p>
      <p>权限:${record.permissionName}</p>
      <p>操作:${record.operation}</p>
      <p>详情:${record.detail}</p>
    </div>
  `).join('');

  recordsContainer.innerHTML = recordsHtml;
}

3.3 鸿蒙 PC 端合规校验要点(新增)

  1. 权限声明合理性:

    • 鸿蒙 PC 端需明确区分 “系统盘权限” 与 “数据盘权限”,仅在用户选择导出至系统盘时才申请ohos.permission.READ_SYSTEM_DISK
    • 禁止默认申请 PC 端摄像头、麦克风权限,仅在用户触发相关功能(如视频会议、语音输入)时才申请。
  2. 权限使用合规性:

    • 鸿蒙 PC 端后台运行时,禁止调用摄像头、麦克风等敏感权限,需在应用进入后台时自动释放临时权限;
    • 支持用户在应用内快速开关权限(通过权限管理中心),无需跳转至系统设置。
  3. 审计日志要求:

    • 鸿蒙 PC 端日志需支持本地导出(导出至文档目录),日志格式为 UTF-8 编码,支持用文本编辑器直接打开;
    • 日志中不可包含用户隐私数据(如文件内容、摄像头拍摄的画面),仅记录操作行为与路径。
  4. PC 端专属交互合规:

    • 权限申请弹窗需适配 PC 端大屏,避免弹窗过小或位置不合理;
    • 权限撤销后,应用需优雅降级(禁用相关功能而非关闭窗口),并给出清晰的提示文案。

合规参考:鸿蒙 PC 应用合规检查指南(官方 PC 端上架权限校验标准)

四、实战案例:鸿蒙 PC 跨设备视频会议应用权限适配

以 “鸿蒙 PC 跨设备视频会议 Electron 应用” 为例,整合动态管控、分布式协同、合规审计三大能力,实现全场景权限适配:

4.1 应用核心功能与权限需求(新增鸿蒙 PC 场景)

功能模块 所需权限(本地) 所需权限(分布式) 权限等级 鸿蒙 PC 专属适配
本地视频通话 CAMERA(摄像头)、MICROPHONE(麦克风) - dangerous 适配 PC 端摄像头切换(外接 / 内置)、麦克风音量调节
跨设备视频共享 CAMERA、MICROPHONE DISTRIBUTED_DEVICE_MANAGER(设备管理)、DISTRIBUTED_DATA_MANAGER(数据同步) dangerous PC 作为主设备发起共享,支持大屏预览从设备画面
会议录制 CAMERA、MICROPHONE、WRITE_USER_STORAGE(文件写入) DISTRIBUTED_FILE_ACCESS(跨设备文件访问) dangerous 支持录制文件导出至 PC 系统盘 / 数据盘,支持大文件分片存储
屏幕共享(PC 专属) ohos.permission.SCREEN_CAPTURE(屏幕捕获) DISTRIBUTED_SCREEN_SHARE(跨设备屏幕共享) dangerous 适配 PC 端多屏幕切换、窗口选择共享功能

4.2 完整适配代码(核心片段,含鸿蒙 PC 专属功能)

// src/renderer/features/video-conference.js
import { ipcRenderer } from 'electron';
import { permissionManager } from '../../utils/permission-manager';
import { permissionAuditor } from '../../utils/permission-audit';
import { PermissionStatus } from '../../utils/permission-constants';

// 核心权限列表(含鸿蒙PC专属权限)
const LOCAL_PERMISSIONS = ['ohos.permission.CAMERA', 'ohos.permission.MICROPHONE'];
const DISTRIBUTED_PERMISSIONS = [
  'ohos.permission.DISTRIBUTED_DEVICE_MANAGER',
  'ohos.permission.DISTRIBUTED_DATA_MANAGER'
];
const RECORD_PERMISSIONS = [...LOCAL_PERMISSIONS, 'ohos.permission.WRITE_USER_STORAGE', 'ohos.permission.DISTRIBUTED_FILE_ACCESS'];
// 鸿蒙PC端添加屏幕共享权限
if (permissionManager.isHarmonyPC) {
  LOCAL_PERMISSIONS.push('ohos.permission.SCREEN_CAPTURE');
  DISTRIBUTED_PERMISSIONS.push('ohos.permission.DISTRIBUTED_SCREEN_SHARE');
}

// 启动本地视频通话(适配鸿蒙PC摄像头切换)
document.getElementById('start-local-call').addEventListener('click', async () => {
  // 申请本地权限
  const result = await permissionManager.requestMultiplePermissions(LOCAL_PERMISSIONS, {
    rationale: permissionManager.isHarmonyPC
      ? '需要摄像头(支持外接/内置切换)、麦克风及屏幕捕获权限以进行本地视频通话'
      : '需要摄像头和麦克风权限以进行本地视频通话',
    fallback: () => {
      document.getElementById('call-status').textContent = '权限不足,无法启动视频通话';
    }
  });

  if (result['ohos.permission.CAMERA'] && result['ohos.permission.MICROPHONE']) {
    // 权限通过,启动本地视频流(鸿蒙PC端传入设备选择参数)
    const pcDeviceParams = permissionManager.isHarmonyPC ? {
      cameraType: document.getElementById('camera-select').value, // 外接/内置摄像头
      microphoneType: document.getElementById('mic-select').value // 外接/内置麦克风
    } : {};
    await ipcRenderer.invoke('video:start-local-stream', pcDeviceParams);
    document.getElementById('call-status').textContent = '本地视频通话已启动';
    // 记录权限使用日志
    permissionAuditor.logPermissionUsage('ohos.permission.CAMERA', '启动本地视频', '分辨率:1080p,设备类型:' + (permissionManager.isHarmonyPC ? pcDeviceParams.cameraType : '内置'));
    permissionAuditor.logPermissionUsage('ohos.permission.MICROPHONE', '启动本地音频', '采样率:48kHz,设备类型:' + (permissionManager.isHarmonyPC ? pcDeviceParams.microphoneType : '内置'));
    // 鸿蒙PC端额外记录屏幕捕获权限使用
    if (permissionManager.isHarmonyPC && result['ohos.permission.SCREEN_CAPTURE']) {
      permissionAuditor.logPermissionUsage('ohos.permission.SCREEN_CAPTURE', '启用屏幕捕获', '待共享状态');
    }
  }
});

// 启动跨设备视频共享(鸿蒙PC作为主设备)
document.getElementById('start-distributed-call').addEventListener('click', async () => {
  const allPermissions = [...LOCAL_PERMISSIONS, ...DISTRIBUTED_PERMISSIONS];
  // 申请本地+分布式权限
  const result = await permissionManager.requestMultiplePermissions(allPermissions, {
    rationale: permissionManager.isHarmonyPC
      ? '需要摄像头、麦克风、屏幕捕获及跨设备协同权限以共享视频至其他设备,支持PC大屏预览'
      : '需要摄像头、麦克风及跨设备协同权限以共享视频至其他设备',
    fallback: () => {
      document.getElementById('call-status').textContent = '权限不足,无法启动跨设备视频共享';
    }
  });

  const hasAllPermissions = allPermissions.every(perm => result[perm]);
  if (hasAllPermissions) {
    // 获取配对设备
    const pairedDevices = await ipcRenderer.invoke('distributed:get-paired-devices');
    if (pairedDevices.length === 0) {
      document.getElementById('call-status').textContent = '无已配对设备,请先配对';
      return;
    }

    // 发起跨设备权限请求(从设备需具备摄像头/麦克风权限)
    const targetDeviceId = pairedDevices[0].deviceId;
    const isDistributedGranted = await ipcRenderer.invoke('distributed:request-permission', {
      targetDeviceId,
      requiredPermissions: LOCAL_PERMISSIONS.filter(p => !p.includes('SCREEN_CAPTURE')), // 从设备无需屏幕捕获权限
      distributedPermission: 'ohos.permission.DISTRIBUTED_DEVICE_MANAGER'
    });

    if (isDistributedGranted) {
      // 启动跨设备视频流(鸿蒙PC端传入大屏预览参数)
      const pcPreviewParams = permissionManager.isHarmonyPC ? {
        previewMode: 'fullscreen', // 全屏预览
        resolution: '2k' // PC端高分辨率预览
      } : {};
      await ipcRenderer.invoke('video:start-distributed-stream', { 
        targetDeviceId,
        ...pcPreviewParams
      });
      document.getElementById('call-status').textContent = `已共享视频至${pairedDevices[0].deviceName}`;
      // 记录分布式权限使用日志
      permissionAuditor.logPermissionUsage(
        'ohos.permission.DISTRIBUTED_DEVICE_MANAGER',
        '跨设备视频共享',
        `目标设备:${targetDeviceId},预览模式:${permissionManager.isHarmonyPC ? '全屏' : '默认'}`
      );
    } else {
      document.getElementById('call-status').textContent = '从设备权限不足,无法共享视频';
    }
  }
});

// 鸿蒙PC端屏幕共享功能(新增)
if (permissionManager.isHarmonyPC) {
  document.getElementById('start-screen-share').addEventListener('click', async () => {
    const hasScreenPermission = await permissionManager.requestSinglePermission('ohos.permission.SCREEN_CAPTURE', {
      rationale: '需要屏幕捕获权限以共享PC屏幕至其他设备,可选择单个窗口或整个屏幕',
      fallback: () => {
        document.getElementById('call-status').textContent = '屏幕捕获权限不足,无法启动屏幕共享';
      }
    });

    if (hasScreenPermission) {

五、常见问题与解决方案(实战踩坑)

5.1 问题 1:跨设备进程发现失败

  • 现象:主进程无法识别已配对的鸿蒙设备;
  • 原因:未申请分布式设备管理权限,或设备未开启 “分布式协同” 功能;
  • 解决方案
    1. 在 harmonyos.config.json 中添加 ohos.permission.DISTRIBUTED_DEVICE_MANAGER 权限(参考 3.4.2 节);
    2. 确保设备已开启 “设置→更多连接→分布式协同”;
    3. 验证设备是否在同一局域网(可通过 ping 设备 IP 测试连通性)。

5.2 问题 2:跨设备 IPC 通信数据序列化失败

  • 现象:发送复杂数据(如嵌套对象、二进制文件)时,接收端解析失败;
  • 原因:Protobuf 协议定义不完整,或数据类型不匹配;
  • 解决方案
    1. 扩展 Protobuf 协议,明确字段类型(如嵌套对象需定义子消息);
    2. 二进制数据通过 bytes 类型传输(修改 ipc.proto):

      protobuf

      message IPCMessage {
        string channel = 1;
        oneof dataType {
          string jsonData = 2; // 普通JSON数据
          bytes binaryData = 3; // 二进制数据(如图片、文件)
        }
        int64 timestamp = 4;
      }
      
    3. 发送二进制数据时,指定 dataType 为 binaryData

5.3 问题 3:进程生命周期不同步

  • 现象:Ability 进入后台后,渲染进程未暂停,仍占用大量资源;
  • 原因:生命周期回调未正确绑定,或暂停逻辑未覆盖所有资源(如定时器、动画);
  • 解决方案
    1. 确保渲染进程的 onBackground 回调中,清除所有定时器(clearInterval)、动画帧(cancelAnimationFrame);
    2. 监听鸿蒙系统的 appStateChange 事件,二次确认应用状态:
      // 渲染进程中添加系统状态监听
      const { systemEventManager } = require('@ohos.app.ability');
      systemEventManager.on('ohos.system.event.APP_STATE_CHANGE', (event) => {
        if (event.appState === 'background') {
          this.pauseRenderer(); // 强制暂停渲染进程
        }
      });
      

5.4 问题 4:跨设备数据同步丢失

  • 现象:设备 A 修改数据后,设备 B 未同步更新;
  • 原因:分布式数据管理未启用,或同步逻辑未监听数据变化;
  • 解决方案
    1. 在 harmonyos.config.json 中启用 dataSync(参考 3.4.2 节);
    2. 使用鸿蒙 DistributedDataManager 监听数据变化:
      const { DistributedDataManager } = require('@harmonyos/distributed-data');
      const dataManager = new DistributedDataManager();
      
      // 监听待办数据变化
      dataManager.onDataChange('todoList', (newData) => {
        this.rendererInstance.todoList = newData;
        this.renderTodoList();
      });
      

六、总结与未来展望

6.1 重构核心价值

Electron 进程模型的鸿蒙化重构,本质是 “桌面端多进程架构” 与 “分布式多终端架构” 的深度融合,核心价值体现在三点:

  1. 打破设备壁垒:实现 Electron 应用的跨设备协同,从 “单设备使用” 升级为 “多设备无缝流转”;
  2. 优化资源效率:进程生命周期与鸿蒙 Ability 协同,按需启动 / 暂停,降低设备资源占用;
  3. 兼容原生体验:统一通信层兼容 Electron 原生 API,开发者无需大幅修改业务代码,适配成本低。

6.2 未来优化方向

  1. AI 驱动的智能进程调度:结合设备性能、用户习惯、网络状态,通过 AI 算法自动分配任务到最优设备(如复杂渲染分配给 GPU 性能更强的智慧屏);
  2. 低功耗优化:针对移动设备(手机 / 平板),优化后台进程的唤醒策略,降低电量消耗;
  3. 多语言进程协同:支持 ArkTS 编写的鸿蒙原生进程与 Electron 进程无缝通信,丰富生态融合场景;
  4. 大文件跨设备传输优化:基于鸿蒙软总线的断点续传和分片传输,提升大文件(如附件、图片)的跨设备传输效率。

6.3 学习资源推荐

  1. 官方文档:
  2. CSDN 技术博客:
  3. 开源项目:

本文从冲突解析、原理拆解、实战落地到性能验证,完整覆盖了 Electron 进程模型的鸿蒙分布式重构方案。如果需要补充某部分的细节代码(如大文件跨设备传输实现)、增加更多测试场景,或调整实战案例的复杂度,欢迎随时告知!

欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/

    Logo

    讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

    更多推荐