posted in DevOps 

由于众所周知的原因官方minikube在中国大陆地区运行会碰到网络问题。

阿里云对官方minikube做了修改,使它默认使用国内镜像仓库。安装和启动参考文档如下:

Minikube - Kubernetes本地实验环境

但是文中的参数任然有问题,如下命令可以正常启动,操作系统Mac

minikube start --image-mirror-country cn \
    --iso-url=https://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/iso/minikube-v1.5.1.iso \
    --registry-mirror=https://xxxxxx.mirror.aliyuncs.com \
    --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers

其中--registry-mirror参数中的"xxxxxx"是个人阿里云镜像加速地址,查询地址:

https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors
posted in DevOps 

一般定义

平台能为产品团队提供可复用能力支撑(赋能),它是一个操作环境,可以让产品团队在其上把产品特性快速交付到客户手中。ThoughtWorks开发出一个五维模型来描述平台的重要特征:

  • 支持端到端交付的基础设施
  • API和架构自愈
  • 提供自助服务
  • 支持实验的基础设施
  • 提供用户触点技术

先来看看什么不是一个平台

按技术领域划分团队

有的企业花大力气构建自动化能力,但是往往会根据技术划分团队。比如,负责中间件管理的团队,负责操作系统管理的团队,负责DB管理的团队,负责LB管理的团队,另外还会有一个专门负责自动化的效能团队,往往能力上只是负责编排。

这些团队,每个都在自己的管理架构下有独立的工作方式,每个团队都在各自的技术领域范围内实现高效率管理,集中专业化,外包非差异化功能,应用治理,并降低成本。但就是没人去衡量客户需求端到端的交付效率。

一个客户需求的交付往往穿越多个团队,端到端交付效率极差。

时间长了,产品和基础设施的质量逐渐下降。环境和配置上会出现很多细微的不一致。产品团队不得不降低交付速度,减少一次交付包含的特性。甚至不敢去做任何改动较大的提升动作或重构动作,而这些动作本来是用来改善问题的。

彼此依赖的待办事项

一个特性的交付往往在一个团队内无法独立完成,于是一个团队的待办事项会依赖其他团队的待办事项,彼此耦合。而往往不同团队有不同的目标,各自的待办事项优先级不同。于是一个团队的待办事项无法落地的原因往往就是依赖另一个团队的待办事项,而另一个团队迟迟不能完成。这就造成了团队之间的隔阂和互相之间的不信任,甚至扯皮、推诿,丧失责任感和创造力。最终使端到端交付的效率变得极低。

显然,一个好的平台的一个重要特征是,他必须能减少待办事项耦合。该平台必须能够支持自助服务。

什么是一个平台

AWS就是一个平台。

如何构建一个平台

  1. 调整组织架构,给每个产品团队增加5%的运维职责,节省平台团队200%的运维职责。"you build it, you run it."
  2. 平台自己也要承担从开发到运行的所有责任,避免平台自身的开发团队只管开发,运维团队管运行。同样要坚守:"you build it, you run it."
  3. 划清平台和产品团队的职责边界,产品团队负责自己产品的构建,部署,监控等;平台团队负责平台的构建,部署,监控等;设计抽象,对外暴露自助服务;平台不应该关心它上面跑的是什么业务,产品不应该关心平台使用的技术。
  4. 除了提供API之外,还要提供文档,guideline, template code, on call, 布道等。
  5. 最初落地时不知道需要提供哪些能力,可以从产品团队了解需求,从解决最迫切的需求开始。
posted in DevOps 

什么是Mutable Infrastructure

传统物理服务器时代,企业倾向于对服务器做修改以实现商业目的。如,更换配件,原地更新软件包(upgrade app),修改配置(update configuration),打补丁(ad-hoc fixes),微调(tweaks)。这些操作渐渐把一个服务器变得独一无二(SnowflakeServer)。这使得它难以被替换,难以维护,难以复制。你越来越无法理解其上面的某个配置这样配的原因。而这一切之所以会这样发展,是因为物理服务器时代服务器更新的难度大,成本高,周期长。所以企业在服务器更新上发展出很多技术,但都是围绕着原地升级的思路发展的。

什么是Immutable Infrastructure

到了虚拟化时代,虚拟服务器变得可以被轻易替换(disposable),整个服务器可以在秒级被重新构建。这使得使用服务器被整体替换的方式更新服务器比原地升级服务器来的更划算。服务器一旦部署完就不再被修改,服务器需要升级时,是基于一个基线重新构建然后替换原有服务器。这使得服务器的复制,替换变得轻而易举。这种服务器也叫PhoenixServer。这样的基础架构无疑更加健壮,可维护。

如何实现Immutable Infrastructure

  • 禁止人工登录服务器做任何修改
  • 使用虚拟化技术
  • 服务器可以基于镜像快速构建,
  • 服务器的生命周期管理由软件自动化实现
    • 服务器目标状态由文档定义
    • 文档由VCS管理
    • 服务器更新由自动化工具基于VCS里文档的更新触发,并最终实现目标状态
  • 一个无状态,变化频繁的应用层
  • 一个数据持久层
    • 日志中心,记录服务器的每次变更
    • 外部数据存储,无状态应用层服务器被频繁替换,数据必须存储在外部
  • 开发和运维无间合作(DevOps)
  • 使用混沌工程工具验证服务的健壮性

理想很丰满,现实很骨感

如果在物理服务器上利用严格规范和自动化工具是否能打造Immutable Infrastructure呢?理论上可以,但现实是物理服务器很多场景下难以实现批量统一的更新,因为物理服务器太大,上面跑的东西太多。
那么是不是使用了虚拟化技术,就一定能实现Immutable Infrastructure?答案是否定的。国内很多大厂,甚至是云计算提供商,其内部服务虽然使用了虚拟机,甚至容器,但是其运维范式仍然遵循传统原地升级模式,仍然会登录到虚拟机或容器内部做一些修改,或者只有部分技术栈(如服务软件包,基础软件包)实现了基线化管理,而其他技术栈(如操作系统配置,网络配置)还是通过其他途径手动升级,整体效果仍然和传统Mutable Infrastructure一样。举个例子,应用扩容一台机器,而防火墙没有同步更新,导致新扩容的服务器和其他服务器之间网络不通,需要人工修改网络配置才能打通网络。
所以说,真正的Immutable Infrastructure的落地,是依赖强有力的运维能力的,而这样的运维能力又依赖配套的组织架构关系和DevOps文化和极强的规范化。

posted in Java 

查找问题线程

  • top 查看问题进程 pid
  • top -Hp pid 查看进程中有问题线程pid tid
  • printf '%x/n' tid 转化为十六进制
  • jstack pid |grep tid的十六进制 -A 30 查看线程堆栈信息

内存溢出问题

  • jstat -gcutil java_pid 刷新时间间隔 查看分区占用情况和gc状况
  • jmap -dump:format=b,file=name.dump java_pid 导出dump文件
  • 用visualVM分析dump文件
posted in EchoFlow 

📋 目录


🌳 分支策略

主要分支

分支名 用途 自动部署 保护级别
main 生产环境代码 ✅ Production 🔒 高度保护
develop 开发环境代码 ✅ Staging 🛡️ 基本保护
feature/* 功能开发分支 📝 无保护
hotfix/* 紧急修复分支 📝 无保护
release/* 发布准备分支 📝 无保护

分支规则

gitgraph
    commit id: "Initial"
    branch develop
    checkout develop
    commit id: "Dev Setup"

    branch feature/user-auth
    checkout feature/user-auth
    commit id: "Add login"
    commit id: "Add signup"

    checkout develop
    merge feature/user-auth
    commit id: "Merge auth"

    branch release/v1.0.0
    checkout release/v1.0.0
    commit id: "Version bump"
    commit id: "Update docs"

    checkout main
    merge release/v1.0.0
    commit id: "Release v1.0.0"

    checkout develop
    merge main

🔄 开发流程

1. 新功能开发

# 1. 从 develop 创建功能分支
git checkout develop
git pull origin develop
git checkout -b feature/audio-enhancement

# 2. 开发功能
# ... 代码开发 ...

# 3. 提交代码(遵循提交规范)
git add .
git commit -m "feat(audio): add real-time audio enhancement

- Implement WebRTC audio processing
- Add noise cancellation algorithm
- Optimize for low-latency streaming

Closes #123"

# 4. 推送并创建 Pull Request
git push origin feature/audio-enhancement
# 在 GitHub 上创建 PR: feature/audio-enhancement → develop

2. 紧急修复(Hotfix)

# 1. 从 main 创建 hotfix 分支
git checkout main
git pull origin main
git checkout -b hotfix/critical-auth-bug

# 2. 修复问题
# ... 代码修复 ...

# 3. 提交修复
git add .
git commit -m "fix(auth): resolve critical login vulnerability

- Fix SQL injection in user authentication
- Add input validation for login parameters
- Update security tests

Fixes #456
BREAKING CHANGE: Updated authentication API requires new client integration"

# 4. 推送并创建 PR
git push origin hotfix/critical-auth-bug
# 创建两个 PR:
# - hotfix/critical-auth-bug → main (紧急生产修复)
# - hotfix/critical-auth-bug → develop (同步到开发分支)

3. 发布流程

# 1. 从 develop 创建 release 分支
git checkout develop
git pull origin develop
git checkout -b release/v1.2.0

# 2. 准备发布
# - 更新版本号
# - 更新 CHANGELOG.md
# - 运行测试
# - 更新文档

# 3. 提交发布准备
git add .
git commit -m "chore(release): prepare v1.2.0

- Update version to 1.2.0
- Update CHANGELOG.md
- Update API documentation"

# 4. 合并到 main
# 创建 PR: release/v1.2.0 → main
# 合并后自动部署到生产环境

# 5. 同步回 develop
# 创建 PR: main → develop

📝 提交规范(Conventional Commits)

提交格式

<type>(<scope>): <subject>

<body>

<footer>

Type 类型

Type 说明 示例
feat 新功能 feat(audio): add voice command support
fix Bug 修复 fix(auth): resolve login timeout issue
docs 文档更新 docs(api): update authentication guide
style 代码格式 style(frontend): fix linting errors
refactor 代码重构 refactor(backend): simplify user service
perf 性能优化 perf(audio): optimize transcription speed
test 测试相关 test(auth): add unit tests for login flow
chore 构建/工具 chore(deps): update dependencies
ci CI/CD 配置 ci(github): add staging deployment
revert 代码回滚 revert: feat(audio): remove broken feature

Scope 范围

  • frontend - iOS 前端相关
  • backend - NestJS 后端相关
  • audio - 音频处理相关
  • auth - 认证授权相关
  • api - API 接口相关
  • db - 数据库相关
  • docs - 文档相关
  • deps - 依赖管理

特殊标记

# 破坏性变更
BREAKING CHANGE: API endpoint /auth/login now requires additional parameters

# 关联 Issue
Closes #123
Fixes #456
Refs #789

# 共同作者
Co-authored-by: John Doe <john@example.com>

🤖 CI/CD 流程

自动触发规则

事件 分支 执行动作
push develop 🧪 测试 + 🚀 部署到 Staging
push main 🧪 测试 + 🚀 部署到 Production
pull_request any → develop 🧪 测试 + 🔍 代码检查
pull_request any → main 🧪 测试 + 🔍 代码检查 + 📋 发布检查

Pipeline 阶段

graph LR
    A[Code Push] --> B[Lint & Format]
    B --> C[Unit Tests]
    C --> D[Integration Tests]
    D --> E[Build]
    E --> F[Security Scan]
    F --> G[Deploy]
    G --> H[Health Check]
    H --> I[Notification]

部署策略

  • Staging: 自动部署,快速迭代
  • Production: 自动部署,但需要 PR 审查通过
  • Rollback: 一键回滚到上一个稳定版本

🏗️ 环境说明

环境配置

环境 分支 用途 数据库 缓存 监控
Development feature/* 本地开发 SQLite 本地 Redis 本地日志
Staging develop 测试验证 PostgreSQL Redis Cluster Sentry + 日志
Production main 生产服务 PostgreSQL HA Redis HA Sentry + APM

环境变量

# Staging (.env.staging)
NODE_ENV=staging
API_URL=https://api-staging.echoflow.com
SENTRY_ENVIRONMENT=staging
LOG_LEVEL=debug

# Production (.env.production)
NODE_ENV=production
API_URL=https://api.echoflow.com
SENTRY_ENVIRONMENT=production
LOG_LEVEL=info

👥 代码审查

PR 要求

目标分支 必需审查者 自动检查
develop 1 人 ✅ Tests, ✅ Lint
main 2 人 ✅ Tests, ✅ Lint, ✅ Security

审查清单

  • 功能完整性: 功能是否按需求实现
  • 代码质量: 代码是否清晰、可维护
  • 测试覆盖: 是否有适当的测试用例
  • 性能影响: 是否对性能有负面影响
  • 安全检查: 是否存在安全隐患
  • 文档更新: 是否需要更新文档

PR 模板示例

## 🎯 功能描述

简述此 PR 解决的问题和实现的功能

## 🔧 改动内容

- [ ] 新增音频处理API
- [ ] 优化缓存策略
- [ ] 更新相关文档

## 🧪 测试说明

- [ ] 单元测试通过
- [ ] 集成测试通过
- [ ] 手动测试完成

## 📸 截图/演示

如果是UI相关改动,请提供截图或GIF

## 🔗 相关链接

- Closes #123
- Related to #456

🚀 快速开始

Clone 和设置

# 1. Clone 仓库
git clone https://github.com/your-org/EchoFlow.git
cd EchoFlow

# 2. 设置开发环境
git checkout develop
git pull origin develop

# 3. 安装依赖
# Backend
cd backend && yarn install

# Frontend
cd ../frontend/EchoFlowApp && open EchoFlowApp.xcodeproj

开始新功能

# 1. 从 develop 创建功能分支
git checkout -b feature/your-awesome-feature

# 2. 开发并提交
git add .
git commit -m "feat(scope): your feature description"

# 3. 推送并创建 PR
git push origin feature/your-awesome-feature

📞 支持

如有问题,请:

  1. 检查此文档
  2. 搜索已有 Issues
  3. 在团队频道询问
  4. 创建新的 Issue

Happy Coding! 🎉

posted in EchoFlow 

📌 系统概述

这是一个可持续维护的数据库迁移解决方案,自动化处理schema变更,支持多数据库切换,与开发流程深度集成。

🚀 核心特性

自动化迁移管理

  • 自动检测: 实体变更时自动生成迁移文件
  • 智能分析: 评估迁移风险和时间
  • 安全执行: 生产环境安全迁移流程
  • 一键回滚: 失败时快速恢复

🔄 多数据库支持

  • 无缝切换: SQLite ↔ PostgreSQL ↔ MySQL ↔ MongoDB
  • 零停机: 蓝绿部署迁移策略
  • 数据验证: 自动验证迁移结果

🛠 开发者友好

  • CLI工具: 简单易用的命令行界面
  • Git集成: 提交时自动检查schema变更
  • CI/CD: 部署流水线集成

📚 使用指南

日常开发流程

1. 修改实体后自动处理

# 1. 修改实体文件 (例如: user-audio-settings.entity.ts)
# 2. 自动生成迁移
yarn db:generate

# 3. 查看迁移计划
yarn db:analyze

# 4. 执行迁移
yarn db:migrate

2. 手动管理迁移

# 生成迁移 (指定名称)
yarn db:generate --name add-user-settings

# 模拟执行 (查看会发生什么)
yarn db:migrate --dry-run

# 实际执行
yarn db:migrate

# 回滚
yarn db:rollback

3. 数据库切换

# 切换到 PostgreSQL
yarn db:switch postgres

# 切换到 MySQL  
yarn db:switch mysql

# 模拟切换 (不实际执行)
yarn db:switch postgres --dry-run

4. 生产部署

# 部署前检查
yarn db:pre-deploy

# 生产环境安全迁移
yarn db:migrate --production

# 健康检查
yarn db:health

🎯 实际使用场景

场景1: 添加新字段

// 1. 修改实体
@Entity('user_audio_settings')
export class UserAudioSettingEntity {
  // ... 现有字段
  
  @Column({ type: 'varchar', length: 100, nullable: true })
  newField: string; // 新增字段
}

// 2. 自动生成迁移
// yarn db:generate
// ✅ 迁移文件生成成功: add-new-field-1703123456

// 3. 查看计划
// yarn db:analyze
// 📋 迁移计划:
//   待执行迁移: 1 个
//   预估时间: 30秒  
//   操作类型: 修改表结构
//   风险提示: ⚡ 可能需要数据转换

// 4. 执行迁移
// yarn db:migrate
// ✅ 迁移执行成功

场景2: 数据库切换

// 当前: SQLite
// 目标: PostgreSQL

// 1. 分析当前状态
yarn db:analyze
// 📋 当前数据库: SQLite
//   数据量: 1,245 条记录
//   预估迁移时间: 2分钟

// 2. 切换数据库
yarn db:switch postgres
// 🔄 切换到 postgres 数据库...
// 📦 创建数据备份...
// 🏗 创建目标数据库schema...
// 📊 迁移数据...
// ✅ 数据库切换到 postgres 完成!

// 3. 验证结果
yarn db:health
// ✅ 数据库连接正常
// ✅ 迁移状态正常  
// ✅ 数据一致性检查通过

场景3: 生产部署

# CI/CD 流水线
name: Deploy to Production
on:
  push:
    branches: [main]

jobs:
  deploy:
    steps:
      # ... 其他步骤
      
      - name: 数据库迁移检查
        run: |
          yarn db:pre-deploy
          if [ $? -ne 0 ]; then
            echo "❌ 数据库迁移检查失败,停止部署"
            exit 1
          fi
      
      - name: 执行数据库迁移
        run: yarn db:migrate --production
        
      - name: 健康检查
        run: yarn db:health

📖 命令参考

基础命令

命令 说明 示例
db:analyze 分析迁移需求 yarn db:analyze
db:generate 生成迁移文件 yarn db:generate --name update-schema
db:migrate 执行迁移 yarn db:migrate
db:rollback 回滚迁移 yarn db:rollback --steps 2
db:health 健康检查 yarn db:health

高级命令

命令 说明 示例
db:switch 切换数据库 yarn db:switch postgres
db:pre-deploy 部署前检查 yarn db:pre-deploy
db:migrate --production 生产迁移 yarn db:migrate --production
db:migrate --dry-run 模拟执行 yarn db:migrate --dry-run

🔧 配置说明

数据库配置

// src/config/database.config.ts
export const databaseConfig = {
  type: process.env.DB_TYPE || 'sqlite',
  // SQLite 配置
  database: 'data/echoflow.db',
  // PostgreSQL 配置  
  host: process.env.DB_HOST,
  port: process.env.DB_PORT,
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
};

环境变量

# .env
DB_TYPE=sqlite          # sqlite|postgres|mysql
DB_HOST=localhost       # 数据库主机
DB_PORT=5432           # 数据库端口
DB_USERNAME=postgres   # 用户名
DB_PASSWORD=password   # 密码
DB_NAME=echoflow      # 数据库名

🛡 安全最佳实践

1. 生产环境迁移

  • ✅ 总是先备份数据
  • ✅ 在维护窗口执行
  • ✅ 使用 --production 模式
  • ✅ 验证迁移结果

2. 风险控制

  • ⚠️ 删除操作需人工确认
  • ⚠️ 大数据量分批迁移
  • ⚠️ 保留回滚方案

3. 监控告警

  • 📊 迁移时间监控
  • 📊 数据一致性检查
  • 📊 性能影响评估

🚀 与项目集成

这个迁移系统已经完全集成到EchoFlow项目中:

  1. 自动化: 修改实体→自动生成迁移→安全执行
  2. 可持续: 支持未来所有领域模型变更
  3. 零配置: 开箱即用,无需额外设置
  4. 生产就绪: 经过测试,可在生产环境安全使用

每次添加新的领域模型或修改现有模型时,系统会自动:

  • 🔍 检测变更
  • 📝 生成迁移文件
  • ⚡ 评估风险
  • 🚀 安全执行

这确保了数据库schema与代码始终保持同步,无需手动维护迁移清单! 🎉

posted in 破解杂谈 

开篇:一个看似简单的需求

作为一个经常需要在工作和个人微信之间切换的 Mac 用户,我突发奇想:能不能在 macOS 上同时运行两个微信实例?这样就不用频繁切换账号了。

想法很美好,现实却很骨感。我简单地复制了 /Applications/WeChat.app 到桌面,改名为 WeChat2.app,并修改了 Info.plist 中的 bundle ID 为 com.eflabs.talk。然后,我满怀期待地双击了这个新应用...

砰! 系统无情地抛出了一个错误:

Launch failed. Launchd job spawn failed. (Error Code: 153)

第一个坑:签名问题

作为一个有经验的开发者,我立刻意识到这可能是签名问题。macOS 要求所有应用都必须经过代码签名才能运行,而我复制的应用还保留着原始开发者的签名。

于是我尝试重新签名:

codesign --force --deep --sign - ~/Desktop/WeChat2.app

结果,另一个错误接踵而至:

resource fork, Finder information, or similar detritus not allowed

第二个坑:Finder 的"礼物"

这个错误信息很明确:代码签名工具检测到了 Finder 留下的"垃圾数据"。当我们复制文件时,Finder 会顺手添加一些扩展属性(extended attributes),比如:

  • com.apple.FinderInfo:Finder 的元数据
  • com.apple.fileprovider.fpfs#P:文件提供者的信息
  • Resource fork:传统 macOS 文件系统的资源分叉

这些"额外信息"在代码签名时是不被允许的,因为它们可能会影响签名的完整性验证。

漫长的探索:各种尝试

尝试 1:清理扩展属性

我的第一反应是清理这些扩展属性:

xattr -rc ~/Desktop/WeChat2.app

清理完成!再次签名... 还是失败。

尝试 2:删除 resource fork

既然扩展属性清理了,那可能是 resource fork 的问题。我检查了文件,发现确实有很多文件包含 resource fork:

find ~/Desktop/WeChat2.app -type f -exec sh -c 'test -e "$1/..namedfork/rsrc" && echo "$1 has resource fork"' _ {} \;

我尝试删除这些 resource fork,但依然无济于事。

尝试 3:使用 cp -RX 重新复制

cp -RX 可以排除 resource fork 进行复制。我重新复制了整个应用:

cp -RX /Applications/WeChat.app ~/Desktop/WeChat2.app

但问题依然存在。

尝试 4:使用 ditto 重新复制

ditto 命令据说可以更好地处理文件系统元数据。我尝试了:

ditto /Applications/WeChat.app ~/Desktop/WeChat2.app

还是没有解决问题。

关键时刻:找到真正的原因

在经历了无数次的失败后,我决定使用更详细的调试信息。我使用了 codesign 的详细输出模式:

codesign --force --sign - --verbose=4 ~/Desktop/WeChat2.app/Contents/PlugIns/WeChatMacShare.appex

这次,错误信息更具体了:

file with invalid attached data: Disallowed xattr com.apple.FinderInfo found on /Users/harvey/Desktop/WeChat2.app/Contents/PlugIns/WeChatMacShare.appex

啊哈! 问题出在目录本身的扩展属性,而不仅仅是文件。我之前的清理操作可能没有完全清理目录级别的扩展属性。

解决方案:系统化的清理和签名

找到了根本原因,解决方案就清晰了。关键是要:

  1. 彻底清理所有扩展属性:包括文件和目录
  2. 逐个签名组件:先签名所有子组件(.app、.appex、.framework),再签名主应用
  3. 使用 --preserve-metadata 选项:保留必要的元数据(entitlements、requirements 等)

完整的解决脚本

# 1. 重新复制应用(排除 resource fork)
cp -RX /Applications/WeChat.app ~/Desktop/WeChat2.app

# 2. 修改 bundle ID
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.eflabs.talk" ~/Desktop/WeChat2.app/Contents/Info.plist

# 3. 彻底清理所有扩展属性(文件和目录)
xattr -rc ~/Desktop/WeChat2.app
find ~/Desktop/WeChat2.app -type d -exec xattr -c {} \; 2>/dev/null

# 4. 移除所有现有签名
codesign --remove-signature ~/Desktop/WeChat2.app 2>/dev/null
find ~/Desktop/WeChat2.app -type d \( -name "*.app" -o -name "*.appex" -o -name "*.framework" \) -exec codesign --remove-signature {} \; 2>/dev/null

# 5. 逐个签名所有组件(从最内层开始)
find ~/Desktop/WeChat2.app -type d \( -name "*.app" -o -name "*.appex" -o -name "*.framework" \) | sort -r | while read dir; do
    xattr -c "$dir" 2>/dev/null
    codesign --force --sign - --preserve-metadata=entitlements,requirements,flags,runtime "$dir" 2>&1
done

# 6. 最后签名主应用
xattr -c ~/Desktop/WeChat2.app
codesign --force --sign - --preserve-metadata=entitlements,requirements,flags,runtime ~/Desktop/WeChat2.app

关键技巧

  1. sort -r:确保从最内层的组件开始签名(依赖关系)
  2. --preserve-metadata:保留 entitlements 等信息,避免应用功能受限
  3. 清理后立即签名:防止系统在签名过程中重新添加扩展属性

成功时刻:两个微信同时运行

执行完上述步骤后,我小心翼翼地尝试启动应用:

open -n ~/Desktop/WeChat2.app

成功了! 应用正常启动,并且我看到两个微信进程同时在运行:

  • 原始微信:com.tencent.xinWeChat (来自 /Applications/WeChat.app)
  • 新微信:com.eflabs.talk (来自 ~/Desktop/WeChat2.app)

它们使用不同的 bundle ID 和独立的数据目录,完全不会互相干扰。

经验总结

  1. macOS 的代码签名非常严格:不仅仅是文件内容,连文件系统元数据也会被检查
  2. Finder 会"好心"地添加元数据:复制文件时要注意清理扩展属性
  3. 签名顺序很重要:必须从最内层的依赖组件开始签名
  4. 使用详细输出模式调试--verbose=4 可以帮助定位具体问题
  5. --preserve-metadata 选项很关键:避免丢失重要的应用配置信息

后记

这个看似简单的需求,实际上涉及了 macOS 的安全机制、文件系统特性、代码签名等多个方面。虽然过程曲折,但最终成功解决了问题,也让我对 macOS 的应用签名机制有了更深入的理解。

现在,我可以愉快地同时使用两个微信账号了!🎉


本文记录了在 macOS 15.6 上同时运行两个微信实例的完整解决方案。如果遇到类似问题,希望这篇文章能帮助你少走弯路。