旧工程在Xcode27环境无法运行到iOS27模拟器

旧工程在Xcode26及以下环境可以运行到模拟器,切换到Xcode27后,提示mismatched platform, 同时,Xcode27的建议如下 iPhone 17 iOS simulator platform doesn't match XXX.app's supported platforms .you can change xxx.app's Base SDK or Supported Platforms to suppport iPhone 17 实际上旧工程的Base SDK设置为iOS ,Supported Platforms 也为iOS,和新建工程是一致的。 对比新旧工程的配置,发现Excluded Architectures有区别。 旧工程: 新工程 需要把Excluded Architectures里指定的arm64配置给删除,或者填写空

August 14, 2026 · 1 min · holdsky

配置Apple Universal Links

配置网站 创建 apple-app-site-association 在网站根目录或者 .well-known目录下创建 apple-app-site-association 文件。注意,文件没有后缀名。 文件是一个json文件,内容如下: { "applinks": { "apps": [], "details": [ { "appID": "团队ID.应用BundleID" "paths": ["/路径/*"] } ] } } 团队ID,和应用签名证书上的TeamId保存一致 路径 ,遵循url对应的规范,建议每一个App应用都使用独立的路径,便于管理 配置MIME 因为apple-app-site-association 的数据格式是json,所有对应MIME类型:application/json。 在对应的站点配置文件里设置,以nginx为例: # 如果文件在根目录 location /apple-app-site-association { default_type application/json; } # 如果文件在.well-known目录 location /.well-known/apple-app-site-association { default_type application/json; } 验证网站配置 打开 https://branch.io/resources/aasa-validator/ , 输入网站域名并验证配置是否正确。 关于缓存 Apple会缓存apple-app-site-association文件内容,缓存未必及时更新(一般不超过48小时),所以有时候通过Universal Links打开App会失败。 打开 https://app-site-association.cdn-apple.com/a/v1/你的网站域名可以查看缓存内容 创建 Universal Links网页 还需要在网站部署一个 Universal Links网页。网页的访问路径需要和apple-app-site-association文件中配置的路径一致。 当使用 Safari 浏览器打开Universal Links网页时,可唤起App。 ...

May 21, 2026 · 2 min · holdsky

Undefined symbols ... dispatch thunk

一个因为swift name mangled符号不一致导致的错误: dispatch thunk of Alamofire.MultipartFormData.append(_: Foundation.Data, withName: Swift.String, fileName: Swift.String?, mimeType: Swift.String?) -> ()", referenced from: 先说工程结构 工程A有一个静态framework target a,通过cocoapods以源码方式集成了封装Alamofire,封装Alamofire的一些能力。 workspace A |____ a (源码) |____Alamofire (源码) 构建输出静态framework a。 工程B有一个App Target,通过cocoapods以源码方式集成封装Alamofire,以静态库方式集成了 a。 workspace B |___App |____ a (静态库) |____ Alamofire (源码) ``` 在同一电脑,用同一个Xcode,设置了相同的swift version,结果App构建失败,找不到Alamofire里的符号。 经过一番观察,发现 "dispatch thunk of Alamofire.MultipartFormData.append(...)"是 Swift 编译器为带有默认参数、可变签名或需要方法桥接的函数生成的“thunk”符号。也就是说静态库a引用了这些由 Alamofire 生成的编译器私有符号,但最终链接器没有在任何被链接的库/框架中找到对应实现。 检查工程配置,首先排除Alamofire 的实现没有被链接到最终可执行文件(常见:静态库a引用但 App 没把 Alamofire 链接上)。 那么,最有可能的情况就是:工程B的Alamofire 与 工程A 使用不同的 Swift 编译环境/选项,导致 thunk 的名称/生成方式不一致,最终没有可匹配的符号。虽然Alamofire代码一摸一样,但是编译后的符号修饰方式不一样。 示意例子,例如同样是方法 test, 工程A中被修饰为 _$s12Alamofire4test tKF 工程B中被修饰为 $s12Alamofire4test_tKj ...

January 23, 2026 · 1 min · holdsky

TLS证书签发流程

签发类型 根据签发机构类型,TLS证书签发可以划分为两种: 公信CA(Certificate Authority)签发的证书(如DigiCert、Let’s Encrypt、Sectigo等) 企业内部私有CA签发的证书。 公信CA签发与企业内部私有CA签发,两者的区别是公信力不同。一般情况下,互联网环境不应信任企业内部私有CA签发的证书。 下面主要详细说明最常见的 公信CA签发域名验证(DV)、组织验证(OV)、扩展验证(EV)证书的过程,以及全网最流行的免费证书Let’s Encrypt为例的自动化流程。 公信CA签发证书的通用流程(DV/OV/EV) 生成CSR(Certificate Signing Request,证书签名请求) 在你的服务器上首先生成一对密钥:私钥(private key)和公钥(public key)。 使用私钥生成CSR文件,CSR里包含了: 域名(Common Name 或 SAN 列表) 组织信息(OV/EV证书需要) 公钥 国家、省份、城市、公司名称等(OV/EV需要) 常用命令(OpenSSL): openssl req -new -newkey rsa:2048 -nodes -keyout domain.key -out domain.csr 选择CA并提交申请 登录CA官网(或通过ACME协议自动化),选择证书类型(DV/OV/EV)、有效期。 上传CSR(或者有些CA让你直接在网页填写信息后帮你生成)。 域名控制权验证(DCV,Domain Control Validation) 这是所有公信证书都必须完成的步骤,证明你确实控制这个域名。常见三种方式: HTTP验证(最常见):CA给你一段随机token,你需要在域名下 .well-known/acme-challenge/ 路径放一个特定文件 示例: http://yourdomain.com/.well-known/acme-challenge/xxxxxx DNS验证:CA给你一段TXT记录,你需要在域名DNS管理面板添加 _acme-challenge.yourdomain.com TXT “xxxxxx” Email验证(较少用):给域名whois邮箱或管理员邮箱(如admin@、webmaster@)发邮件,点链接确认 DV证书只做这一步;OV/EV还要额外验证组织合法性。 (OV/EV专属)组织信息验证 核实公司工商注册信息(通过第三方数据库如Dun & Bradstreet) 核实营业执照、法人身份 电话回访确认申请人身份和授权 验证域名管理权归属公司(whois + 额外证明文件) CA签发证书 CA用自己的中间证书或根证书的私钥,对你的公钥+域名+有效期等信息进行签名,生成证书文件(.crt 或 .pem) 同时会下发CA链证书(中间证书 bundle,参考下面附录) 下载并安装证书 你把证书、公钥、私钥安装到Web服务器(Nginx/Apache/IIS等) 重启服务,以使证书生效 最常见的自动化签发:Let’s Encrypt(ACME协议) Let’s Encrypt是目前全球使用量最大的免费CA,90天有效期,完全自动化。 ...

December 10, 2025 · 2 min · holdsky

后端登陆模块常见的校验策略

后端登录模块常见的校验策略(以安全为首要目标),从输入校验到风控防御,通常会分多层实现,以下是生产环境中广泛采用的实战校验策略汇总: 1. 基础参数校验(最基本的一道防线) 用户名/手机号/邮箱:非空、格式正则、长度限制、防注入(预编译语句或ORM自动处理) 密码:非空、长度(通常8-32位)、不能包含用户名等 验证码(图片/短信/邮箱):非空、长度固定、是否过期(通常60-300秒) 请求必传字段完整性(deviceId、timestamp、sign等) 2. 账号状态校验 账号是否存在 账号是否被禁用/冻结/注销 账号是否需要强制修改密码(首次登录、密码过期) 账号是否被加入黑名单 3. 密码相关安全校验 密码明文传输 → 必须强制HTTPS 密码加密传输(前端通常用RSA或SM2加密,后端私钥解密) 后端密码存储:bcrypt、scrypt、Argon2(加盐,绝不用MD5/SHA1明盐) 密码输错次数限制(单账号5-10次后锁定30分钟~永久,需要人工解封) 密码复杂度策略(可选:大小写+数字+特殊字符) 4. 验证码多重校验(防止暴力破解) 图片验证码:防自动化脚本 短信/邮箱验证码:限流(同一手机号/IP/设备1分钟1条,1小时5条,24小时10条) 验证码有效期 + 只能使用一次 验证码输错次数限制(3-5次后需要重新获取) 5. 登录风控与异常检测(核心防爆破、防薅羊毛) 同一账号短时间内多次登录失败次数超限 → 锁定 同一IP短时间内大量不同账号登录失败 → IP封禁 同一设备(deviceId/deviceFingerprint)短时大量账号尝试 → 设备封禁 异地登录检测(根据IP归属地,与历史登录地对比,差距>1000km触发二次验证) 短时间内大规模登录请求 → 触发全局限流或人工介入 用户代理(User-Agent)异常或频繁切换 无头浏览器、模拟器、爬虫特征检测 6. 会话安全策略 登录成功后颁发token(推荐无状态JWT,或服务端Session+Redis) JWT需设置合理过期时间(accessToken 15min ~ 2h,refreshToken 7 ~ 30天) 支持单点登录(同账号新登录踢掉旧设备) 支持多端互踢配置(Web端登录踢掉App等) 敏感操作二次验证(修改密码、支付等需重新输入密码或验证码) 7. 常见辅助安全措施 登录接口全局限流(令牌桶/漏桶算法,如QPS>500触发降级) 关键日志记录安全审计日志(登录IP、设备、时间、结果等) 登录成功后下发设备绑定关系(可用于后续风控) 支持可信设备管理(记住设备30天免二次验证) 密码传输前端可使用RSA/SM2非对称加密,或使用SRP协议(密码永不离客户端) 8. 特殊场景校验 第三方登录(微信、QQ、Apple):校验state防CSRF,校验code是否已被使用 无密码登录(短信登录):必须绑定手机号 + 短信验证码 + 设备风控 扫码登录:防钓鱼站点、心跳保活、状态机严格校验 推荐的实战分层校验伪代码结构(Java/SpringBoot示例) @PostMapping("/login") public Result login(@RequestBody LoginDTO dto) { // 1. 基础参数校验 validateParams(dto); // 2. 验证码校验 captchaService.check(dto.getCaptchaId(), dto.getCode()); // 3. 风控预检(IP、设备、全球限流) riskControl.preCheck(dto); // 4. 账号状态与密码校验 User user = userService.checkAccountAndPassword(dto); // 5. 登录风控后置(异地、异常检测) riskControl.afterLogin(user, request); // 6. 生成token并记录登录日志 String token = jwtUtil.createToken(user); loginLogService.asyncSave(user, request, true); return Result.ok(token); } 总结:现代后端登录模块已远不止“用户名+密码正确就放行”,而是一整套“参数→验证码→风控→账号状态→密码→二次验证→会话管理→日志”的立体防御体系。缺任何一层都可能被拖库、撞库、短信轰炸、羊毛党攻破。 ...

December 8, 2025 · 1 min · holdsky

在hugo里集成mermaid图支持

本文环境: hugo v0.152.2+extended+withdeploy darwin/amd64 BuildDate=2025-10-24T15:31:49Z VendorInfo=Homebrew 一般使用了hugo主题的话,默认是集成了mermaid图。 若不使用hugo主题,那么就需要自己手动集成了。 步骤如下: 创建 layouts/_markup/render-codeblock-mermaid.html 负责开启一个mermaid图的html区块,内容如下: <pre class="mermaid"> {{- .Inner | safeHTML }} </pre> {{ .Page.Store.Set "hasMermaid" true }} 修改 layouts/_default/baseof.html 如果没有该文件就创建一个。默认情况下,所有html页面的主体结构和内容由这个文件渲染。和mermaid图相关的内容如下: <!DOCTYPE html> <html lang="zh-CN"> <body> ...... ...... ...... <!-- 必须放到最后在 (.Content 之下),因为这渲染钩子直到执行 .Content 之后才会被处理),否则.Page.Store.Get "hasMermaid"永远为false --> {{ if .Page.Store.Get "hasMermaid" }} <!-- 只有文章里写了 mermaid 才加载下面这些资源,节省性能 --> <script type="module"> import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs'; mermaid.initialize({ startOnLoad: true }); </script> {{ end }} </body> </html> 添加一个mermaid图看看效果 在网站的任意一个markdown文件里添加样例,看下效果 ...

December 1, 2025 · 1 min · holdsky

TLS 1.3前向安全的通俗解释

前向安全 前向安全(Forward Secrecy,简称 PFS)是 TLS(以及其他加密协议)中一个非常重要的安全属性,简单来说: “即使攻击者将来拿到了服务器的长期私钥(即证书私钥),也无法解密之前已经完成的所有 TLS 会话” 这就是前向安全的核心意义:过去的通信记录永远是安全的,即使私钥彻底泄露。 前向安全在TLS 1.3中强制开启的,安全性由 椭圆曲线离散对数问题(ECDLP) 保障 ECDH(椭圆曲线 Diffie-Hellman)最核心的数学原理 一句话就能说清楚: “乘法可以随便做,除法几乎做不出来” 我们用最常用、最直观的曲线 X25519 / Curve25519 来解释(TLS 1.3 里 90% 以上用的就是它)。 1. 公共参数(全世界都一样) 选定一个安全的椭圆曲线:y² = x³ + 486662x² + x(模 2²⁵⁵-19) 选定一个基点(base point)G,一个固定的点,坐标是已知的: G = (9, 447…)(具体数字不重要,只要大家用同一个就行) 2. 双方各自生成临时私钥(就是随机数) 爱丽丝(Alice,客户端)随机选一个 256 bit 的整数作为私钥: a = 随机数(比如 3f1a…c2,绝对保密!) 鲍勃(Bob,服务器)也随机选一个 256 bit 的整数: b = 随机数(比如 7d2b…e9,绝对保密!) 3. 计算并公开“公钥”(其实就是把私钥乘以基点 G) 爱丽丝算出自己的临时公钥: A = a × G 鲍勃算出自己的临时公钥: B = b × G ...

November 26, 2025 · 2 min · holdsky

using bridging headers with module interfaces is unsupported

Xcode创建framework,选择语言Swift。在使用Swift语言编写framework时,如果要引入Objective-C的代码或者其他Objective-C的库,可能会报错“using bridging headers with module interfaces is unsupported”。这通常是因为在模块接口中使用了桥接头文件(bridging-header),而这是不被支持的, 一般有两种解决办法: 一、如果这个framework不需要对外发布,那么将Build Setting里的Build Library For Distrubution 设置为NO,则可以继续使用桥接头文件(bridging-header)。 二、使用module map文件替换桥接头文件(bridging-header),具体操作如下: 1、在framework工程里创建一个头文件,如 Test.h 在Build Setting里设置User Header Search Paths,如 $(SRCROOT)/$(PRODUCT_NAME)。设置的路径需要能找到 Test.h 文件 2、在 Test.h 文件里引入需要引用的Objective-C头文件 #import <MyLib/MyLib.h> 3、创建一个module map文件, module.modulemap,内容如下 module TestModule (名字自定义) { header "Test.h" export * } 4、在Swift文件里,使用import指令,然后就可以调用Objective-C的方法了 import TestModule

September 26, 2025 · 1 min · holdsky

创建Objective-C可使用的Swift Framework

默认创建的使用Swift语言编写的framework是没有头文件的,如果另一个使用Objective-C语言编写的target想用引入swift framework,则需要在swift framework的工程上打开一些配置 同时使用public 和 @objc标记 要给Objective-C暴露swift编写的api,则需要使用public 和 @objc标记 @objc public class MySwiftClass: NSObject { @objc public func doSomething() { print("Doing something in Swift") } } 生成 Objective-C 兼容头文件 Swift Framework 的 Build Settings 中: Defines Module 设置为 YES。 Install Generated Header 设置为 YES。 这会生成一个 FrameworkName-Swift.h 头文件,包含 Swift 代码的 Objective-C 接口。 在 Objective-C 项目中导入导入 Swift Framework 的头文件 #import <FrameworkName/FrameworkName-Swift.h> MySwiftClass *swiftObject = [[MySwiftClass alloc] init]; [swiftObject doSomething];

September 24, 2025 · 1 min · holdsky

移动端环境,Kotlin Multiplatfrom 调用C/C++方法

前言 本文成文环境: macOS 26 Xcode 26 以及 iOS SDK Android Studio MeerKat Feature Drop 以及 Android SDK、NDK JDK 17 Gradle 8.11.1 Kotlin Multiplatfrom 通过Cinterop工具调用C/C++方法,也可以通过JNI规范调用C/C++方法。 本质上,都是调用C方法,C++方法需要通过 extern "C" 修饰为C方法。 本文将介绍Kotlin/Native在Android和iOS环境,如何调用C/C++方法。 Android Studio 推荐使用 Android Studio 开发Kotlin/Native (Kotlin更像是一款Android开发语言) Gradle Android Studio的御用构建工具(相比其构建工具,复杂且难用) CMake 一个事实上的C/C++构建标准工具,在Android和iOS环境调用各自平台的工具链构建C/C++。 创建 Kotlin Multiplatfrom 工程 如果Android Studio没有 Kotlin Multiplatfrom 工程模版,则需要先安装 Kotlin Multiplatfrom插件。 安装完成后,使用 Kotlin Multiplatfrom 工程模版创建一个工程(创建过程中,需要选择iOS的包管理方式,选择cocoapods) 镜像 因为环大陆的网络墙问题,可能需要配置下镜像仓库 1、找到工程根目录下的 settings.gradle.kts,添加下镜像仓库(下面配置的是阿里云的maven仓库) // settings.gradle.kts pluginManagement { repositories { maven{ setUrl("https://maven.aliyun.com/repository/public") } maven{ setUrl("https://maven.aliyun.com/repository/central") } maven{ setUrl("https://maven.aliyun.com/repository/gradle-plugin") } ... ... ... } } dependencyResolutionManagement { repositories { maven{ setUrl("https://maven.aliyun.com/repository/public") } maven{ setUrl("https://maven.aliyun.com/repository/central") } maven{ setUrl("https://maven.aliyun.com/repository/gradle-plugin") } ... ... ... } } 2、找到根目录下 gradle/wrapper/gradle-wrapper.properties,替换为distributionUrl ...

July 18, 2025 · 3 min · holdsky