第一章:Golang游戏客户端Mac App Store审核失败的全局认知
Mac App Store(MAS)对游戏类应用的审核具有高度结构性约束,尤其当客户端基于 Golang 构建时,其底层运行机制与 Apple 的沙盒、签名及隐私规范存在系统性张力。Golang 默认生成静态链接的二进制文件,不依赖系统动态库,这一特性在提升分发便利性的同时,却常触发 MAS 审核中关于“可执行代码完整性”“运行时权限透明度”和“符号表暴露风险”的深度检查。
常见拒审核心动因
- 无理由的硬编码网络请求:如客户端内嵌未声明的 CDN 域名或第三方分析 SDK(如 Sentry、Firebase),违反《App Store 审核指南》第 5.1.2 条;
- 沙盒外文件访问行为:Golang
os.OpenFile或ioutil.ReadFile若路径指向~/Library/Preferences以外的用户目录(如~/Documents),且未在entitlements.plist中显式声明com.apple.security.files.user-selected.read-write; - 未签名的辅助进程或插件:部分游戏使用 Go 编写的后台音频服务或更新器,若未通过
codesign --deep --force --sign "Apple Distribution: XXX" ./helper逐层签名,将导致ITMS-90237错误。
关键合规验证步骤
执行以下命令校验 MAS 提交包基础合规性:
# 1. 检查二进制是否启用 hardened runtime(必需)
codesign -d --entitlements :- YourGame.app/Contents/MacOS/YourGame | grep 'com.apple.security.cs'
# 2. 验证沙盒 entitlements 是否完整嵌入(注意:必须含 com.apple.security.app-sandbox = true)
codesign -d --entitlements :- YourGame.app | xmllint --xpath '//key[text()="com.apple.security.app-sandbox"]/following-sibling::true' -
# 3. 扫描是否存在禁止的 API 调用(如 ptrace、task_for_pid)
otool -Iv YourGame.app/Contents/MacOS/YourGame | grep -E "(ptrace|task_for_pid|sysctl)"
MAS 审核策略特征对比
| 维度 | 传统 macOS 分发 | Mac App Store 分发 |
|---|---|---|
| 代码签名要求 | Ad-hoc 或 Developer ID | 强制 Apple Distribution 证书 |
| 运行时权限模型 | 用户授权后自由访问 | 沙盒限制 + entitlements 显式声明 |
| 网络通信透明度 | 无强制域名白名单 | 所有出站域名须在 Info.plist 的 NSAppTransportSecurity 中声明 |
Golang 构建链需在 go build 后插入 codesign 和 productbuild 流程,任何跳过 entitlements 注入或 runtime 硬化环节的操作,均会导致审核在自动化初筛阶段直接失败。
第二章:ATS配置的深度解析与实战修复
2.1 ATS强制策略原理与Go HTTP栈的兼容性分析
ATS(App Transport Security)是iOS/macOS强制启用TLS 1.2+、证书校验及加密套件限制的安全策略。其核心通过NSURLSession底层拦截HTTP请求,拒绝不合规连接。
TLS协商阶段拦截机制
ATS在SSL handshake前注入校验逻辑,若服务端不支持TLS 1.2或使用弱签名算法(如SHA-1),系统直接终止连接,不进入Go的net/http.Transport流程。
Go HTTP栈的被动适配约束
Go默认启用TLS 1.2+,但http.Transport无法绕过系统级ATS拦截:
tr := &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12, // 必须≥1.2
CurvePreferences: []tls.CurveID{tls.CurveP256},
},
}
此配置仅影响Go内部TLS握手参数;若ATS已因域名未列在
NSExceptionDomains中提前拒绝,该配置永不生效。
| 兼容维度 | ATS强制行为 | Go可干预点 |
|---|---|---|
| 协议版本 | 拒绝 | tls.Config.MinVersion |
| 证书验证 | 系统级OCSP/CRL校验 | InsecureSkipVerify=false(不可绕过ATS) |
| 加密套件 | 限定AEAD类(如AES-GCM) | CipherSuites需匹配ATS白名单 |
graph TD
A[Go http.Client.Do] --> B[触发 NSURLSession]
B --> C{ATS检查:域名/协议/证书}
C -->|通过| D[进入Go TLS握手]
C -->|拒绝| E[Error: NSURLErrorNotConnected]
2.2 Info.plist中NSAppTransportSecurity的精细化配置实践
理解默认限制与例外场景
iOS 9+ 强制启用 ATS(App Transport Security),要求所有 HTTP 连接必须使用 TLS 1.2+。但实际开发中需对特定域名或服务做差异化放行。
配置结构与关键键值
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/> <!-- 禁用全局豁免,强制逐项声明 -->
<key>NSExceptionDomains</key>
<dict>
<key>api.example.com</key>
<dict>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/> <!-- 允许非ECDHE密钥交换 -->
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string> <!-- 降级兼容旧服务 -->
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
逻辑分析:NSAllowsArbitraryLoads 设为 false 是安全基线;NSExceptionDomains 下按域名粒度控制,NSExceptionRequiresForwardSecrecy 关闭前向保密可适配不支持 ECDHE 的中间件,但需明确承担风险。
常见例外策略对比
| 场景 | 推荐配置 | 安全影响 |
|---|---|---|
| 内部测试 API(HTTP) | NSExceptionAllowsInsecureHTTPLoads = YES |
仅限调试,禁止上架 |
| 遗留支付 SDK(TLS 1.0) | NSExceptionMinimumTLSVersion = TLSv1.0 |
高风险,须附 App Store 审核说明 |
安全验证流程
graph TD
A[编译后检查 Info.plist] --> B{是否含 NSAllowsArbitraryLoads=true?}
B -- 是 --> C[拒审风险]
B -- 否 --> D[逐域验证 TLS 版本与证书链]
D --> E[使用 nscurl --ats-diagnostics 测试]
2.3 Go net/http与TLS 1.3握手失败的诊断与绕行方案
常见失败现象
客户端报 x509: certificate signed by unknown authority 或 tls: unexpected message,多见于自签名证书、中间 CA 缺失或服务端禁用 TLS 1.3 早期数据(0-RTT)。
快速诊断步骤
- 启用 Go TLS 调试日志:
GODEBUG=tls13=1 go run main.go - 使用
curl -v --tlsv1.3 https://example.com验证服务端兼容性 - 检查证书链完整性:
openssl s_client -connect example.com:443 -tls1_3 -showcerts
绕行方案对比
| 方案 | 适用场景 | 安全影响 | 代码侵入性 |
|---|---|---|---|
InsecureSkipVerify=true |
开发/测试环境 | ⚠️ 完全跳过证书校验 | 低 |
自定义 RootCAs |
内网私有 CA | ✅ 保留链验证 | 中 |
| 强制降级至 TLS 1.2 | 遗留服务端 | ⚠️ 放弃 1.3 安全增强 | 中 |
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // 仅限调试!
MinVersion: tls.VersionTLS12,
},
}
此配置强制使用 TLS 1.2 并跳过证书验证。
MinVersion控制协议下限,InsecureSkipVerify绕过VerifyPeerCertificate回调,但会禁用证书吊销检查与域名匹配。
推荐修复路径
graph TD
A[握手失败] --> B{是否证书链不全?}
B -->|是| C[添加 RootCAs]
B -->|否| D{是否服务端 TLS 1.3 实现有缺陷?}
D -->|是| E[协商降级至 TLS 1.2]
D -->|否| F[升级 Go 版本 ≥1.19]
2.4 嵌入式WebView(如WKWebView)的ATS例外声明实操
iOS 9+ 强制启用 App Transport Security(ATS),但 WKWebView 加载本地 HTML、测试域名或自签名证书资源时需配置例外。
配置 Info.plist 的 ATS 例外项
需在 NSAppTransportSecurity 字典中声明:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
</dict>
</dict>
✅ NSAllowsArbitraryLoadsInWebContent 允许 WKWebView 绕过 ATS(不影响 NSURLSession);
✅ NSExceptionDomains 精确控制特定域名策略,比全局放行更安全。
例外策略对比
| 键名 | 适用场景 | 安全等级 |
|---|---|---|
NSAllowsArbitraryLoadsInWebContent |
仅 WKWebView 加载 HTTP/本地资源 | ⚠️ 中(隔离作用域) |
NSAllowsArbitraryLoads |
全局禁用 ATS(含网络请求) | ❌ 低(审核风险高) |
加载流程示意
graph TD
A[WKWebView loadRequest] --> B{ATS 检查}
B -->|本地文件/localhost| C[查 NSExceptionDomains]
B -->|HTTPS 正常域名| D[执行完整 TLS 校验]
C --> E[允许加载]
D --> F[校验失败则中断]
2.5 自建CDN/后端服务的证书链验证与Go client端适配验证
自建CDN或边缘后端常使用私有CA签发的证书,Go默认仅信任系统根证书池,需显式注入自定义CA。
客户端证书链校验配置
rootCAs := x509.NewCertPool()
rootCAs.AppendCertsFromPEM(caPEM) // 自建CA根证书(PEM格式)
tr := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: rootCAs,
InsecureSkipVerify: false, // 必须禁用跳过验证
},
}
client := &http.Client{Transport: tr}
RootCAs指定可信根集;InsecureSkipVerify=false强制执行完整链验证(包括中间证书可达性)。
常见证书链问题对照表
| 现象 | 根因 | 修复方式 |
|---|---|---|
| x509: certificate signed by unknown authority | 缺失中间证书 | 将中间CA拼接至服务端证书链 |
| x509: certificate has expired | 服务端证书过期 | 更新全链(根+中间+终端) |
验证流程图
graph TD
A[Go client发起TLS握手] --> B{是否提供完整证书链?}
B -->|否| C[验证失败:unknown authority]
B -->|是| D[逐级向上验证签名+有效期]
D --> E[根CA是否在RootCAs中?]
E -->|否| C
E -->|是| F[连接建立成功]
第三章:Notarization签名链的构建与可信传递
3.1 Apple公证服务(Notarization)全流程与Go二进制签名链断裂根因
Apple 公证服务要求所有 macOS 外部分发的二进制必须经 codesign 签名后上传至 Apple 服务器验证,再通过 stapler staple 嵌入公证票证。Go 构建的二进制默认启用 CGO_ENABLED=0 时生成静态链接可执行文件,但其符号表与 Mach-O 加载器元数据不兼容 Apple 的公证校验器。
公证失败典型路径
# 构建并签名(关键:必须带 --deep 和 --strict)
$ go build -o myapp main.go
$ codesign --force --deep --sign "Developer ID Application: XXX" --strict --timestamp myapp
$ xcrun notarytool submit myapp --keychain-profile "AC_PASSWORD" --wait
--deep递归签名嵌入资源(如 cgo 动态库),--strict启用运行时完整性检查;Go 静态二进制无 dylib 依赖,但公证器仍扫描__LINKEDIT段完整性——若 Go 版本 -buildmode=pie 缺失导致段偏移异常,触发链验证失败。
根因对比表
| 因素 | Go ≤1.20 | Go ≥1.21 |
|---|---|---|
| 默认 PIE 支持 | ❌(需显式 -buildmode=pie) |
✅(自动启用) |
__PAGEZERO 保护 |
未对齐,触发 hardened runtime 拒绝 |
对齐且受 cs_flags 正确标记 |
公证流程关键节点
graph TD
A[Go build] --> B[codesign with --strict]
B --> C[notarytool submit]
C --> D{Apple 服务扫描}
D -->|Mach-O segment check| E[PIE/CS flags validation]
D -->|Code requirement eval| F[Signature chain trace]
E -->|fail| G[“Signature chain broken”]
F -->|fail| G
3.2 使用codesign + altool + notarytool构建可验证的Go bundle签名链
macOS 要求分发的 Go 应用(尤其是 GUI bundle)必须具备完整签名链:代码签名 → Apple ID 认证 → 苹果公证服务校验。
签名与公证流程概览
graph TD
A[Go 构建 bundle] --> B[codesign --deep --force --sign \"ID\" MyApp.app]
B --> C[notarytool submit MyApp.app --key-id KEY_ID --issuer ISSUER --password @keychain:AC_PASSWORD]
C --> D[staple MyApp.app]
关键步骤示例
# 深度签名整个 bundle(含嵌套二进制、资源)
codesign --deep --force --options=runtime \
--sign "Apple Development: dev@example.com" \
--entitlements entitlements.plist \
MyApp.app
--deep 递归签名所有嵌套可执行文件;--options=runtime 启用运行时硬编码签名验证(必需 macOS 10.15+);--entitlements 绑定权限配置。
工具演进对照表
| 工具 | 状态 | 用途 |
|---|---|---|
altool |
已弃用 | 旧式公证提交(2023 年后失效) |
notarytool |
推荐 | 新一代公证 CLI,支持 MFA 和细粒度密钥管理 |
公证成功后务必执行 xattr -d com.apple.quarantine MyApp.app 清除隔离属性,确保首次启动无阻断。
3.3 Go build -ldflags -H=universal与Mach-O签名完整性冲突解决
当使用 go build -ldflags "-H=universal" 构建 macOS 跨架构二进制(x86_64 + arm64)时,Go 会生成 FAT Mach-O 文件,但该操作会重写 __LINKEDIT 段并破坏已签名的代码签名。
冲突根源
-H=universal触发 Go linker 的 FAT 归档逻辑,覆盖原始签名结构;codesign --verify报错:code object is not signed at all或invalid signature。
解决方案流程
# 先构建通用二进制(不签名)
go build -ldflags="-H=universal" -o app-universal main.go
# 清除残留签名(如有)
codesign --remove-signature app-universal
# 重新签名(必须指定 entitlements 且使用 ad-hoc 或开发者证书)
codesign --force --sign "Apple Development: name@email.com" \
--entitlements entitlements.plist \
--options runtime \
app-universal
参数说明:
--options runtime启用 hardened runtime;--force覆盖旧签名;--entitlements确保权限声明与签名一致。
关键约束对比
| 阶段 | 是否允许签名 | 原因 |
|---|---|---|
-H=universal 后 |
❌ 不可预先签名 | FAT 头写入会破坏签名结构 |
codesign 后 |
✅ 必须签名 | 系统校验依赖完整签名链 |
graph TD
A[go build -H=universal] --> B[生成FAT Mach-O]
B --> C[销毁原有__LINKEDIT签名]
C --> D[codesign --remove-signature]
D --> E[codesign --force --sign ...]
E --> F[验证通过]
第四章:Hardened Runtime权限声明的精准控制与沙盒适配
4.1 Hardened Runtime核心限制(如library validation、device access)与Go运行时行为映射
Hardened Runtime 通过签名验证、动态库白名单和设备访问拦截强化 macOS 应用安全。Go 程序因静态链接默认规避 dylib 加载,但 cgo 启用时会触发 library validation 机制。
library validation 的 Go 行为影响
当启用 CGO_ENABLED=1 且调用 dlopen()(如 via net 或 os/user 包),系统校验所有加载路径是否在签名嵌入的 Library Validation entitlement 白名单中:
# 编译时需显式声明允许的动态库路径
codesign --entitlements entitlements.plist \
--sign "Apple Development" myapp
逻辑分析:
entitlements.plist中com.apple.security.cs.allow-jit和com.apple.security.cs.disable-library-validation不适用于 Go;必须使用com.apple.security.cs.allowed-library-path列出绝对路径(如/usr/lib/libSystem.B.dylib)。未声明路径将触发kern_invalid_address异常。
设备访问限制映射
| Go 操作 | Hardened Runtime 检查点 | 是否默认阻断 |
|---|---|---|
os.Open("/dev/usb") |
I/O Kit device access policy | 是(需 com.apple.security.device.usb) |
syscall.Syscall(SYS_IOCTL, ...) |
Kernel extension entitlement | 是(需 com.apple.security.kernel.extended) |
// 示例:绕过设备访问限制的合规写法(需 entitlement + Info.plist 声明)
func safeUSBProbe() error {
f, err := os.Open("/dev/disk2") // 仅当 entitlement 显式授权 /dev/disk*
if err != nil {
return fmt.Errorf("device access denied: %w", err) // runtime panics if unentitled
}
defer f.Close()
return nil
}
参数说明:
/dev/disk*必须在com.apple.security.device.diskentitlement 中通配声明;否则open(2)返回EPERM,Go 运行时将其转为os.PathError。
graph TD A[Go 程序启动] –> B{cgo enabled?} B –>|Yes| C[触发 library validation] B –>|No| D[静态链接,绕过校验] C –> E[检查 allowed-library-path] E –>|Match| F[加载成功] E –>|Mismatch| G[dyld abort]
4.2 Info.plist中com.apple.security.* entitlements的最小化声明策略与验证工具链
Apple 安全模型要求 com.apple.security.* 权限必须显式声明且严格最小化。过度声明将导致审核失败或沙盒拒绝。
最小化实践原则
- 仅声明当前功能必需的 entitlement;
- 避免
com.apple.security.network.client/server全局开启,按域名或端口细化; com.apple.security.files.user-selected.read-write优于com.apple.security.files.downloads.read-write。
验证工具链示例
<!-- Info.plist 片段 -->
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<false/> <!-- 显式禁用非必要项 -->
该配置强制应用仅使用网络客户端能力,且不访问下载目录——符合 App Sandbox 的“默认拒绝”原则。<false/> 声明虽非必需,但可提升审查可读性与意图明确性。
Entitlements 检查流程
graph TD
A[build.xcarchive] --> B[security find-identity -p codesigning]
B --> C[codesign --display --entitlements :- MyApp.app]
C --> D[entitlements-lint.py 静态校验]
| Entitlement | 推荐值 | 触发场景 |
|---|---|---|
com.apple.security.app-sandbox |
true |
必需启用沙盒 |
com.apple.security.network.client |
true |
发起 HTTP 请求 |
com.apple.security.print |
false |
无打印功能时显式关闭 |
4.3 Go CGO调用系统API(如IOKit、CoreAudio)时的权限动态申请与沙盒兼容模式
权限声明与 Info.plist 配置
macOS 要求 IOKit 和 CoreAudio 等敏感 API 必须在 Info.plist 中显式声明权限键:
| 权限类型 | Info.plist 键名 | 说明 |
|---|---|---|
| 设备枚举 | com.apple.developer.driverkit |
IOKit 设备访问必需 |
| 音频硬件控制 | com.apple.security.audio.input |
CoreAudio 输入设备访问 |
| 辅助功能权限 | NSAccessibilityDescription |
需用户手动授权 |
动态权限检查与降级逻辑
// CGO 导出函数,用于运行时检测权限
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
int has_iokit_privilege() {
io_service_t service = IOServiceGetMatchingService(
kIOMasterPortDefault,
IOServiceMatching("IOHDIXController") // 非侵入式探测
);
if (service == IO_OBJECT_NULL) return 0;
IOObjectRelease(service);
return 1;
}
该函数不触发权限弹窗,仅试探性获取服务句柄;返回 表示无权或被沙盒拦截,此时应切换至 AVFoundation 等沙盒友好替代路径。
沙盒兼容流程
graph TD
A[Go 调用 CGO 函数] --> B{has_iokit_privilege?}
B -->|true| C[执行 IOKit 原生操作]
B -->|false| D[启用 AVAudioSession 回退]
D --> E[请求 NSMicrophoneUsageDescription]
4.4 游戏资源热加载、插件机制与Hardened Runtime的dylib加载策略重构
热加载与插件解耦设计
采用基于NSBundle的模块化插件架构,配合资源哈希校验与版本元数据(plugin.json)实现安全热替换:
// 加载插件bundle并验证签名与哈希
let bundle = Bundle(url: pluginURL)!
guard bundle.isSigned && bundle.validateSignature() else {
throw PluginError.invalidSignature
}
let metadata = try JSONDecoder().decode(PluginManifest.self, from:
Data(contentsOf: pluginURL.appendingPathComponent("plugin.json")))
此逻辑确保插件来源可信且内容未篡改:
isSigned检查代码签名完整性,validateSignature()调用SecStaticCodeCreateWithPath验证Hardened Runtime兼容签名;PluginManifest含resourceHash字段用于运行时资源一致性校验。
Hardened Runtime下的dylib加载限制应对
| 场景 | 传统方式 | Hardened Runtime合规方案 |
|---|---|---|
| 动态加载 | dlopen()任意路径 |
仅允许@rpath相对路径 + com.apple.security.cs.allow-jit entitlement |
| 符号解析 | dlsym() |
使用NSClassFromString + objc_getClass()替代C函数符号绑定 |
插件生命周期协同流程
graph TD
A[资源变更监听] --> B{文件哈希匹配?}
B -->|否| C[触发增量编译+签名]
B -->|是| D[卸载旧Bundle]
C --> E[加载新Bundle]
D --> E
E --> F[调用didLoadPlugin协议方法]
第五章:从审核失败到一次过审的工程化闭环
在某金融类App的iOS上架实践中,团队曾连续3次因“隐私政策未明确说明数据共享场景”被App Store拒绝。第4次提交前,我们摒弃了“人工补文档+临时改代码”的救火模式,构建了一套覆盖需求、开发、测试、合规、发布全链路的工程化闭环。
隐私合规前置评审机制
产品PRD阶段即嵌入《GDPR/CCPA/《个人信息保护法》交叉检查表》,由法务+安全+研发三方联合签字。例如,当需求提出“接入第三方广告SDK”时,系统自动触发弹窗提示:“需同步补充SDK数据流向图、用户授权时机说明、退出机制实现方案”,否则无法进入研发排期。
自动化合规扫描流水线
CI/CD中集成自研工具PrivacyGuard,在每次Git Push后自动执行三类扫描:
- 静态分析:识别
[NSUserDefaults standardUserDefaults]等敏感API调用,关联代码注释中的// @privacy: user_id, encrypted标签; - 配置校验:比对
Info.plist中NSLocationWhenInUseUsageDescription与PrivacyPolicy.md中地理位置章节的措辞一致性; - 截图验证:通过Appium截取首次启动页、权限请求弹窗、设置页截图,OCR识别文字并匹配合规文案库。
| 扫描类型 | 触发节点 | 修复SLA | 误报率 |
|---|---|---|---|
| 静态分析 | 编译前 | ≤2小时 | 3.2% |
| 配置校验 | 构建后 | ≤15分钟 | 0% |
| 截图验证 | 测试包生成后 | ≤30分钟 | 8.7% |
审核失败根因反哺机制
建立审核失败案例知识库,每条记录包含:App Store回复原文(含审核员ID)、对应代码Commit Hash、失败截图、修复Diff Patch。当新需求涉及相同关键词(如“tracking”、“IDFA”),Jira自动推送历史案例至开发者看板。
flowchart LR
A[需求评审] --> B{含数据采集?}
B -->|是| C[触发隐私影响评估PIA]
B -->|否| D[进入常规开发]
C --> E[生成数据流图DFD]
E --> F[嵌入代码注释]
F --> G[CI自动校验DFD与实际调用一致性]
G --> H[不一致则阻断构建]
多环境策略化隐私开关
采用Feature Flag管理不同地区合规要求:
privacy_mode=strict(欧盟):禁用所有非必要追踪,广告ID强制重置;privacy_mode=standard(中国大陆):保留基础设备标识,但用户可一键清除;privacy_mode=none(内部测试):关闭所有拦截逻辑。
该开关由启动时读取https://config.example.com/privacy/{region}动态加载,避免硬编码导致合规风险。
审核材料自动化生成
提交前执行make appstore-pack命令,自动完成:
- 从Git Tag提取版本号与变更日志;
- 调用
PrivacyGuard --export-report生成带时间戳的合规证明PDF; - 截取最新版权限请求流程视频(MP4,≤300MB);
- 拼接
AppStoreConnect.json元数据文件,包含所有第三方SDK名称、版本、用途声明。
该流程已在6个App中稳定运行,最近12次提交全部一次过审,平均审核周期缩短至38小时。
