<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>holdsky &#8211; 编程技术记录</title>
	<atom:link href="https://blog.z6z8.cn/author/holdsky/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.z6z8.cn</link>
	<description>世界你好!</description>
	<lastBuildDate>Fri, 23 Jan 2026 10:23:31 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.3</generator>
	<item>
		<title>Undefined symbols &#8230; dispatch thunk</title>
		<link>https://blog.z6z8.cn/2026/01/23/undefined-symbols-dispatch-thunk/</link>
					<comments>https://blog.z6z8.cn/2026/01/23/undefined-symbols-dispatch-thunk/#respond</comments>
		
		<dc:creator><![CDATA[holdsky]]></dc:creator>
		<pubDate>Fri, 23 Jan 2026 10:23:02 +0000</pubDate>
				<category><![CDATA[iOS]]></category>
		<category><![CDATA[学习笔记]]></category>
		<guid isPermaLink="false">https://blog.z6z8.cn/?p=1527</guid>

					<description><![CDATA[一个因为swift name mangled符号不一致导致的错误： dispatch thunk of Ala [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>一个因为swift name mangled符号不一致导致的错误：</p>
<p>dispatch thunk of Alamofire.MultipartFormData.append(_: Foundation.Data, withName: Swift.String, fileName: Swift.String?, mimeType: Swift.String?) -&gt; ()&quot;, referenced from:</p>
<p>先说工程结构<br />
工程A有一个静态framework target a，通过cocoapods以源码方式集成了封装Alamofire，封装Alamofire的一些能力。</p>
<pre><code> workspace A
              |____ a (源码)
              |____Alamofire （源码）</code></pre>
<p>构建输出静态framework a。</p>
<p>工程B有一个App Target，通过cocoapods以源码方式集成封装Alamofire，以静态库方式集成了 a。</p>
<pre><code> workspace B
             ｜___App
                      |____ a (静态库)
                      |____ Alamofire （源码）
 ```

 在同一电脑，用同一个Xcode，设置了相同的swift version，结果App构建失败，找不到Alamofire里的符号。

经过一番观察，发现 &quot;dispatch thunk of Alamofire.MultipartFormData.append(...)&quot;是 Swift 编译器为带有默认参数、可变签名或需要方法桥接的函数生成的“thunk”符号。也就是说静态库a引用了这些由 Alamofire 生成的编译器私有符号，但最终链接器没有在任何被链接的库/框架中找到对应实现。

 检查工程配置，首先排除Alamofire 的实现没有被链接到最终可执行文件（常见：静态库a引用但 App 没把 Alamofire 链接上）。

那么，最有可能的情况就是：工程B的Alamofire 与 工程A  使用不同的 Swift 编译环境/选项，导致 thunk 的名称/生成方式不一致，最终没有可匹配的符号。虽然Alamofire代码一摸一样，但是编译后的符号修饰方式不一样。
</code></pre>
<p>示意例子，例如同样是方法 test，<br />
工程A中被修饰为 _$s12Alamofire4test<em>tKF<br />
工程B中被修饰为 </em>$s12Alamofire4test_tKj</p>
<pre><code>

有了排查方向，经过对比工程A和工程B的配置，最终发现cocoapods自动生成target Alamofire的配置有些许不一致 

Build Libraries for Distribution 这个配置不一样，一个是YES，一个是NO。
又经实验，发现如果依赖Alamofire的target的 Build Libraries for Distribution 配置为YES，那么cocoapods自动生成的Alamofire对应的配置也是YES。

最后，只要将Alamofire对应Build Libraries for Distribution设置为NO（保持一致）就解决了Undefined symbols问题。</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.z6z8.cn/2026/01/23/undefined-symbols-dispatch-thunk/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>TLS证书签发流程</title>
		<link>https://blog.z6z8.cn/2025/12/10/tls%e8%af%81%e4%b9%a6%e7%ad%be%e5%8f%91%e6%b5%81%e7%a8%8b/</link>
					<comments>https://blog.z6z8.cn/2025/12/10/tls%e8%af%81%e4%b9%a6%e7%ad%be%e5%8f%91%e6%b5%81%e7%a8%8b/#respond</comments>
		
		<dc:creator><![CDATA[holdsky]]></dc:creator>
		<pubDate>Wed, 10 Dec 2025 02:26:48 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[TLS]]></category>
		<category><![CDATA[证书]]></category>
		<guid isPermaLink="false">https://blog.z6z8.cn/?p=1524</guid>

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

# 2. 切换到Let’s Encrypt（默认就是）
acme.sh --set-default-ca --server letsencrypt

# 3. 签发证书（两种主流验证方式任选其一）

# 方法A：HTTP验证（需要80端口开放）
acme.sh --issue -d example.com -d www.example.com --webroot /var/www/html

# 方法B：DNS验证（推荐，停机也可续期）
acme.sh --issue -d example.com --dns dns_cf   # 以Cloudflare为例，会自动调用API添加TXT记录

# 4. 安装证书到Nginx/Apache
acme.sh --install-cert -d example.com \
--key-file /etc/nginx/ssl/example.key \
--fullchain-file /etc/nginx/ssl/example.cer \
--reloadcmd &quot;nginx -s reload&quot;</code></pre>
<p>整个过程几秒到几十秒完成，无需人工干预，每60天自动续期。</p>
<h1>证书签发后链路示意</h1>
<pre><code>用户浏览器 ←HTTPS→ 你的服务器（证书+私钥）
           ↑
        验证链：
你的证书 → CA中间证书 → 根证书（已预装在操作系统/浏览器中）</code></pre>
<h1>总结对比表</h1>
<table>
<thead>
<tr>
<th>类型</th>
<th>验证内容</th>
<th>签发时间</th>
<th>有效期</th>
<th>价格</th>
<th>典型用途</th>
</tr>
</thead>
<tbody>
<tr>
<td>DV证书</td>
<td>仅域名控制权</td>
<td>几秒~几分钟</td>
<td>90天~1年</td>
<td>免费~几百元</td>
<td>普通网站、博客</td>
</tr>
<tr>
<td>OV证书</td>
<td>域名+组织合法性</td>
<td>1~5个工作日</td>
<td>1~2年</td>
<td>几百~几千元</td>
<td>企业官网、电商</td>
</tr>
<tr>
<td>EV证书</td>
<td>域名+严格组织验证</td>
<td>5~10个工作日</td>
<td>1~2年</td>
<td>千元以上</td>
<td>银行、金融、大型电商</td>
</tr>
<tr>
<td>Let’s Encrypt</td>
<td>域名控制权（DV）</td>
<td>几秒~1分钟</td>
<td>90天</td>
<td>完全免费</td>
<td>绝大多数互联网网站</td>
</tr>
</tbody>
</table>
<h1>附录：CA链证书</h1>
<p>简单来说：<strong>CA链证书就是“中间证书”</strong>，它是连接你的网站证书和浏览器信任的根证书之间的“桥梁”。如果没有正确安装链证书，用户访问你的HTTPS网站时就会报错（最常见的是“NET::ERR_CERT_AUTHORITY_INVALID”或“证书不受信任”）。</p>
<h2>为什么需要链证书？（信任链原理）</h2>
<p>为什么公信CA（包括Let’s Encrypt、DigiCert、GlobalSign等）<strong>坚决不直接用根证书私钥（Root Private Key）去签发终端用户证书</strong>？原因非常明确，一共就这几条，但每一条都是致命的：</p>
<table>
<thead>
<tr>
<th>理由</th>
<th>如果直接用根私钥签发终端证书，后果会怎样？</th>
<th>真实案例（已经发生过）</th>
</tr>
</thead>
<tbody>
<tr>
<td>1. 根私钥一旦泄露，整个生态瞬间崩塌</td>
<td>攻击者可以随意给自己或任何人签发<strong>看起来100%合法</strong>的证书，伪造全球任意网站（银行、Google、苹果等），MITM攻击无法被发现。全球数亿网站同时不安全。</td>
<td>2011年 DigiNotar（荷兰CA）根私钥被伊朗黑客盗取 → 伪造了Google、Yahoo、WordPress等500+证书 → 整个CA直接破产，被所有浏览器永久拉黑。<br />2015年 Symantec不当签发测试证书 → 根信任被Chrome逐步移除。</td>
</tr>
<tr>
<td>2. 根私钥必须永久离线，日常无法使用</td>
<td>根私钥通常保存在<strong>物理保险柜 + HSM硬件安全模块 + 多方仪式</strong>中，启动一次需要多位高管同时到场、插多把实体钥匙。根本不可能每天拿来签发几十万张证书。</td>
<td>Let’s Encrypt的根ISRG Root X1从2015年生成后，私钥就再也没有在联网设备上出现过。</td>
</tr>
<tr>
<td>3. 根证书撤销几乎不可能</td>
<td>如果根被污染，唯一办法是让所有浏览器和操作系统<strong>永久移除这个根</strong>，代价极其高昂（用户需要升级系统/浏览器）。而中间证书被污染，只需要撤销那一个中间证书就行，影响面小1000倍。</td>
<td>2024年有人发现某个小CA的中间证书被滥用，浏览器只撤销了那张中间证书，用户几乎无感知。</td>
</tr>
<tr>
<td>4. 可以灵活撤销和轮换</td>
<td>中间证书可以1~5年更换一次（Let’s Encrypt中间证书就换过好几次），根证书却要用20~30年。出了问题只换中间就行，不影响根的长期信任。</td>
<td>DigiCert、GlobalSign每年都会轮换若干中间证书，就是为了降低风险。</td>
</tr>
<tr>
<td>5. 符合国际标准和浏览器强制要求</td>
<td>Baseline Requirements（CA/Browser Forum规定）和所有主流浏览器（Chrome、Firefox、Safari、Edge）<strong>明确禁止</strong>根私钥直接签发终端实体证书，违反者直接除根。</td>
<td>任何被发现直接用根签终端证书的CA，会在几天内被所有浏览器除根（等于商业死亡）。</td>
</tr>
</tbody>
</table>
<p>所以现代公信CA为了安全，<strong>绝不直接用根证书私钥去签发终端用户证书</strong>，而是采用分层结构：</p>
<pre><code>根证书（Root CA）
    └── 中间证书1（Intermediate CA）   ← CA用根私钥签发
            └── 中间证书2（Intermediate CA）   ← 用中间1签发
                    └── 你的网站证书（Leaf Certificate） ← 用中间2签发</code></pre>
<ul>
<li>根证书：预装在操作系统和浏览器里（DigiCert、GlobalSign、Let’s Encrypt的根叫ISRG Root X1等），数量很少，私钥离线保存在保险柜里，几乎永不使用。</li>
<li>中间证书：CA用根私钥签发的证书，中间证书的私钥才真正用来签发你的网站证书。</li>
<li>你的网站证书（叶子证书）：只包含你的域名和公钥。</li>
</ul>
<p>浏览器验证证书时会沿着这条链一层层往上验证，直到找到自己信任的根证书为止。</p>
<h2>实际拿到的证书文件通常有这几种</h2>
<ol>
<li>
<p><strong>你的网站证书</strong>（也叫leaf证书、end-entity证书）<br />
文件名通常是：<code>yourdomain.crt</code>、<code>yourdomain.pem</code>、<code>certificate.crt</code></p>
</li>
<li>
<p><strong>链证书 / 中间证书（最重要的就是这个）</strong><br />
可能有1~3个中间证书串在一起，常见文件名：</p>
<ul>
<li><code>chain.crt</code>、<code>ca-bundle.crt</code>、<code>intermediate.crt</code>、<code>fullchain.pem</code></li>
</ul>
</li>
<li>
<p><strong>根证书</strong>（一般不需要你提供）<br />
浏览器自己有，不需要放在服务器上。</p>
</li>
</ol>
<h2>如何撤销中间证书</h2>
<p>中间证书（Intermediate CA）一旦被发现有安全问题、被滥用、私钥泄露、CA违规等，必须立即撤销。  </p>
<h3>谁有权力、谁来执行撤销？</h3>
<table>
<thead>
<tr>
<th>角色</th>
<th>职责</th>
</tr>
</thead>
<tbody>
<tr>
<td>发行该中间证书的根CA</td>
<td>决定是否撤销、生成CRL或OCSP</td>
</tr>
<tr>
<td>浏览器/操作系统厂商</td>
<td>Chrome、Firefox、Microsoft、Apple、Mozilla 决定是否接受并强制执行</td>
</tr>
<tr>
<td>CA/Browser Forum</td>
<td>制定最晚撤销期限（通常5~7天）</td>
</tr>
</tbody>
</table>
<h3>实际撤销的技术方式（都必须同时做）</h3>
<ol>
<li>
<p><strong>CRL（Certificate Revocation List，证书吊销列表）</strong>  </p>
<ul>
<li>根CA或上一级中间CA用自己的私钥签名一个列表，里面列出被撤销的证书序列号  </li>
<li>文件通常放在 <a href="http://crl.exampleca.com/xxx.crl">http://crl.exampleca.com/xxx.crl</a>  </li>
<li>缺点：体积大，浏览器很少真正下载检查（基本被淘汰）</li>
</ul>
</li>
<li>
<p><strong>OCSP（Online Certificate Status Protocol，在线证书状态协议）</strong>  </p>
<ul>
<li>实时查询：浏览器问 OCSP Responder：“序列号为12345的证书还好吗？”  </li>
<li>回应：Good / Revoked / Unknown  </li>
<li>现在是主流，所有正规CA都必须提供OCSP服务</li>
</ul>
</li>
<li>
<p><strong>OCSP Must-Staple（强制 stapling）</strong>  </p>
<ul>
<li>证书签发时就加上一个扩展，要求服务器必须在TLS握手中附带OCSP响应  </li>
<li>Let’s Encrypt、DigiCert 等主流CA默认都开启  </li>
<li>一旦中间证书被撤销，所有使用该中间证书签发的网站都会立刻变红锁（几小时内）</li>
</ul>
</li>
</ol>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.z6z8.cn/2025/12/10/tls%e8%af%81%e4%b9%a6%e7%ad%be%e5%8f%91%e6%b5%81%e7%a8%8b/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>后端登陆模块常见的校验策略</title>
		<link>https://blog.z6z8.cn/2025/12/08/%e5%90%8e%e7%ab%af%e7%99%bb%e9%99%86%e6%a8%a1%e5%9d%97%e5%b8%b8%e8%a7%81%e7%9a%84%e6%a0%a1%e9%aa%8c%e7%ad%96%e7%95%a5/</link>
					<comments>https://blog.z6z8.cn/2025/12/08/%e5%90%8e%e7%ab%af%e7%99%bb%e9%99%86%e6%a8%a1%e5%9d%97%e5%b8%b8%e8%a7%81%e7%9a%84%e6%a0%a1%e9%aa%8c%e7%ad%96%e7%95%a5/#respond</comments>
		
		<dc:creator><![CDATA[holdsky]]></dc:creator>
		<pubDate>Mon, 08 Dec 2025 00:19:59 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[登陆]]></category>
		<guid isPermaLink="false">https://blog.z6z8.cn/?p=1520</guid>

					<description><![CDATA[后端登录模块常见的校验策略（以安全为首要目标），从输入校验到风控防御，通常会分多层实现，以下是生产环境中广泛采 [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>后端登录模块常见的校验策略（以安全为首要目标），从输入校验到风控防御，通常会分多层实现，以下是生产环境中广泛采用的实战校验策略汇总：</p>
<h3>1. 基础参数校验（最基本的一道防线）</h3>
<ul>
<li>用户名/手机号/邮箱：非空、格式正则、长度限制、防注入（预编译语句或ORM自动处理）</li>
<li>密码：非空、长度（通常8-32位）、不能包含用户名等</li>
<li>验证码（图片/短信/邮箱）：非空、长度固定、是否过期（通常60-300秒）</li>
<li>请求必传字段完整性（deviceId、timestamp、sign等）</li>
</ul>
<h3>2. 账号状态校验</h3>
<ul>
<li>账号是否存在</li>
<li>账号是否被禁用/冻结/注销</li>
<li>账号是否需要强制修改密码（首次登录、密码过期）</li>
<li>账号是否被加入黑名单</li>
</ul>
<h3>3. 密码相关安全校验</h3>
<ul>
<li>密码明文传输 → 必须强制HTTPS</li>
<li>密码加密传输（前端通常用RSA或SM2加密，后端私钥解密）</li>
<li>后端密码存储：bcrypt、scrypt、Argon2（加盐，绝不用MD5/SHA1明盐）</li>
<li>密码输错次数限制（单账号5-10次后锁定30分钟~永久，需要人工解封）</li>
<li>密码复杂度策略（可选：大小写+数字+特殊字符）</li>
</ul>
<h3>4. 验证码多重校验（防止暴力破解）</h3>
<ul>
<li>图片验证码：防自动化脚本</li>
<li>短信/邮箱验证码：限流（同一手机号/IP/设备1分钟1条，1小时5条，24小时10条）</li>
<li>验证码有效期 + 只能使用一次</li>
<li>验证码输错次数限制（3-5次后需要重新获取）</li>
</ul>
<h3>5. 登录风控与异常检测（核心防爆破、防薅羊毛）</h3>
<ul>
<li>同一账号短时间内多次登录失败次数超限 → 锁定</li>
<li>同一IP短时间内大量不同账号登录失败 → IP封禁</li>
<li>同一设备（deviceId/deviceFingerprint）短时大量账号尝试 → 设备封禁</li>
<li>异地登录检测（根据IP归属地，与历史登录地对比，差距&gt;1000km触发二次验证）</li>
<li>短时间内大规模登录请求 → 触发全局限流或人工介入</li>
<li>用户代理（User-Agent）异常或频繁切换</li>
<li>无头浏览器、模拟器、爬虫特征检测</li>
</ul>
<h3>6. 会话安全策略</h3>
<ul>
<li>登录成功后颁发token（推荐无状态JWT，或服务端Session+Redis）</li>
<li>JWT需设置合理过期时间（accessToken 15min ~ 2h，refreshToken 7 ~ 30天）</li>
<li>支持单点登录（同账号新登录踢掉旧设备）</li>
<li>支持多端互踢配置（Web端登录踢掉App等）</li>
<li>敏感操作二次验证（修改密码、支付等需重新输入密码或验证码）</li>
</ul>
<h3>7. 常见辅助安全措施</h3>
<ul>
<li>登录接口全局限流（令牌桶/漏桶算法，如QPS&gt;500触发降级）</li>
<li>关键日志记录安全审计日志（登录IP、设备、时间、结果等）</li>
<li>登录成功后下发设备绑定关系（可用于后续风控）</li>
<li>支持可信设备管理（记住设备30天免二次验证）</li>
<li>密码传输前端可使用RSA/SM2非对称加密，或使用SRP协议（密码永不离客户端）</li>
</ul>
<h3>8. 特殊场景校验</h3>
<ul>
<li>第三方登录（微信、QQ、Apple）：校验state防CSRF，校验code是否已被使用</li>
<li>无密码登录（短信登录）：必须绑定手机号 + 短信验证码 + 设备风控</li>
<li>扫码登录：防钓鱼站点、心跳保活、状态机严格校验</li>
</ul>
<h3>推荐的实战分层校验伪代码结构（Java/SpringBoot示例）</h3>
<pre><code class="language-java">@PostMapping(&quot;/login&quot;)
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);
}</code></pre>
<p>总结：现代后端登录模块已远不止“用户名+密码正确就放行”，而是一整套“参数→验证码→风控→账号状态→密码→二次验证→会话管理→日志”的立体防御体系。缺任何一层都可能被拖库、撞库、短信轰炸、羊毛党攻破。</p>
<p>如果你的项目体量较大，强烈建议引入专业的风控系统（如阿里云滑块验证码、网易易盾、同盾、ThreatBook等）或自研设备指纹+行为分析能力。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.z6z8.cn/2025/12/08/%e5%90%8e%e7%ab%af%e7%99%bb%e9%99%86%e6%a8%a1%e5%9d%97%e5%b8%b8%e8%a7%81%e7%9a%84%e6%a0%a1%e9%aa%8c%e7%ad%96%e7%95%a5/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>在hugo里集成mermaid图支持</title>
		<link>https://blog.z6z8.cn/2025/12/01/%e5%9c%a8hugo%e9%87%8c%e9%9b%86%e6%88%90mermaid%e5%9b%be%e6%94%af%e6%8c%81/</link>
					<comments>https://blog.z6z8.cn/2025/12/01/%e5%9c%a8hugo%e9%87%8c%e9%9b%86%e6%88%90mermaid%e5%9b%be%e6%94%af%e6%8c%81/#respond</comments>
		
		<dc:creator><![CDATA[holdsky]]></dc:creator>
		<pubDate>Mon, 01 Dec 2025 09:42:07 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[hugo]]></category>
		<category><![CDATA[mermaid]]></category>
		<guid isPermaLink="false">https://blog.z6z8.cn/?p=1514</guid>

					<description><![CDATA[本文环境： hugo v0.152.2+extended+withdeploy darwin/amd64 Bu [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>本文环境：<br />
hugo v0.152.2+extended+withdeploy darwin/amd64 BuildDate=2025-10-24T15:31:49Z VendorInfo=Homebrew</p>
<p>一般使用了hugo主题的话，默认是集成了mermaid图。<br />
若不使用hugo主题，那么就需要自己手动集成了。<br />
步骤如下：</p>
<h2>创建 layouts/_markup/render-codeblock-mermaid.html</h2>
<p>负责开启一个mermaid图的html区块，内容如下：</p>
<pre><code class="language-html">&lt;pre class=&quot;mermaid&quot;&gt;
    {{- .Inner | safeHTML }}
  &lt;/pre&gt;
  {{ .Page.Store.Set &quot;hasMermaid&quot; true }}</code></pre>
<h2>修改 layouts/_default/baseof.html</h2>
<p>如果没有该文件就创建一个。默认情况下，所有html页面的主体结构和内容由这个文件渲染。和mermaid图相关的内容如下:</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;zh-CN&quot;&gt;

&lt;body&gt;
   ......
   ......
   ......

   &lt;!-- 必须放到最后在 （.Content 之下），因为这渲染钩子直到执行 .Content 之后才会被处理），否则.Page.Store.Get &quot;hasMermaid&quot;永远为false --&gt;
       {{ if .Page.Store.Get &quot;hasMermaid&quot; }}
    &lt;!-- 只有文章里写了 mermaid 才加载下面这些资源，节省性能 --&gt;
        &lt;script type=&quot;module&quot;&gt;
            import mermaid from &#039;https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs&#039;;
            mermaid.initialize({ startOnLoad: true });
        &lt;/script&gt;
    {{ end }}
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h2>添加一个mermaid图看看效果</h2>
<p>在网站的任意一个markdown文件里添加样例，看下效果</p>
<pre><code>```mermaid
flowchart LR
%% 左右布局（TD=上下，LR=左右，TB=上下，BT=下上）
    Start[开始] --&gt; Stop[结束]
    A[矩形节点] --&gt; B(圆角矩形)
    C{菱形判断} --&gt;|是| D[操作1]
    C --&gt;|否| E[操作2]</code></pre>
<pre><code></code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.z6z8.cn/2025/12/01/%e5%9c%a8hugo%e9%87%8c%e9%9b%86%e6%88%90mermaid%e5%9b%be%e6%94%af%e6%8c%81/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>TLS 1.3前向安全的通俗解释</title>
		<link>https://blog.z6z8.cn/2025/11/26/tls-1-3%e5%89%8d%e5%90%91%e5%ae%89%e5%85%a8%e7%9a%84%e9%80%9a%e4%bf%97%e8%a7%a3%e9%87%8a/</link>
					<comments>https://blog.z6z8.cn/2025/11/26/tls-1-3%e5%89%8d%e5%90%91%e5%ae%89%e5%85%a8%e7%9a%84%e9%80%9a%e4%bf%97%e8%a7%a3%e9%87%8a/#respond</comments>
		
		<dc:creator><![CDATA[holdsky]]></dc:creator>
		<pubDate>Wed, 26 Nov 2025 10:12:02 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[TLS]]></category>
		<guid isPermaLink="false">https://blog.z6z8.cn/?p=1509</guid>

					<description><![CDATA[前向安全 前向安全（Forward Secrecy，简称 PFS）是 TLS（以及其他加密协议）中一个非常重要 [&#8230;]]]></description>
										<content:encoded><![CDATA[<h1>前向安全</h1>
<p>前向安全（Forward Secrecy，简称 PFS）是 TLS（以及其他加密协议）中一个非常重要的安全属性，简单来说：<br />
“即使攻击者将来拿到了服务器的长期私钥（即证书私钥），也无法解密之前已经完成的所有 TLS 会话”<br />
这就是前向安全的核心意义：过去的通信记录永远是安全的，即使私钥彻底泄露。</p>
<p>前向安全在TLS 1.3中强制开启的，安全性由<strong>椭圆曲线离散对数问题（ECDLP）</strong>保障</p>
<h1>ECDH（椭圆曲线 Diffie-Hellman）最核心的数学原理</h1>
<p>一句话就能说清楚：<strong>“乘法可以随便做，除法几乎做不出来”</strong>  </p>
<p>我们用最常用、最直观的曲线 <strong>X25519 / Curve25519</strong> 来解释（TLS 1.3 里 90% 以上用的就是它）。</p>
<h4>1. 公共参数（全世界都一样）</h4>
<ul>
<li>选定一个安全的椭圆曲线：y² = x³ + 486662x² + x（模 2²⁵⁵-19）</li>
<li>选定一个基点（base point）G，一个固定的点，坐标是已知的：<br />
G = (9, 447…)（具体数字不重要，只要大家用同一个就行）</li>
</ul>
<h4>2. 双方各自生成临时私钥（就是随机数）</h4>
<ul>
<li>爱丽丝（Alice，客户端）随机选一个 256 bit 的整数作为私钥：<br />
a = 随机数（比如 3f1a…c2，绝对保密！）</li>
<li>鲍勃（Bob，服务器）也随机选一个 256 bit 的整数：<br />
b = 随机数（比如 7d2b…e9，绝对保密！）</li>
</ul>
<h4>3. 计算并公开“公钥”（其实就是把私钥乘以基点 G）</h4>
<ul>
<li>爱丽丝算出自己的临时公钥：<br />
A = a × G</li>
<li>鲍勃算出自己的临时公钥：<br />
B = b × G</li>
</ul>
<p>这一步叫<strong>标量乘法</strong>（scalar multiplication），很快就能算。</p>
<h4>4. 双方互换公钥（在网络上明文发送，没关系！）</h4>
<p>爱丽丝把 A 发给鲍勃<br />
鲍勃把 B 发给爱丽丝</p>
<h4>5. 双方各自再做一次标量乘法，得到共享秘密 Z</h4>
<ul>
<li>爱丽丝算：Z = a × B  </li>
<li>鲍勃算：  Z = b × A</li>
</ul>
<p>奇迹发生了：<br />
a × B = a × (b × G) = (a × b) × G<br />
b × A = b × (a × G) = (b × a) × G  </p>
<p>因为乘法交换律，a×b = b×a，所以两个 Z <strong>完全相等</strong>！</p>
<p>这就是共享秘密 Z（32 字节），<strong>网络上从来没有传输过 Z</strong>，攻击者只看到 A 和 B。</p>
<h4>6. 为什么攻击者算不出 Z？（数学陷阱）</h4>
<p>攻击者看到：</p>
<ul>
<li>G（公开）</li>
<li>A = a × G（公开）</li>
<li>B = b × G（公开）</li>
</ul>
<p>他想求 Z = a × b × G<br />
→ 必须先从 A 求出 a，或者从 B 求出 b<br />
→ 这就是<strong>椭圆曲线离散对数问题（ECDLP）</strong></p>
<p>目前人类最好的算法要花大约 2¹²⁸ 次运算才能破解 X25519（相当于 300 亿亿亿年），完全不可能。</p>
<p>这就是“乘法容易，除法几乎做不出来”的本质。</p>
<h4>超级简化的数字比喻（虽然现实不是素数乘法，但帮助理解）</h4>
<p>把椭圆曲线上的“点加”想象成普通乘法：</p>
<ul>
<li>G = 5（基点）</li>
<li>爱丽丝私钥 a = 7</li>
<li>鲍勃私钥 b = 13</li>
</ul>
<p>爱丽丝发：A = 5⁷ = 78125<br />
鲍勃发：B = 5¹³ = 1220703125  </p>
<p>爱丽丝算：78125¹³ = 4038967834731580443708050254247865495926816947758197784423828125<br />
鲍勃算：1220703125⁷ = 4038967834731580443708050254247865495926816947758197784423828125  </p>
<p>攻击者看到 78125 和 1220703125，想求出 7 或 13 ，也不容易。<br />
椭圆曲线只是把“指数”换成了“点加”，但数学难度被做得更高。</p>
<h4>总结：ECDH 全过程一句话</h4>
<ol>
<li>大家约定一个起点 G  </li>
<li>各自用自己的私钥（随机数）把 G “乘”很多次得到公钥，公开交换  </li>
<li>再把对方的公钥乘上自己的私钥，得到同一个共享秘密 Z  </li>
<li>因为离散对数难题，谁也反推不出对方的私钥 → Z 永远安全</li>
</ol>
<p>这就是 TLS 1.3 里每一次握手都能实现完美前向安全的数学根基。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.z6z8.cn/2025/11/26/tls-1-3%e5%89%8d%e5%90%91%e5%ae%89%e5%85%a8%e7%9a%84%e9%80%9a%e4%bf%97%e8%a7%a3%e9%87%8a/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>using bridging headers with module interfaces is unsupported</title>
		<link>https://blog.z6z8.cn/2025/09/26/using-bridging-headers-with-module-interfaces-is-unsupported/</link>
					<comments>https://blog.z6z8.cn/2025/09/26/using-bridging-headers-with-module-interfaces-is-unsupported/#respond</comments>
		
		<dc:creator><![CDATA[holdsky]]></dc:creator>
		<pubDate>Fri, 26 Sep 2025 01:28:16 +0000</pubDate>
				<category><![CDATA[iOS]]></category>
		<category><![CDATA[学习笔记]]></category>
		<guid isPermaLink="false">https://blog.z6z8.cn/?p=1499</guid>

					<description><![CDATA[Xcode创建framework，选择语言Swift。在使用Swift语言编写framework时，如果要引入 [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Xcode创建framework，选择语言Swift。在使用Swift语言编写framework时，如果要引入Objective-C的代码或者其他Objective-C的库，可能会报错“using bridging headers with module interfaces is unsupported”。这通常是因为在模块接口中使用了桥接头文件(bridging-header)，而这是不被支持的，</p>
<p>一般有两种解决办法：</p>
<p>一、如果这个framework不需要对外发布，那么将Build Setting里的Build Library For Distrubution 设置为NO，则可以继续使用桥接头文件(bridging-header)。</p>
<p>二、使用module map文件替换桥接头文件(bridging-header)，具体操作如下：</p>
<p>1、在framework工程里创建一个头文件，如<code>Test.h</code><br />
在Build Setting里设置User Header Search Paths，如<code>$(SRCROOT)/$(PRODUCT_NAME)</code>。设置的路径需要能找到<code>Test.h</code>文件</p>
<p>2、在<code>Test.h</code>文件里引入需要引用的Objective-C头文件</p>
<pre><code>#import &lt;MyLib/MyLib.h&gt;</code></pre>
<p>3、创建一个module map文件,<code>module.modulemap</code>，内容如下</p>
<pre><code>module  TestModule (名字自定义)  {
    header &quot;Test.h&quot;
    export *
}</code></pre>
<p>4、在Swift文件里，使用import指令，然后就可以调用Objective-C的方法了</p>
<pre><code>import  TestModule</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.z6z8.cn/2025/09/26/using-bridging-headers-with-module-interfaces-is-unsupported/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>创建Objective-C可使用的Swift Framework</title>
		<link>https://blog.z6z8.cn/2025/09/24/%e5%88%9b%e5%bb%baobjective-c%e5%8f%af%e4%bd%bf%e7%94%a8%e7%9a%84swift-framework/</link>
					<comments>https://blog.z6z8.cn/2025/09/24/%e5%88%9b%e5%bb%baobjective-c%e5%8f%af%e4%bd%bf%e7%94%a8%e7%9a%84swift-framework/#respond</comments>
		
		<dc:creator><![CDATA[holdsky]]></dc:creator>
		<pubDate>Wed, 24 Sep 2025 06:53:58 +0000</pubDate>
				<category><![CDATA[iOS]]></category>
		<category><![CDATA[学习笔记]]></category>
		<guid isPermaLink="false">https://blog.z6z8.cn/?p=1497</guid>

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

MySwiftClass *swiftObject = [[MySwiftClass alloc] init];
[swiftObject doSomething];</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.z6z8.cn/2025/09/24/%e5%88%9b%e5%bb%baobjective-c%e5%8f%af%e4%bd%bf%e7%94%a8%e7%9a%84swift-framework/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>移动端环境，Kotlin Multiplatfrom 调用C/C++方法</title>
		<link>https://blog.z6z8.cn/2025/07/18/%e7%a7%bb%e5%8a%a8%e7%ab%af%e7%8e%af%e5%a2%83%ef%bc%8ckotlin-multiplatfrom-%e8%b0%83%e7%94%a8c-c%e6%96%b9%e6%b3%95/</link>
					<comments>https://blog.z6z8.cn/2025/07/18/%e7%a7%bb%e5%8a%a8%e7%ab%af%e7%8e%af%e5%a2%83%ef%bc%8ckotlin-multiplatfrom-%e8%b0%83%e7%94%a8c-c%e6%96%b9%e6%b3%95/#respond</comments>
		
		<dc:creator><![CDATA[holdsky]]></dc:creator>
		<pubDate>Fri, 18 Jul 2025 03:32:20 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<guid isPermaLink="false">https://blog.z6z8.cn/?p=1492</guid>

					<description><![CDATA[前言 本文成文环境： macOS 26 Xcode 26 以及 iOS SDK Android Studio  [&#8230;]]]></description>
										<content:encoded><![CDATA[<h1>前言</h1>
<p>本文成文环境：</p>
<ul>
<li>macOS 26</li>
<li>Xcode 26 以及 iOS SDK</li>
<li>Android Studio MeerKat Feature Drop 以及 Android SDK、NDK</li>
<li>JDK 17</li>
<li>Gradle 8.11.1</li>
</ul>
<p>Kotlin Multiplatfrom 通过Cinterop工具调用C/C++方法，也可以通过JNI规范调用C/C++方法。</p>
<p>本质上，都是调用C方法，C++方法需要通过<code>extern &quot;C&quot;</code>修饰为C方法。</p>
<p>本文将介绍Kotlin/Native在Android和iOS环境，如何调用C/C++方法。</p>
<h1>Android Studio</h1>
<p>推荐使用 Android Studio 开发Kotlin/Native （Kotlin更像是一款Android开发语言）</p>
<h1>Gradle</h1>
<p>Android Studio的御用构建工具（相比其构建工具，复杂且难用）</p>
<h1>CMake</h1>
<p>一个事实上的C/C++构建标准工具，在Android和iOS环境调用各自平台的工具链构建C/C++。</p>
<h1>创建 Kotlin Multiplatfrom 工程</h1>
<p>如果Android Studio没有 Kotlin Multiplatfrom 工程模版，则需要先安装 Kotlin Multiplatfrom插件。<br />
安装完成后，使用 Kotlin Multiplatfrom 工程模版创建一个工程（创建过程中，需要选择iOS的包管理方式，选择cocoapods）</p>
<h1>镜像</h1>
<p>因为环大陆的网络墙问题，可能需要配置下镜像仓库</p>
<p>1、找到工程根目录下的<code>settings.gradle.kts</code>,添加下镜像仓库（下面配置的是阿里云的maven仓库）</p>
<pre><code class="language-gradle">// settings.gradle.kts

pluginManagement {
    repositories {
        maven{ setUrl(&quot;https://maven.aliyun.com/repository/public&quot;) }
        maven{ setUrl(&quot;https://maven.aliyun.com/repository/central&quot;) }
        maven{ setUrl(&quot;https://maven.aliyun.com/repository/gradle-plugin&quot;) }
...
...
...
    }
}

dependencyResolutionManagement {
    repositories {
        maven{ setUrl(&quot;https://maven.aliyun.com/repository/public&quot;) }
        maven{ setUrl(&quot;https://maven.aliyun.com/repository/central&quot;) }
        maven{ setUrl(&quot;https://maven.aliyun.com/repository/gradle-plugin&quot;) }
...
...
...
    }
}
</code></pre>
<p>2、找到根目录下<code>gradle/wrapper/gradle-wrapper.properties</code>，替换为distributionUrl</p>
<pre><code class="language-ini">#gradle/wrapper/gradle-wrapper.properties

#distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-all.zip</code></pre>
<p>bin是二进制工具，all包含二进制和源码，gradle工具构建时可能会下载源码。</p>
<h1>修改shared模块</h1>
<p>默认情况下，创建的工程会有一个shared模块<br />
展开后，可以看到src目录下有androidMain、commonMain、iosMain</p>
<p>使用Android Studio在shared模块中添加C/C++（鼠标右键选中shared模块，Add C/C++ to Module），这样就添加了Android Native相关代码,目录如下:</p>
<pre><code>shared
        |_src
                |_commonMain (通用方法)
                    |_kotlin
                        |_com.example.kmpapptest(包名)
                            |_Platform.kt
                |_androidMain (android平台相关的方法)
                    |_kotlin
                        |_com.example.kmpapptest(包名)
                            |_Platform.android.kt
                |_iosMain (ios平台相关的方法)
                    |_kotlin
                        |_com.example.kmpapptest(包名)
                            |_Platform.ios.kt
                |_main (C/C++方法)
                    |_cpp
                        |_CMakeLists.txt (Android so构建配置)
                        |_main.cpp</code></pre>
<p>因为我们使用的Android Studio的<code>Add C/C++ to Module</code>菜单创建的C/C++目录，所以在shared模块的build.gradle.kts里会自动添加Android平台的JNI构建任务</p>
<pre><code class="language-gradle">android {
    ...
    defaultConfig {
        ...
        externalNativeBuild {
            cmake {
                cppFlags += &quot;&quot;
            }
        }
        ...
    }
    ...
    externalNativeBuild {
        cmake {
            path = file(&quot;src/main/cpp/CMakeLists.txt&quot;)
            version = &quot;3.22.1&quot;
        }
    }
    ...
}</code></pre>
<h2>expect/actual</h2>
<p>kotlin关键字 <code>expect</code>用来声明方法，<code>actul</code>标记实现方法。</p>
<p>我们在commonMain 中的<code>Platform.kt</code>声明(expect)一个 <code>get_native_message()</code>方法</p>
<pre><code class="language-kotlin">// shared/src/commonMain/kotlin/com/example/kmpapptest/Platform.kt

package com.example.kmpapptest

expect fun get_native_message(): string </code></pre>
<p>在androidMain和iOSmain各自实现(actual)这个<code>get_native_message</code>方法</p>
<pre><code class="language-kotlin">// shared/src/androidMain/kotlin/com/example/kmpapptest/Platform.kt

package com.example.kmpapptest

actual fun get_native_message(): String{
    return AndroidNativeLib.stringFromJNI()
}

class AndroidNativeLib {
    companion object {
        external fun stringFromJNI() : String
        init {
            System.loadLibrary(&quot;android-native-lib&quot;)
        }
    }
}</code></pre>
<pre><code class="language-kotlin">// shared/src/iosMain/kotlin/com/example/kmpapptest/Platform.kt

package com.example.kmpapptest
import kotlinx.cinterop.toKString

@kotlinx.cinterop.ExperimentalForeignApi
actual fun get_native_message(): String{
    return iosNativeLib.get_message()?.toKString().toString()
}</code></pre>
<h2>Kotlin通过JNI在Android环境调用C/C++方法</h2>
<p>再来回顾下 <code>AndroidNativeLib</code>的<code>stringFromJNI</code>方法</p>
<pre><code class="language-kotlin">
package com.example.kmpapptest

class AndroidNativeLib {
    companion object {
        external fun stringFromJNI() : String
        init {
            System.loadLibrary(&quot;android-native-lib&quot;)
        }
    }
}</code></pre>
<p>在C/C++侧，对应的方法名为<code>Java_com_example_kmpapptest_AndroidNativeLib_00024Companion_stringFromJNI</code></p>
<blockquote>
<p>kotlin方法名和C/C++方法的对应规则:<br />
1、Java_包名_类名_方法名<br />
2、Java_包名_方法名<br />
3、Java_包名_类名_00024Companion_方法名<br />
&quot;00024Companion&quot; 是 &quot;$Companion&quot; 的转译字符串</p>
</blockquote>
<p>在<code>shared//src/main/cpp/main.cpp</code>中添加对应方法</p>
<pre><code class="language-cpp">//shared/src/main/cpp/main.cpp

#include &lt;jni.h&gt;

extern &quot;C&quot; JNIEXPORT jstring JNICALL
Java_com_example_kmpapptest_NativeLib_00024Companion_stringFromJNI(JNIEnv * env , jobject thiz) {
    return env-&gt;NewStringUTF(&quot;test_kotlin_call_android_native&quot;);
}
</code></pre>
<p>修改<code>shared/src/main/cpp/CMakeLists.txt</code></p>
<pre><code class="language-txt">...
project(&quot;android-native-lib&quot;) # 这里的名字要和AndroidNativeLib类加载的lib库名对应
...</code></pre>
<p>到这里，可以运行 <code>androidApp</code>看下效果</p>
<h2>Kotlin通过Cinterop在iOS环境调用C/C++方法</h2>
<p>在<code>shared/main/</code>创建<code>cinterop/CMakeLists.txt</code>,<code>cinterop/ios_cmake.sh</code>，<code>cinterop/ios.cpp</code>,<code>cinterop/ios.h</code>，<code>cinterop/ios.def</code></p>
<pre><code>shared
        |_src
                |_main (C/C++方法)
                    |_cinterop
                        |_CMakeLists.txt (ios lib 构建配置)
                        |_ios_cmake.sh (cmake构建具体命令)
                        |_ios.h (方法声明)
                        |_ios.cpp (方法实现)
                        |_ios.def (暴露给kotlin的方法定义文件)</code></pre>
<h3>方法声明和实现</h3>
<pre><code class="language-cpp">//shared/src/main/cinterop/ios.h

#ifndef TEST_SHARED_IOS_H
#define TEST_SHARED_IOS_H

#if defined(__cplusplus)
extern &quot;C&quot; {
#endif

const char *get_message(void);

#if defined(__cplusplus)
}
#endif

#endif //TEST_SHARED_IOS_H</code></pre>
<pre><code class="language-cpp">//shared/src/main/cinterop/ios.cpp

#include &quot;ios.h&quot;

extern &quot;C&quot;  const char *get_message(void)
{
    return &quot;cinterop_test_kotlin_call_ios_native&quot;;
}
</code></pre>
<h3>CMakeLists.txt 和 ios_cmake.sh</h3>
<p>编辑<code>shared/src/main/cinterop/CMakeLists.txt</code></p>
<pre><code># shared/src/main/cinterop/CMakeLists.txt

# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)

# 编译模块名 产物为libnative-lib
project(&quot;native-lib&quot;)

# 设置系统环境
set(CMAKE_SYSTEM_NAME iOS)
# 设置cpu arch
set(CMAKE_OSX_ARCHITECTURES arm64 x86_64)

# 指定头文件目录
include_directories(${CMAKE_SOURCE_DIR})

# 收集所有 源文件
file(GLOB SOURCES
        ${CMAKE_SOURCE_DIR}/*.cpp
)

add_library(${CMAKE_PROJECT_NAME} STATIC
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        ${SOURCES})</code></pre>
<p>编辑<code>shared/src/main/cinterop/ios_cmake.sh</code></p>
<pre><code class="language-sh"># 产物输出目录 (外面传入参数)
build_output_dir=$1

# 根据CMakeLists.txt生产构建配置信息，如makefile
cmake . -B $build_output_dir

cd $build_output_dir
#执行构建
cmake --build .</code></pre>
<p>可以手动执行<code>ios_cmake.sh</code>看下效果，执行后记得清理输出物</p>
<pre><code class="language-sh">cd ios_cmake.sh目录
./ios_cmake.sh  ./buildout</code></pre>
<h3>.def文件</h3>
<p>暴露给kotlin的方法定义文件<br />
内容如下：</p>
<pre><code class="language-def"># shared/src/main/cinterop/ios.def

# 暴露给kotlin的包名
package = iosNativeLib

# 暴露给kotlin的头文件，kotlin侧可以看到里面的方法名
# 这里的路径是相对于.def文件的路径
headers = ios.h

# 构建过程中，链接库的搜索目录
# 这里的路径是相对于projectdir（gradle的这个设定是不是bug？）
# 也可以使用绝对路径，从工程管理角度不建议用绝对路径
libraryPaths = build/cmake

# 需要链接的库文件，这里需要链接上面的iOS nativle lib
# 查找libraryPaths目录下的库文件
staticLibraries = libnative-lib.a</code></pre>
<h3>修改Gradle脚本</h3>
<p>上面已经完成了ios native lib的构建，以及native lib需要暴露的方法配置，现需要使用Gradle脚本将这些工作串起来。</p>
<pre><code class="language-gradle">// shared/build.gradle.kts

fun org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget.addCInterop_sharedCC() {
    val main by compilations.getting // target name : main or test
    val iosNativeLib by main.cinterops.creating {
        // .def 定位文件
        defFile(&quot;src/main/cinterop/ios.def&quot;) 
        // kotlin侧需要搜索的头文件目录
        includeDirs(&quot;src/main/cinterop&quot;)
    }
}

kotlin {
    ...

    iosX64() {
        addCInterop_sharedCC()
    }
    iosArm64()  {
        addCInterop_sharedCC()
    }
    iosSimulatorArm64() {
        addCInterop_sharedCC()
    }
    ...
}

...
// 创建ios_buildCmake任务
val ios_buildCmake by tasks.registering(Exec::class) {
    val ios_cmakebuildDir = File(projectDir ,&quot;build/cmake&quot;)
    val ios_cmakeSourceDir = File(projectDir,&quot;src/main/cinterop&quot;)
    description = &quot;Build native C/C++ with CMake&quot;
    workingDir = ios_cmakeSourceDir
    commandLine = listOf(&quot;sh&quot;, &quot;ios_cmake.sh&quot; , ios_cmakebuildDir.absolutePath)
}
# 设置cinterop的ios构建任务依赖ios_buildCmake任务
tasks.matching { it.name.startsWith(&quot;cinterop&quot;) &amp;&amp; it.name.contains(&quot;Ios&quot;) }.all {
    dependsOn(ios_buildCmake)
}
</code></pre>
<p>至此，可以执行iOSApp看看效果</p>
<h2>总结</h2>
<p>在无法摆脱C/C++生态的情况下，使用Kotlin Multiplatfrom实现跨平台，维护成本比较高，不如直接使用C/C++。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.z6z8.cn/2025/07/18/%e7%a7%bb%e5%8a%a8%e7%ab%af%e7%8e%af%e5%a2%83%ef%bc%8ckotlin-multiplatfrom-%e8%b0%83%e7%94%a8c-c%e6%96%b9%e6%b3%95/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CocoaPods Repo更新经常失败</title>
		<link>https://blog.z6z8.cn/2024/11/01/cocoapods-repo%e6%9b%b4%e6%96%b0%e7%bb%8f%e5%b8%b8%e5%a4%b1%e8%b4%a5/</link>
					<comments>https://blog.z6z8.cn/2024/11/01/cocoapods-repo%e6%9b%b4%e6%96%b0%e7%bb%8f%e5%b8%b8%e5%a4%b1%e8%b4%a5/#respond</comments>
		
		<dc:creator><![CDATA[holdsky]]></dc:creator>
		<pubDate>Fri, 01 Nov 2024 08:16:07 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<guid isPermaLink="false">https://blog.z6z8.cn/?p=1480</guid>

					<description><![CDATA[先解决的墙的问题，建立高速且稳定的梯子，可以解决更新失败的问题。 还有一种原因是http协议导致的，切换到ss [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>先解决的墙的问题，建立高速且稳定的梯子，可以解决更新失败的问题。</p>
<p>还有一种原因是http协议导致的，切换到ssh协议成功率会更高。需要注意，使用ssh协议，需要有github账号，并配置好ssh 公钥信息。</p>
<p>使用ssh协议更新成功后，还要记得再将本地的cocoapods git仓库设置为http协议.</p>
<pre><code>cd ~/.cocoapods/repos/cocoapods

// ssh
git remote set-url origin https://github.com/CocoaPods/Specs.git

// http
git remote set-url origin git@github.com:CocoaPods/Specs.git</code></pre>
<p>最后，首次初始化cocoapods repo仓库，也可以使用ssh协议</p>
<pre><code>cd 一个临时文件夹
// 克隆到tmpRepo文件夹
git clone  git@github.com:CocoaPods/Specs.git tmpRepo

// 添加repo
pod repo add trunk /xxx/yyy/tmpRepo

// 修改远程地址
cd ~/.cocoapods/repos/cocoapods
git remote set-url origin https://github.com/CocoaPods/Specs.git</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.z6z8.cn/2024/11/01/cocoapods-repo%e6%9b%b4%e6%96%b0%e7%bb%8f%e5%b8%b8%e5%a4%b1%e8%b4%a5/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>解析mach-o文件的依赖库列表，修改加载顺序</title>
		<link>https://blog.z6z8.cn/2024/10/29/%e8%a7%a3%e6%9e%90mach-o%e6%96%87%e4%bb%b6%e7%9a%84%e4%be%9d%e8%b5%96%e5%ba%93%e5%88%97%e8%a1%a8%ef%bc%8c%e4%bf%ae%e6%94%b9%e5%8a%a0%e8%bd%bd%e9%a1%ba%e5%ba%8f/</link>
					<comments>https://blog.z6z8.cn/2024/10/29/%e8%a7%a3%e6%9e%90mach-o%e6%96%87%e4%bb%b6%e7%9a%84%e4%be%9d%e8%b5%96%e5%ba%93%e5%88%97%e8%a1%a8%ef%bc%8c%e4%bf%ae%e6%94%b9%e5%8a%a0%e8%bd%bd%e9%a1%ba%e5%ba%8f/#respond</comments>
		
		<dc:creator><![CDATA[holdsky]]></dc:creator>
		<pubDate>Tue, 29 Oct 2024 06:45:17 +0000</pubDate>
				<category><![CDATA[iOS]]></category>
		<category><![CDATA[学习笔记]]></category>
		<guid isPermaLink="false">https://blog.z6z8.cn/?p=1475</guid>

					<description><![CDATA[两个命令可以解决，otool和install_name_tool otool和install_name_too [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>两个命令可以解决，otool和install_name_tool</p>
<h1>otool和install_name_tool</h1>
<pre><code># 显示依赖库列表
otool -L  &lt;mach-o文件路径&gt;</code></pre>
<pre><code># 添加新的依赖库路径
install_name_tool  -add_rpath  &lt;rpath&gt;   &lt;mach-o文件路径&gt;

# 删除依赖库路径
install_name_tool -delete_rpath  &lt;rpath&gt;  &lt;mach-o文件路径&gt;

# 替换依赖库路径
install_name_tool -change  &lt;old_rpath&gt;  &lt;new_rpath&gt;   &lt;mach-o文件路径&gt;</code></pre>
<h1>yololib 和ios App</h1>
<p>对于iOS App，install_name_tool的-add_rpath、  -delete_rpath总是执行失败，需要借助yololib工具添加依赖</p>
<pre><code># 删除iOS App 的依赖
install_name_tool -change 需要删除的依赖   替换的空依赖 #变相实现删除

# 排序iOS App 的依赖
# 不断执行 install_name_tool -change  ，实现排序</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.z6z8.cn/2024/10/29/%e8%a7%a3%e6%9e%90mach-o%e6%96%87%e4%bb%b6%e7%9a%84%e4%be%9d%e8%b5%96%e5%ba%93%e5%88%97%e8%a1%a8%ef%bc%8c%e4%bf%ae%e6%94%b9%e5%8a%a0%e8%bd%bd%e9%a1%ba%e5%ba%8f/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
