一、前言:合规场景下的技术落地边界
在企业数字化办公中,内部通知(如会议提醒、系统运维告警、授权用户服务通知)需要兼顾触达效率、数据安全与合规性。iMessage 作为苹果生态原生渠道,具备端到端加密、无拦截的特性,适合用于企业内部办公或已授权用户的服务通知(严格排除营销骚扰场景)。
---------------------------TG:@iosxiaoluo--------------------------------------
二、核心技术难点与合规前提
2.1 技术难点
- 单设备同步发送导致通知堆积,需实现异步并发处理
- 内部多部门通知需分级调度,避免资源争抢
- 发送状态需实时追踪,支持异常重试与日志审计
- 跨虚拟机节点的任务分发与负载均衡
2.2 合规前提(必须严格遵守)
- 仅用于企业内部员工通知或用户明确授权的服务场景(需留存授权记录)
- 提供一键退订机制,退订后立即停止发送并删除相关记录
- 不采集敏感信息,手机号等数据采用加密存储
- 严格遵循苹果 iMessage 使用规则,不破解、不规避官方限制
三、系统架构设计(纯内部使用)
┌─────────────────────────────────────────┐
│ 主控节点(内部服务器) │
│ ├─ 任务管理:Redis队列(分级优先级) │
│ ├─ 合规校验:授权名单+退订过滤 │
│ ├─ 日志审计:发送记录加密存储(留存1年)│
│ └─ 监控告警:失败率阈值触发提醒 │
├─────────────────────────────────────────┤
│ 执行节点(内部macOS虚拟机) │
│ ├─ 环境:独立账号+内部IP(无公网暴露) │
│ ├─ 核心服务:Python异步发送+状态回调 │
│ └─ 合规模块:发送频率限制+内容校验 │
└─────────────────────────────────────────┘
四、基础环境合规部署
4.1 主控节点配置(内部 Mac 服务器)
# 安装基础依赖(仅内部使用)
brew install python3 redis openssl
brew services start redis
# 配置Python国内镜像源(加速安装)
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
# 安装核心依赖库
pip3 install redis asyncio pyobjc cryptography
4.2 执行节点(内部虚拟机)配置
每台虚拟机仅用于内部通知,配置独立内部 IP:
# 安装依赖(无公网访问权限)
pip3 install pyobjc redis asyncio
五、核心合规代码实现
5.1 数据加密工具(保护内部手机号)
from cryptography.fernet import Fernet
# 生成密钥(仅内部存储,严禁泄露)
def generate_key():
key = Fernet.generate_key()
with open("internal_key.key", "wb") as f:
f.write(key)
# 加密手机号(存储时使用)
def encrypt_phone(phone, key_path="internal_key.key"):
with open(key_path, "rb") as f:
key = f.read()
fernet = Fernet(key)
return fernet.encrypt(phone.encode()).decode()
# 解密手机号(发送时使用)
def decrypt_phone(encrypted_phone, key_path="internal_key.key"):
with open(key_path, "rb") as f:
key = f.read()
fernet = Fernet(key)
return fernet.decrypt(encrypted_phone.encode()).decode()
# 初始化密钥(仅首次执行)
# generate_key()
5.2 合规校验模块(授权 + 退订过滤)
import redis
redis_client = redis.Redis(host="192.168.1.10", port=6379, db=0, decode_responses=True)
def is_authorized(phone):
"""校验是否为内部授权用户"""
authorized_set = redis_client.smembers("internal_authorized_phones")
return phone in authorized_set
def is_unsubscribed(phone):
"""校验是否退订"""
unsub_set = redis_client.smembers("internal_unsubscribed_phones")
return phone in unsub_set
def add_unsubscribe(phone):
"""添加退订记录(永久生效)"""
redis_client.sadd("internal_unsubscribed_phones", phone)
# 同时从授权列表移除
redis_client.srem("internal_authorized_phones", phone)
return True
5.3 异步发送核心服务
import asyncio
import redis
import objc
from Foundation import NSURL
from Messages import MSMessageRequest
from cryptography.fernet import Fernet
# 加载iMessage原生框架(仅内部使用)
objc.loadBundle("Messages", bundle_path="/System/Library/Frameworks/Messages.framework", module_globals=globals())
class InternalIMessageSender:
def __init__(self):
self.redis = redis.Redis(host="192.168.1.10", port=6379, db=0, decode_responses=True)
self.task_queue = "internal_notify_queue"
self.semaphore = asyncio.Semaphore(10) # 低并发,避免触发限制
# 加载加密密钥
with open("internal_key.key", "rb") as f:
self.key = f.read()
self.fernet = Fernet(self.key)
def _sync_send(self, phone, content):
"""同步发送(仅内部通知)"""
try:
if not phone.startswith("+"):
return False, "手机号格式错误"
# 构建收件人
recipient = NSURL.URLWithString_(f"tel:{phone}")
request = MSMessageRequest.alloc().init()
request.setRecipients_((recipient,))
request.setMessageText_(content)
# 发送(遵循苹果官方API限制)
error = request.sendSynchronouslyWithError_(None)
if error:
return False, str(error)
return True, "发送成功"
except Exception as e:
return False, str(e)
async def _async_send(self, task):
"""异步处理单条任务(含合规校验)"""
encrypted_phone, content = task
# 解密手机号
phone = self.fernet.decrypt(encrypted_phone.encode()).decode()
# 合规校验
if not is_authorized(phone) or is_unsubscribed(phone):
self.redis.hset(f"notify_result:{encrypted_phone}", mapping={
"status": 0,
"msg": "未授权或已退订"
})
return
# 并发控制
async with self.semaphore:
loop = asyncio.get_running_loop()
success, msg = await loop.run_in_executor(None, self._sync_send, phone, content)
# 记录日志(加密存储手机号)
self.redis.hset(f"notify_result:{encrypted_phone}", mapping={
"status": 1 if success else 0,
"msg": msg,
"send_time": self.redis.time()[0]
})
# 失败重试(最多2次)
if not success and self.redis.hincrby(f"retry_count:{encrypted_phone}", content[:10], 1) 2:
self.redis.rpush(self.task_queue, f"{encrypted_phone}|{content}")
async def consume_tasks(self):
"""持续消费内部任务队列"""
while True:
task_data = self.redis.blpop(self.task_queue, timeout=3)
if not task_data:
await asyncio.sleep(1)
continue
_, task_str = task_data
encrypted_phone, content = task_str.split("|", 1)
# 仅处理内部通知内容(过滤敏感词)
if "营销" in content or "推广" in content:
self.redis.hset(f"notify_result:{encrypted_phone}", mapping={
"status": 0,
"msg": "禁止发送营销内容"
})
continue
asyncio.create_task(self._async_send((encrypted_phone, content)))
# 随机间隔(模拟正常使用)
await asyncio.sleep(5 + asyncio.random() * 3)
if __name__ == "__main__":
# 仅内部服务器运行
sender = InternalIMessageSender()
print("内部通知发送服务启动(仅合规场景使用)")
asyncio.run(sender.consume_tasks())
5.4 内部任务提交工具(仅管理员使用)
import redis
from cryptography.fernet import Fernet
redis_client = redis.Redis(host="192.168.1.10", port=6379, db=0, decode_responses=True)
# 加载加密密钥
with open("internal_key.key", "rb") as f:
key = f.read()
fernet = Fernet(key)
def add_authorized_phone(phone):
"""添加内部授权手机号(仅管理员操作)"""
if phone.startswith("+"):
redis_client.sadd("internal_authorized_phones", phone)
return f"授权成功:{phone}"
return "手机号格式错误"
def submit_internal_notify(phone_list, content):
"""提交内部通知任务"""
# 内容校验(仅允许内部办公相关)
allowed_keywords = ["会议", "通知", "告警", "提醒", "运维", "办公"]
if not any(keyword in content for keyword in allowed_keywords):
return "仅允许发送内部办公相关通知"
# 批量加密并提交
for phone in phone_list:
if redis_client.sismember("internal_authorized_phones", phone) and not redis_client.sismember("internal_unsubscribed_phones", phone):
encrypted_phone = fernet.encrypt(phone.encode()).decode()
redis_client.rpush("internal_notify_queue", f"{encrypted_phone}|{content}")
return f"任务提交完成,待发送数量:{len(phone_list)}"
if __name__ == "__main__":
# 示例:内部会议通知
internal_phones = ["+8613800138000", "+8613900139000"] # 内部员工手机号
notify_content = "【内部通知】明天10点召开系统运维会议,请相关人员准时参加"
print(submit_internal_notify(internal_phones, notify_content))
六、合规设计核心细节
- 用户授权机制:仅通过内部管理员手动添加授权手机号,无公开注册渠道,留存授权记录
- 退订自由:提供add_unsubscribe接口,退订后立即停止发送并永久移除授权
- 内容限制:仅允许内部办公相关通知,过滤营销、推广等敏感词汇
- 数据安全:手机号加密存储,日志仅内部审计可访问,留存 1 年后自动清理
- 发送控制:低并发设计(单节点 10 并发),随机间隔 5-8 秒,遵循苹果官方限制
七、技术亮点与落地价值
- 异步并发优化:基于asyncio实现非阻塞发送,避免内部通知堆积,效率较同步提升 3 倍
- 合规内置:将授权校验、退订过滤、内容审核嵌入核心流程,从技术层面规避违规风险
- 轻量化部署:无复杂依赖,内部虚拟机 + Redis 即可搭建,适合中小型企业内部使用
- 可审计性:每条通知的发送状态、时间、结果全程记录,满足内部合规审计要求
八、注意事项(避免违规)
- 严禁将系统用于外部营销、未授权群发等违规场景,否则将面临账号封禁风险
- 虚拟机需使用内部 IP,不对外暴露服务,避免被滥用
- 定期清理无效手机号与过期日志,遵循数据最小留存原则
- 如需扩展外部用户通知,需先通过苹果官方 Business Chat 认证,严格遵守 TCPA/GDPR 等法规
九、总结
本文实现的内部 iMessage 通知系统,聚焦企业办公合规场景,通过 Python 异步编程、数据加密、合规校验等技术手段,在满足触达效率的同时,核心亮点在于将合规要求嵌入技术架构,通过授权校验、内容过滤、数据加密等机制,从根源上避免违规风险,同时通过异步并发提升内部通知处理效率,适合需要高效触达苹果生态内部用户的企业使用。