🍎 Electron 桌面端应用合法性问题全流程解决指南(新手友好版)

作者:去码头整点薯片日期:2025/11/8

本文目标:帮助你把本地的 Electron 应用打包成 macOS 的 .dmg,并做到打开不再被 Gatekeeper 拦截(不再提示“来自身份不明的开发者/无法验证是否含有恶意软件”)。

适用对象:个人开发者 & 小团队。


🧩 一、问题场景

当你满心欢喜地将精心开发的 Electron 应用打包分发给用户,却接到反馈:在 macOS 上无法打开,系统弹窗冷冰冰地提示“无法验证开发者”,文件被直接移入废纸篓。

如果这个场景让你感同身受,那么你正遭遇 macOS 强大的 Gatekeeper 安全机制。别担心,这不是你的代码有问题,而是应用缺少一张合法的“身份证”——苹果的开发者签名。本指南将带你一步步为你的 Electron 应用完成代码签名,彻底告别“不安全软件”的提示。


🚀 二、从 0 到 1 的整体流程(必做)

阶段目标核心工具产出
1. 证书准备让 Apple 识别你的身份Apple Developer、KeychainDeveloper ID Application 证书
2. 签名给 .app 加数字签名electron-builder/codesign已签名的 .app/.dmg
3. 公证让 Apple 云端扫描并背书notarytoolAccepted 的公证结果
4. staple把公证票据装订回产物xcrun stapler可离线验证的 .app/.dmg
5. 验证本地三板斧校验codesign/spctl/stapler放心分发

🧱 三、准备工作

1) Apple Developer 账号

  • 开通 developer.apple.com 开发者计划($99/年)。
  • 建议以组织(Organization)形式创建,便于分权协作。

2) 正确的证书类型

不要Mac Development/Apple Development。它们只用于调试。

  • ✅ 需要:Developer ID Application(给 .app/.dmg 签名用)
  • (可选):Developer ID Installer(制作 .pkg 安装器才用)

可能的问题:

  • 创建页里没有 "Developer ID Application" → 你不是 Account Holder/Admin 或选错了 Team。让 Account Holder 升级角色或代为创建。

3) 获取 Developer ID Application 证书的详细步骤

步骤 3.1:在本地生成证书签名请求(CSR)

  1. 打开 macOS 的 钥匙串访问 (Keychain Access)
  2. 菜单栏选择:钥匙串访问 → 证书助理 → 从证书颁发机构请求证书...
  3. 填写信息:
    • 用户电子邮件地址:填写你的 Apple ID 邮箱(例如:[email protected]
    • 常用名称:填写你的名字或公司名(例如:Example Developer
    • CA 电子邮件地址:留空
    • 选择:存储到磁盘
    • 勾选:让我指定密钥对信息
  4. 点击 继续
  5. 在密钥对信息中:
    • 密钥大小:2048 位
    • 算法:RSA
  6. 保存 CSR 文件(例如:CertificateSigningRequest.certSigningRequest)到桌面或其他位置

⚠️ 注意:生成 CSR 后,钥匙串中会自动生成一个对应的私钥,请勿删除!证书下载后需要与这个私钥配对使用。

步骤 3.2:在 Apple Developer 网站申请证书

  1. 登录 Apple Developer
  2. 进入:Certificates, Identifiers & Profiles → Certificates
  3. 点击左上角 + 按钮创建新证书
  4. 选择证书类型:
    • 滚动到 Production 区域
    • 选择 Developer ID Application
    • 点击 Continue
  5. 上传 CSR:
    • 点击 Choose File
    • 选择刚才生成的 .certSigningRequest 文件
    • 点击 Continue
  6. 下载证书:
    • 点击 Download 下载 .cer 文件(例如:developerID_application.cer

步骤 3.3:将证书导入钥匙串

  1. 双击下载的 .cer 文件
  2. 系统会自动打开钥匙串访问并导入证书
  3. 验证导入成功:
    • 打开 钥匙串访问
    • 选择 登录 钥匙串
    • 我的证书 分类中查找
    • 应该能看到类似 Developer ID Application: Your Name (TEAM_ID) 的证书
    • 展开证书,确认下方有对应的私钥(🔑 图标)
  4. 验证证书可用性:
1# 列出所有可用的签名身份
2security find-identity -v -p codesigning
3
4# 应该能看到类似输出:
5# 1) ABCD1234EFGH5678IJKL9012MNOP3456QRST7890 "Developer ID Application: Your Name (TEAM_ID)"
6

⚠️ 常见问题

  • 证书导入后没有私钥:说明 CSR 不是在本机生成的,或者私钥被删除了。需要重新生成 CSR 并申请新证书。
  • 无法在钥匙串中找到证书:确认导入到了 登录 钥匙串而不是 系统 钥匙串。

4) App Store Connect API Key(用于公证)

步骤 4.1:创建 API Key

  1. 登录 App Store Connect
  2. 导航路径:Users and Access → Keys → App Store Connect API
  3. 点击页面中的 + 按钮(或 Generate API Key
  4. 填写信息:
    • Name:给 Key 起个名字(例如:Notarization KeyCI/CD Key
    • Access:选择 Developer(公证只需要 Developer 权限)
  5. 点击 Generate

🔐 权限说明

  • Admin:最高权限,可管理所有内容
  • Developer:可提交公证请求(推荐用于自动化)
  • 如果只做公证,选择 Developer 即可

步骤 4.2:下载和保存 API Key

  1. 创建成功后,页面会显示:
    • Key ID(例如:KEYID12345)← 记录此值
    • Issuer ID(页面顶部,例如:00000000-1111-2222-3333-444444444444)← 记录此值
    • Download API Key 按钮
  2. 立即下载 .p8 文件:
    • 点击 Download API Key
    • 文件名格式:AuthKey_XXXXXXXX.p8(例如:AuthKey_ABCD1234.p8
    • ⚠️ 此文件只能下载一次! 下载后妥善保管,丢失需要重新创建 Key
  3. 保存到项目目录:
1# 建议的项目结构
2your-electron-app/
3├── build/
4│   ├── AuthKey_ABCD1234.p8          # API Key 文件
5│   ├── entitlements.mac.plist       # 权限配置
6│   └── afterSign.js                 # 可选:自动补签脚本
7├── electron-builder.yml
8└── package.json
9
  1. 安全建议
    • .p8 文件添加到 .gitignore,避免提交到 Git
    • 在 CI/CD 中使用环境变量或加密存储
1# .gitignore 示例
2build/*.p8
3*.p8
4

步骤 4.3:记录三项关键信息(mock 示例)

完成后,你应该拥有以下三项信息:

项目说明Mock 示例
API Key 文件.p8 文件路径build/AuthKey_ABCD1234.p8
Key ID在 App Store Connect 中显示KEYID12345
Issuer ID在 App Store Connect 顶部显示00000000-1111-2222-3333-444444444444

可能的问题:

  • App Store Connect看不到 Keys 菜单 → 你不是 Account Holder;请让 Account Holder 创建并分享 .p8/KeyID/IssuerID 给你。
  • .p8 文件丢失 → 无法重新下载,只能删除旧 Key 并创建新的。
  • 权限不足 → 如果公证失败提示权限问题,尝试使用 Admin 权限的 Key。

⚙️ 四、项目配置(使用 electron-builder)

1) 完整的项目结构

1your-electron-app/
2├── build/                                # 构建资源目录
3│   ├── AuthKey_ABCD1234.p8              # ⚠️ API Key(不要提交到 Git)
4│   ├── entitlements.mac.plist           # macOS 权限配置
5│   ├── afterSign.js                     # 可选:签名后处理脚本
6│   └── icon.icns                        # 应用图标
7├── src/                                  # 源代码
8│   ├── main.js                          # Electron 主进程
9│   └── renderer/                        # 渲染进程
10├── scripts/
11│   └── check-sign.sh                    # 签名自检脚本
12├── .env                                  # ⚠️ 环境变量(不要提交到 Git)
13├── .gitignore
14├── electron-builder.yml                 # electron-builder 配置
15└── package.json
16

2) package.json 配置(完整示例)

1{
2  "name": "my-electron-app",
3  "version": "1.0.0",
4  "description": "My Electron App",
5  "main": "src/main.js",
6  "scripts": {
7    "start": "electron .",
8    "build": "electron-builder",
9    "build:mac": "electron-builder --mac",
10    "build:mac:dmg": "electron-builder --mac dmg",
11    "build:dir": "electron-builder --mac --dir",
12    "pack": "electron-builder --dir",
13    "dist": "electron-builder",
14    "check-sign": "bash scripts/check-sign.sh dist/mac/MyApp.app"
15  },
16  "build": {
17    "appId": "com.example.myapp",
18    "productName": "MyApp",
19    "extends": "electron-builder.yml"
20  },
21  "devDependencies": {
22    "electron": "^28.0.0",
23    "electron-builder": "^24.9.1"
24  },
25  "author": "Your Name",
26  "license": "MIT"
27}
28

💡 脚本说明

  • build:mac:构建 macOS 版本(包括 dmg 和 zip)
  • build:mac:dmg:只构建 dmg
  • build:dir:不打包,只构建目录(用于测试签名)
  • check-sign:运行签名自检脚本

3) electron-builder.yml 配置(三种方式)

方式 A:直接硬编码(适合个人本地开发)

electron-builder.yml(mock 示例)

1appId: com.example.myapp
2productName: MyApp
3
4mac:
5  category: public.app-category.productivity
6  target:
7    - dmg
8  hardenedRuntime: true
9  gatekeeperAssess: false
10  entitlements: build/entitlements.mac.plist
11  entitlementsInherit: build/entitlements.mac.plist
12  notarize:
13    tool: "notarytool"
14    appleApiKey: "build/AuthKey_ABCD1234.p8"        # mock:你的 .p8 文件路径
15    appleApiKeyId: "KEYID12345"                     # mock:你的 Key ID
16    appleApiIssuer: "00000000-1111-2222-3333-444444444444"  # mock:你的 Issuer ID
17
18dmg:
19  title: "${productName} ${version}"
20  icon: "build/icon.icns"
21  background: "build/background.png"               # 可选:DMG 背景图
22  window:
23    width: 540
24    height: 380
25  contents:
26    - x: 140
27      y: 200
28      type: "file"
29    - x: 400
30      y: 200
31      type: "link"
32      path: "/Applications"
33

⚠️ 注意:此方式会把敏感信息写在配置文件中,不要提交到公开仓库!

方式 B:使用环境变量(推荐用于 CI/CD)

electron-builder.yml

1appId: com.example.myapp
2productName: MyApp
3
4mac:
5  category: public.app-category.productivity
6  target:
7    - dmg
8  hardenedRuntime: true
9  entitlements: build/entitlements.mac.plist
10  entitlementsInherit: build/entitlements.mac.plist
11  notarize:
12    tool: "notarytool"
13    # 留空,通过环境变量传递
14

.env(本地开发,不要提交到 Git)

1# Apple Developer 证书相关
2APPLE_ID=[email protected]
3APPLE_ID_PASSWORD=your-app-specific-password
4
5# App Store Connect API Key(推荐方式)
6APPLE_API_KEY=build/AuthKey_ABCD1234.p8
7APPLE_API_KEY_ID=KEYID12345
8APPLE_API_ISSUER=00000000-1111-2222-3333-444444444444
9
10# 或者使用 base64 编码的 .p8 内容(CI/CD 推荐)
11# APPLE_API_KEY_BASE64=<base64-encoded-content>
12

使用环境变量的构建命令

1# 方式 1:使用 dotenv
2npm install dotenv-cli --save-dev
3npx dotenv electron-builder --mac dmg
4
5# 方式 2:直接导出环境变量
6export APPLE_API_KEY_ID="KEYID12345"
7export APPLE_API_ISSUER="00000000-1111-2222-3333-444444444444"
8export APPLE_API_KEY="build/AuthKey_ABCD1234.p8"
9npm run build:mac:dmg
10

方式 C:使用 electron-builder 的环境变量自动识别

electron-builder 会自动读取以下环境变量:

环境变量说明Mock 示例
APPLE_API_KEYAPI Key 文件路径或 base64 内容build/AuthKey_ABCD1234.p8
APPLE_API_KEY_IDKey IDKEYID12345
APPLE_API_ISSUERIssuer ID00000000-1111-2222-3333-444444444444
APPLE_IDApple ID 邮箱(旧方式)[email protected]
APPLE_APP_SPECIFIC_PASSWORDApp-specific password(旧方式)xxxx-xxxx-xxxx-xxxx

💡 推荐:在 CI/CD 中使用环境变量方式,避免敏感信息泄露。

build/entitlements.mac.plist(建议最小集)

1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0">
4  <dict>
5    <key>com.apple.security.cs.allow-jit</key><true/>
6    <key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>
7    <key>com.apple.security.cs.disable-library-validation</key><true/>
8    <key>com.apple.security.app-sandbox</key><false/>
9    <key>com.apple.security.network.client</key><true/>
10  </dict>
11</plist>
12

可能的问题:

  • hardenedRuntime 未启用会导致公证失败。
  • entitlements 缺项(JIT/unsigned-executable-memory)会导致 Electron 无法正常运行或公证失败。

4) CI/CD 集成示例

GitHub Actions 配置示例

.github/workflows/build-macos.yml

1name: Build macOS App
2
3on:
4  push:
5    branches: [main]
6    tags: ['v*']
7  pull_request:
8    branches: [main]
9
10jobs:
11  build:
12    runs-on: macos-latest
13
14    steps:
15      - name: Checkout code
16        uses: actions/checkout@v4
17
18      - name: Setup Node.js
19        uses: actions/setup-node@v4
20        with:
21          node-version: '20'
22
23      - name: Install dependencies
24        run: npm ci
25
26      # 准备签名证书(方式1:使用 .p12 文件)
27      - name: Import signing certificate
28        env:
29          CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE_BASE64 }}
30          CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
31        run: |
32          # 创建临时 keychain
33          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
34          KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
35
36          security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
37          security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
38          security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
39
40          # 导入证书
41          echo $CERTIFICATE_BASE64 | base64 --decode > certificate.p12
42          security import certificate.p12 -k $KEYCHAIN_PATH -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign
43          security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
44
45          # 设置为默认 keychain
46          security list-keychain -d user -s $KEYCHAIN_PATH
47
48          # 清理
49          rm -f certificate.p12
50
51      # 准备 API Key
52      - name: Prepare API Key
53        env:
54          APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }}
55        run: |
56          mkdir -p build
57          echo $APPLE_API_KEY_BASE64 | base64 --decode > build/AuthKey.p8
58
59      # 构建应用
60      - name: Build and notarize
61        env:
62          APPLE_API_KEY: build/AuthKey.p8
63          APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
64          APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
65        run: npm run build:mac:dmg
66
67      # 上传构建产物
68      - name: Upload artifacts
69        uses: actions/upload-artifact@v4
70        with:
71          name: macos-dmg
72          path: dist/*.dmg
73

准备 GitHub Secrets

在 GitHub 仓库设置中添加以下 Secrets(Settings → Secrets and variables → Actions):

Secret Name说明如何获取
APPLE_API_KEY_BASE64.p8 文件的 base64 编码base64 -i AuthKey_ABCD1234.p8
APPLE_API_KEY_IDKey ID从 App Store Connect 复制
APPLE_API_ISSUERIssuer ID从 App Store Connect 复制
MACOS_CERTIFICATE_BASE64证书 .p12 的 base64base64 -i certificate.p12
MACOS_CERTIFICATE_PASSWORD.p12 文件的密码导出证书时设置的密码

如何导出 .p12 证书:

1# 1. 在钥匙串访问中找到证书
2# 2. 右键点击证书 → 导出
3# 3. 文件格式选择 "个人信息交换 (.p12)"
4# 4. 设置密码(用于 CI/CD)
5# 5. 保存后,转换为 base64:
6base64 -i certificate.p12 | pbcopy  # 复制到剪贴板
7

🔐 五、安全管理最佳实践

1) .gitignore 配置(必做)

1# .gitignore
2# macOS 构建相关
3*.p8
4*.p12
5*.cer
6*.mobileprovision
7build/*.p8
8build/AuthKey_*.p8
9
10# 环境变量
11.env
12.env.local
13.env.production
14
15# 构建产物
16dist/
17out/
18release/
19
20# 临时文件
21*.dmg
22*.zip
23*.pkg
24

2) 敏感信息管理建议

场景推荐方案不推荐
本地开发.env 文件 + .gitignore硬编码在配置文件中
团队协作1Password / Bitwarden 等密码管理器通过聊天工具传递
CI/CDGitHub Secrets / GitLab Variables明文存储在配置文件
多环境不同的 API Key(dev/prod)共用同一个 Key

3) API Key 权限最小化

1# 生产环境:只给 Developer 权限
2production_key:
3  access: Developer
4  scope: Notarization only
5
6# 开发环境:可以给更多权限方便调试
7development_key:
8  access: Developer
9  scope: Notarization + TestFlight
10

4) 证书过期管理

1# 检查证书有效期
2security find-identity -v -p codesigning | grep "Developer ID Application"
3openssl x509 -inform DER -in developerID_application.cer -noout -dates
4
5# 证书有效期通常为 5-7 年,提前 1 个月续签
6

5) CI/CD 安全检查清单

  • ✅ 确保 .p8.p12 文件已添加到 .gitignore
  • ✅ 使用 base64 编码存储在 CI Secrets 中
  • ✅ 构建后自动清理临时 keychain
  • ✅ 不在日志中打印敏感信息
  • ✅ 使用短期有效的临时 keychain
  • ✅ 限制 CI/CD 的访问权限(只给必要的人员)

🏗️ 六、打包与自动公证

1# 安装依赖(示例)
2npm i -D electron-builder
3
4# 一条龙:签名 + 提交公证 + staple
5npx electron-builder --mac dmg
6

公证状态查看(任选其一)

1# 查看历史
2xcrun notarytool history \
3  --key build/AuthKey_ABCD1234.p8 \
4  --key-id KEYID12345 \
5  --issuer 00000000-1111-2222-3333-444444444444
6
7# 查看指定请求
8xcrun notarytool info <REQUEST_ID> \
9  --key build/AuthKey_ABCD1234.p8 \
10  --key-id KEYID12345 \
11  --issuer 00000000-1111-2222-3333-444444444444
12

当状态为 Accepted 后,若未自动 staple:

1xcrun stapler staple "dist/mac/MyApp.app"
2xcrun stapler staple "dist/mac/MyApp.dmg"
3

🔎 七、公证前自检脚本(强烈推荐)

在提交公证前,先扫描 .app 内所有 Mach-O 文件并验证签名,可大幅提高一次通过率。

scripts/check-sign.sh

1#!/usr/bin/env bash
2set -euo pipefail
3
4APP="${1:-dist/mac/MyApp.app}"
5
6echo "== 1) 枚举 Mach-O 可执行体 =="
7find "$APP" -type f -print0 \
8| xargs -0 file \
9| grep -E 'Mach-O|dynamically linked shared library|bundle with Mach-O' \
10| sed 's/:.*$//' \
11| tee /tmp/macho.list
12
13echo
14echo "== 2) 对每个 Mach-O 执行严格签名校验 =="
15rc=0
16while IFS= read -r f; do
17  echo "-> $f"
18  if ! codesign --verify --strict --verbose=4 "$f" >/dev/null 2>&1; then
19    echo "⚠️ 未签名或签名无效: $f"
20    rc=1
21  fi
22done < /tmp/macho.list
23
24echo
25echo "== 3) 校验 .app 整体签名 =="
26if ! codesign --verify --strict --deep --verbose=4 "$APP"; then
27  echo "⚠️ .app 整体签名校验失败"
28  rc=1
29fi
30
31echo
32echo "== 4) Gatekeeper 预检 =="
33if ! spctl -a -vv "$APP"; then
34  echo "⚠️ Gatekeeper 预检未通过"
35  rc=1
36fi
37
38echo
39echo "== 5) 列出解包原生模块(高风险区) =="
40if [ -d "$APP/Contents/Resources/app.asar.unpacked" ]; then
41  find "$APP/Contents/Resources/app.asar.unpacked" -type f \( -name "*.node" -o -name "*.dylib" -o -name "*.jnilib" -o -name "*.so" \) -print
42fi
43
44echo
45echo "== 6) 检查主可执行体是否启用 Hardened Runtime =="
46MAIN_BIN="$APP/Contents/MacOS/$(defaults read "$APP/Contents/Info" CFBundleExecutable 2>/dev/null || echo '')"
47if [ -n "$MAIN_BIN" ] && ! codesign -d --verbose=4 "$MAIN_BIN" 2>&1 | grep -qi 'runtime'; then
48  echo "⚠️ 未检测到 hardened runtime 选项(需要 mac.hardenedRuntime: true 且使用 --options runtime)"
49  rc=1
50fi
51
52echo
53if [ "$rc" -ne 0 ]; then
54  echo "❌ 自检未通过,请根据上方提示修复后再提交公证"
55  exit 1
56else
57  echo "✅ 自检通过,建议开始 Notarization"
58fi
59

使用方式

1chmod +x scripts/check-sign.sh
2scripts/check-sign.sh dist/mac/MyApp.app
3

🛠️ 八、自动补签(afterSign.js,可选)

若第三方依赖解包出了 .dylib/.jnilib/.node 且未签名,可在 electron-builder.yml 中挂载:

1afterSign: build/afterSign.js
2

build/afterSign.js(简版示例)

1const { execFile } = require("child_process");
2const fs = require("fs");
3const path = require("path");
4
5function run(cmd, args) {
6  return new Promise((resolve, reject) => {
7    execFile(cmd, args, (err, stdout, stderr) => {
8      if (err) return reject(new Error(stderr || stdout || err?.message));
9      resolve();
10    });
11  });
12}
13
14function walk(dir, exts, out) {
15  if (!fs.existsSync(dir)) return;
16  for (const name of fs.readdirSync(dir)) {
17    const p = path.join(dir, name);
18    const st = fs.lstatSync(p);
19    if (st.isSymbolicLink()) continue;
20    if (st.isDirectory()) walk(p, exts, out);
21    else if (exts.some(e => p.endsWith(e))) out.push(p);
22  }
23}
24
25module.exports = async (context) => {
26  if (context.electronPlatformName !== "darwin") return;
27  const appPath = path.join(context.appOutDir, `${context.packager.appInfo.productFilename}.app`);
28  const entitlements = path.join(process.cwd(), "build/entitlements.mac.plist");
29  const identity = "Developer ID Application";
30
31  const targets = [];
32  walk(path.join(appPath, "Contents/Frameworks"), [".dylib", ".jnilib", ".node", ".so"], targets);
33  walk(path.join(appPath, "Contents/Resources", "app.asar.unpacked"), [".dylib", ".jnilib", ".node", ".so"], targets);
34
35  for (const f of targets) {
36    try {
37      // 已签名会通过;未签名会抛异常,进入 catch 重新签名
38      await run("codesign", ["--verify", "--strict", "--verbose=2", f]);
39    } catch {
40      console.log("🔧 重新签名:", f);
41      await run("codesign", ["--force", "--sign", identity, "--timestamp", "--options", "runtime", "--entitlements", entitlements, f]);
42    }
43  }
44};
45

🧪 九、三板斧验证(公证后)

1codesign -dv --verbose=4 "dist/mac/MyApp.app"   # Authority 应为 Developer ID Application: ...
2spctl -a -vv "dist/mac/MyApp.app"               # 应为 accepted
3xcrun stapler validate "dist/mac/MyApp.dmg"     # The validate action worked!
4

🔁 十、何时需要重新公证?

  • 改动 .app 内任何内容(包括图标 .icnsInfo.plist、资源、代码、Electron 版本等)→ ✅ 需要重新签名+公证
  • 只改 .dmg 的外观/背景(不改 .app)→ ❌ 无需重公证,但建议对新 .dmg 再次 stapler staple

🧱 十一、常见坑位速查表

现象/报错核心原因处理
看不到 “Keys” 创建入口不是 Account Holder让 Account Holder 代建 API Key
-25294 无法导入证书证书与私钥不匹配或钥匙串未解锁重新用本机 CSR 申请,或导入 .p12
Archive contains critical validation errors包内存在未签名的 .dylib/.jnilib/.node自检脚本找出 → afterSign.js 补签或排除
does not have a ticket stapled未装订公证票据xcrun stapler staple 到 .app/.dmg
长时间 In Progress队列繁忙或网络慢稍等几分钟或重试
首次运行仍弹提示其实公证未通过/未装订走三板斧验证,确保 Accepted + staple

🧭 十二、复盘总结

本文从问题场景出发,按证书 → 签名 → 公证 → 装订 → 验证的顺序,解决 Electron .dmg 在 macOS 上的合法性问题,并提供:

  • 详细的证书获取步骤:从生成 CSR、申请证书到导入钥匙串的完整流程;
  • API Key 获取指南:创建、下载和管理 App Store Connect API Key 的详细说明;
  • 三种 Electron 项目配置方式:硬编码、环境变量、CI/CD 集成的完整示例;
  • GitHub Actions CI/CD 配置:包括证书导入、秘密管理的生产级配置;
  • 安全管理最佳实践:.gitignore 配置、敏感信息管理、权限最小化等;
  • ✅ 可直接落地的 electron-builder.yml + entitlements.mac.plist 模板(均为 mock 示例);
  • 公证前自检脚本:在提交前发现未签名的原生库;
  • 自动补签afterSign.js(适用于第三方原生依赖);
  • ✅ 多个常见坑点与权限问题(Keys 不可见、证书导入失败等)的定性与修复。

按本文执行后,你的 .dmg 将具备签名 + 公证 + 票据装订的完整链路,用户安装时不再被系统拦截。无论是本地开发还是 CI/CD 自动化,都能找到对应的解决方案。祝分发顺利!


🍎 Electron 桌面端应用合法性问题全流程解决指南(新手友好版)》 是转载文章,点击查看原文


相关推荐


Python 的内置函数 globals
IMPYLH2025/11/6

Python 内建函数列表 > Python 的内置函数 globals Python 的内置函数 globals() 是一个非常重要的工具函数,它返回一个字典,表示当前全局符号表。这个字典包含了当前模块中定义的所有全局变量、函数和类的名称及其对应的值。 def globals(): ''' 返回实现当前模块命名空间的字典 :return: 当前模块命名空间的字典 ''' 具体来说: 返回值是一个字典对象字典的键是变量名或函数名(字符串形式)字典的值是


Python 的内置函数 filter
IMPYLH2025/11/1

Python 内建函数列表 > Python 的内置函数 eval Python 的内建函数 filter() 是一个非常有用的高阶函数,它用于对可迭代对象进行筛选过滤。它的基本工作原理是根据指定的函数条件,从输入的可迭代对象中筛选出符合条件的元素,返回一个迭代器对象。 def filter(fn, iterable): ''' 过滤数据 :param fn: 回调函数,返回 True 是元素保留,返回 False 时元素去除 :param iterable


彻底搞懂「字幕」:从格式、软硬到嵌入,告别所有困惑
mortimer2025/10/30

“我明明把字幕加上了,为什么播放时看不到?” “为什么别人的视频能切换中英文字幕,我的就不行?” “软字幕、硬字幕、.srt、.ass……这些到底是什么鬼?” 如果你曾被这些问题困扰,那么这篇文章就是为你准备的。我将用最清晰的逻辑和最易懂的语言,一次性讲透关于字幕的所有核心知识。 第一站:先懂最核心的区别 —— 软字幕 vs. 硬字幕 在讨论任何格式或工具之前,你必须先理解字幕最终呈现的两种形态。这决定了你的字幕能否被关闭或切换。 硬字幕 (Hard Subs):印在画面上的“纹身” 硬字幕


Redis(87)Redis缓存的LRU淘汰策略如何配置?
Victor3562025/10/27

配置Redis缓存的LRU(Least Recently Used,最近最少使用)淘汰策略需要以下几个步骤: 设置最大内存限制 选择和配置淘汰策略 验证配置 以下是详细的步骤和代码示例。 1. 设置最大内存限制 可以在Redis配置文件 redis.conf 中设置 maxmemory 选项来限制Redis使用的最大内存。 # redis.conf # 设置最大内存为256MB maxmemory 256mb 如果你不使用配置文件,可以通过命令行参数直接设置: redis-server -


Redis(80)如何解决Redis的缓存穿透问题?
Victor3562025/10/24

解决Redis缓存穿透问题的方法有多种,具体的解决方案可以根据实际情况选择。以下是几种常见的解决方法及详细的代码示例,包括缓存空结果、使用布隆过滤器以及参数校验。 1. 缓存空结果 当查询数据库返回空结果时,也将其缓存起来,并设置一个较短的过期时间,比如5分钟。这样即使请求数据不存在,也不需要每次都访问数据库。 示例代码: import redis.clients.jedis.Jedis; import com.fasterxml.jackson.databind.ObjectMapper;


零代码改造 + 全链路追踪!Spring AI 最新可观测性详细解读
阿里云云原生2025/10/22

作者:张铭辉(希铭) 本文部分总结于 8 月的 LoongSuite 系列公开课及 AI 原生应用实战营 meetup 分享内容。如需查看原视频欢迎移步:developer.aliyun.com/live/255218… 前言:AI Agent 从 Demo 到生产阶段的挑战 自 2022 年底 GPT-3.5 引爆大模型革命以来,AI 应用经历了从技术探索到产业落地的快速演进。开源模型迭代与低代码平台的兴起,推动了 AI Agent 开发效率的显著提升。然而,行业普遍面临一个核心矛盾:绝大多数


DeepSeek OCR:用'眼睛'阅读长文本,AI记忆新纪元?
墨风如雪2025/10/21

嘿,AI圈的朋友们!最近DeepSeek团队又搞了个大动作,发布了一款名叫DeepSeek-OCR的开源模型。但你可别以为这只是一个普通的文字识别工具,它的核心理念简直是脑洞大开,可能会彻底改变我们处理长文本的方式。 想象一下,我们的大语言模型(LLM)在面对海量文本时,常常会因为算力消耗过大而头疼不已,上下文一长,计算量就呈平方级增长。DeepSeek-OCR另辟蹊径,它不直接处理文本,而是巧妙地把文本信息“画”成图像,然后对这些图像进行压缩!是不是有点像人类先看图再理解,而不是一个字一个字地


如何从 iPhone 中导出视频
Digitally2025/10/20

如果你的 iPhone 存储空间不足,或者你想在发布到社交媒体之前编辑视频,你可以将视频从 iPhone 转移到电脑上,以释放更多空间。在 Windows 电脑上,通常使用 iTunes 在电脑和 iPhone 之间传输文件。在 Mac 上,如果你使用的是 macOS Mojave 或更早版本,可以使用 iTunes;如果你使用的是 macOS Catalina 或更高版本,则可以使用 Finder。在本文中,我们将向你展示多种从 iPhone 中导出视频的方法。有些方法需要使用 iTunes,


Pinia 状态管理原理与实战全解析
90后晨仔2025/10/19

一、前言:为什么选择 Pinia? 在 Vue2 时代,我们常用 Vuex 来做全局状态管理。 但是 Vue3 带来了全新的响应式系统(Composition API + Proxy),于是 Vue 官方团队推出了 Pinia —— 一款更轻量、更现代、更易用的状态管理库。 Pinia 的核心理念是: “让状态管理像使用普通变量一样简单。” 相比 Vuex,它具备以下优势: 特点VuexPinia语法基于 Mutation


图解AI核心技术:大模型、RAG、智能体、MCP
京东云开发者2025/10/17

简介 本文整理了来自Daily Dose of Data Science最热门或最新的文章,其中极具特色的动图以生动形象的方式,帮助我们更好的理解AI中的一些核心技术,希望能够帮助大家更好的理解和使用AI。 大模型 Transformer vs. Mixture of Experts 混合专家 (MoE) 是一种流行的架构,它使用不同的“专家”来改进 Transformer 模型。 下图解释了它们与 Transformers 的区别。 Transformer 使用前馈网络。 MoE 使用专家

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0