diff --git a/.github/workflows/jekyll.yml b/.github/workflows/jekyll.yml deleted file mode 100644 index 2343f4c..0000000 --- a/.github/workflows/jekyll.yml +++ /dev/null @@ -1,68 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# Sample workflow for building and deploying a Jekyll site to GitHub Pages -name: Deploy Jekyll site to Pages - -on: - # Runs on pushes targeting the default branch - push: - branches: ["main"] - - workflow_dispatch: - - # 每天北京时间早上 8 点(UTC 0:00)自动运行一次,确保预发布的博文能准时上线 - schedule: - - cron: '0 0 * * *' - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - # Build job - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Setup Ruby - # https://github.com/ruby/setup-ruby/releases/ - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.4' # Not needed with a .ruby-version file - bundler-cache: true # runs 'bundle install' and caches installed gems automatically - cache-version: 0 # Increment this number if you need to re-download cached gems - - name: Setup Pages - id: pages - uses: actions/configure-pages@v6 - - name: Build with Jekyll - # Outputs to the './_site' directory by default - run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" - env: - JEKYLL_ENV: production - - name: Upload artifact - # Automatically uploads an artifact from the './_site' directory by default - uses: actions/upload-pages-artifact@v5 - - # Deployment job - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v5 diff --git a/.gitignore b/.gitignore index 33f6175..badbc02 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,2 @@ -.DS_Store _site .sass-cache -.jekyll-cache -.jekyll-metadata -vendor diff --git a/404.html b/404.html deleted file mode 100644 index 3a16ab5..0000000 --- a/404.html +++ /dev/null @@ -1,25 +0,0 @@ ---- -permalink: /404.html -layout: page ---- - - - -
Page not found :(
-The requested page could not be found.
- elements
- mermaid_blocks = fragment.css('pre > code.language-mermaid')
-
- if mermaid_blocks.any?
- mermaid_blocks.each do |code_element|
- pre_element = code_element.parent
-
- # Create the new div
- mermaid_div = Nokogiri::XML::Node.new('div', fragment)
- mermaid_div['class'] = 'mermaid'
- mermaid_div.content = code_element.content
-
- # Replace the pre element with the new div
- pre_element.replace(mermaid_div)
- end
-
- # Update the document output
- doc.output = fragment.to_html
- end
- end
-end
diff --git a/_posts/2015-02-12-welcome-to-jekyll.markdown b/_posts/2015-02-12-welcome-to-jekyll.markdown
new file mode 100644
index 0000000..a209bf1
--- /dev/null
+++ b/_posts/2015-02-12-welcome-to-jekyll.markdown
@@ -0,0 +1,25 @@
+---
+layout: post
+title: "Welcome to Jekyll!"
+date: 2015-02-12 13:46:40
+categories: jekyll update
+---
+You’ll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `jekyll serve`, which launches a web server and auto-regenerates your site when a file is updated.
+
+To add new posts, simply add a file in the `_posts` directory that follows the convention `YYYY-MM-DD-name-of-post.ext` and includes the necessary front matter. Take a look at the source for this post to get an idea about how it works.
+
+Jekyll also offers powerful support for code snippets:
+
+{% highlight ruby %}
+def print_hi(name)
+ puts "Hi, #{name}"
+end
+print_hi('Tom')
+#=> prints 'Hi, Tom' to STDOUT.
+{% endhighlight %}
+
+Check out the [Jekyll docs][jekyll] for more info on how to get the most out of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll’s dedicated Help repository][jekyll-help].
+
+[jekyll]: http://jekyllrb.com
+[jekyll-gh]: https://github.com/jekyll/jekyll
+[jekyll-help]: https://github.com/jekyll/jekyll-help
diff --git a/_posts/2025-12-23-android-webview-session-consistency.md b/_posts/2025-12-23-android-webview-session-consistency.md
deleted file mode 100644
index 29e1049..0000000
--- a/_posts/2025-12-23-android-webview-session-consistency.md
+++ /dev/null
@@ -1,99 +0,0 @@
----
-layout: post
-title: "Android WebView 拦截请求导致的会话不一致问题及解决方案"
-date: 2025-12-23 21:00:00 +0000
-categories: android webview
-tags: android webview okhttp cookie
----
-
----
-layout: post
-title: "Android WebView 拦截请求导致的会话不一致问题及解决方案"
-date: 2025-12-23 21:00:00 +0000
-categories: android webview
-tags: android webview okhttp cookie
----
-
-## 问题现象
-
-在 Android 开发中,常通过重写 WebView 的 `shouldInterceptRequest` 方法,使用自定义网络栈(如 OkHttp)来接管部分资源请求(例如仅拦截 GET 请求以使用 HTTPDNS 或统一缓存)。
-
-这种**部分拦截**的策略极易引发一个严重问题:**会话(Session)不一致**。
-用户在登录后(原生网络栈 POST 请求),后续页面依然处于未登录状态;或图形验证码校验始终不过。
-
-## 根本原因
-
-会话不一致的根本原因是:**原生 WebView 的 `CookieManager` 与第三方网络库(OkHttp)的 Cookie 存储相互隔离且未同步。**
-
-```
-用户登录流程的时序割裂:
-1. [GET] 加载登录页面 → 被拦截,走 OkHttp 请求
-2. [POST] 提交登录表单 → 未被拦截,走 WebView 原生请求,服务端返回 Set-Cookie
-3. [GET] 获取用户信息 → 被拦截,走 OkHttp 请求
-```
-
-- WebView 原生请求收到的 `Set-Cookie` 会保存在 `CookieManager` 中。
-- 后续拦截走 OkHttp 的请求,如果没有提取并携带 `CookieManager` 中的 Cookie,或者 OkHttp 响应的 `Set-Cookie` 没有反向同步给 `CookieManager`,两套网络栈使用的就是不同的 Session 标识。
-
-## 解决方案
-
-核心思路:**在拦截逻辑中,充当 Cookie 的双向同步桥梁。**
-
-> 注:`WebResourceRequest.requestHeaders` 在调用 `shouldInterceptRequest` 时,系统已经自动从 `CookieManager` 注水了最新的 Cookie,因此只需要关注**响应后**的 Cookie 回写。
-
-在拦截实现中增加响应后的 Cookie 同步逻辑:
-
-```kotlin
-private fun getResponseByOkHttp(request: WebResourceRequest?): WebResourceResponse? {
- request ?: return null
- val url = request.url.toString()
- val requestBuilder = okhttp3.Request.Builder()
- .url(url)
- .method(request.method, null)
-
- // 1. 携带 WebView 已有的 Cookie (系统已注入到 requestHeaders)
- request.requestHeaders?.forEach { (key, value) ->
- requestBuilder.addHeader(key, value)
- }
-
- val response = okhttpClient.newCall(requestBuilder.build()).execute()
-
- // 2. 关键:同步响应头中的 Set-Cookie 到 WebView的 CookieManager
- // 必须在判断状态码之前同步,以防 302 重定向丢失 Cookie
- syncCookiesToManager(url, response)
-
- if (response.code != 200) {
- response.close()
- return null
- }
-
- val body = response.body ?: return null
- // 构建并返回 WebResourceResponse...
-}
-
-/**
- * 将 OkHttp 响应中的 Set-Cookie 写入 CookieManager
- */
-private fun syncCookiesToManager(url: String, response: okhttp3.Response) {
- val cookies = response.headers("Set-Cookie")
- if (cookies.isNotEmpty()) {
- val cookieManager = android.webkit.CookieManager.getInstance()
- cookies.forEach {
- cookieManager.setCookie(url, it)
- }
- cookieManager.flush() // 确保立即持久化到本地,防止进程被杀导致丢失
- }
-}
-```
-
-## 扩展建议
-
-**1. 过滤第三方域名**
-并非所有请求都适合拦截。涉及第三方支付、授权的网页,建议通过白名单/黑名单机制过滤,让原生 WebView 自行处理,避免引发未知的安全或跨域 Cookie 异常。
-
-**2. HTTPDNS 附带效应**
-使用 HTTPDNS 替换域名为 IP 后发起请求,其真实的 `Host` 头需要手动补全,否则可能触发 CDN 阻断或服务端 Host 校验严格环境下的 400 错误。
-
-## 小结
-
-WebView 请求拦截是优化利器,但**拦截了请求,就必须完整接管并维护 HTTP 的状态机(Cookie/Session)**。构建稳定的双向 Cookie 同步机制,是此类架构落地的必修课。
diff --git a/_posts/2025-12-31-harmonyos-httpdns-integration.md b/_posts/2025-12-31-harmonyos-httpdns-integration.md
deleted file mode 100644
index 600304e..0000000
--- a/_posts/2025-12-31-harmonyos-httpdns-integration.md
+++ /dev/null
@@ -1,187 +0,0 @@
----
-layout: post
-title: "鸿蒙 Next 实战:集成阿里云 HTTPDNS 优化网络请求"
-date: 2025-12-31 10:00:00 +0800
-categories: harmonyos network
-tags: harmonyos httpdns network optimization
----
-
-在移动应用开发中,域名劫持和解析延迟是常见的网络问题。为了提升网络连接的稳定性和速度,引入 HTTPDNS 是一个非常有效的方案。本文将分享如何在鸿蒙 HarmonyOS Next 项目中集成阿里云 HTTPDNS(`@aliyun/httpdns`)并结合 Axios 实现自定义 DNS 解析。
-
-## 1. 引入依赖
-
-首先,我们需要在项目的 `oh-package.json5` 中引入阿里云 HTTPDNS 的 SDK 和 Axios。
-
-```json
-"dependencies": {
- "@aliyun/httpdns": "^1.0.1",
- "@ohos/axios": "^2.2.0"
-}
-```
-
-执行 `ohpm install` 安装依赖。
-
-## 2. 封装 HttpDnsManager
-
-为了方便管理和调用,我们封装一个单例类 `HttpDnsManager`。这个类主要负责 SDK 的初始化和提供 DNS 解析方法。
-
-创建 `HttpDnsManager.ets`:
-
-```typescript
-import { httpdns, IHttpDnsService, HttpDnsResult } from '@aliyun/httpdns';
-import { connection } from '@kit.NetworkKit';
-import common from '@ohos.app.ability.common';
-
-export class HttpDnsManager {
- private static instance: HttpDnsManager;
- private httpDnsService: IHttpDnsService | undefined;
-
- // 替换为你自己的阿里云 Account ID
- private static readonly ALIYUN_ACCOUNT_ID = "";
-
- private constructor() {
- }
-
- public static getInstance(): HttpDnsManager {
- if (!HttpDnsManager.instance) {
- HttpDnsManager.instance = new HttpDnsManager();
- }
- return HttpDnsManager.instance;
- }
-
- /**
- * 初始化 HTTPDNS 服务
- * @param context UIAbilityContext
- * @param accountId 阿里云 Account ID
- */
- public init(context: common.UIAbilityContext, accountId: string = HttpDnsManager.ALIYUN_ACCOUNT_ID) {
- // 1. 配置服务上下文
- httpdns.configService(accountId, {
- context: context
- });
-
- // 2. 异步获取服务实例
- httpdns.getService(accountId).then((service) => {
- this.httpDnsService = service;
- }).catch((err: Error) => {
- console.error("HttpDns init failed: " + JSON.stringify(err));
- });
- }
-
- /**
- * 自定义 DNS 解析方法 (供 Axios 调用)
- */
- public async lookup(hostname: string): Promise> {
- if (!this.httpDnsService) {
- // 服务未初始化,返回空让 Axios 走默认系统 DNS
- return [];
- }
-
- try {
- // 异步获取解析结果,内部包含了缓存策略:有缓存用缓存,无缓存请求网络
- let result: HttpDnsResult = await this.httpDnsService.getHttpDnsResultAsync(hostname);
-
- let netAddresses: Array = [];
-
- // 处理 IPv4 结果
- if (result.ipv4s && result.ipv4s.length > 0) {
- for (let ip of result.ipv4s) {
- netAddresses.push({
- address: ip,
- family: 1, // 1 代表 IPv4 (AF_INET)
- port: 0
- });
- }
- }
-
- // 处理 IPv6 结果
- if (result.ipv6s && result.ipv6s.length > 0) {
- for (let ip of result.ipv6s) {
- netAddresses.push({
- address: ip,
- family: 2, // 2 代表 IPv6 (AF_INET6)
- port: 0
- });
- }
- }
-
- return netAddresses;
- } catch (e) {
- console.error("HttpDns lookup failed: " + JSON.stringify(e));
- return [];
- }
- }
-}
-```
-
-## 3. 在 Axios 中应用
-
-Axios 自 `2.2.0` 版本起支持自定义 `dns.lookup` 配置。我们可以将 `HttpDnsManager` 的 `lookup` 方法注入到 Axios 中。
-
-### 全局配置
-
-如果你希望项目中所有的 Axios 请求都走 HTTPDNS:
-
-```typescript
-import axios from '@ohos/axios';
-import { HttpDnsManager } from './HttpDnsManager'; // 假设路径
-
-// 配置 Axios 默认的 DNS 解析
-axios.defaults.dns = {
- lookup: async (hostname: string) => {
- // 获取 HTTPDNS 解析结果
- return await HttpDnsManager.getInstance().lookup(hostname);
- }
-};
-```
-
-### 实例配置
-
-如果你只想为特定的请求实例开启 HTTPDNS:
-
-```typescript
-import axios from '@ohos/axios';
-import { HttpDnsManager } from './HttpDnsManager';
-
-const request = axios.create({
- baseURL: 'https://api.yourdomain.com',
- timeout: 10000,
- dns: {
- lookup: async (hostname: string) => {
- return await HttpDnsManager.getInstance().lookup(hostname);
- }
- }
-});
-```
-
-## 4. 全局初始化
-
-最后,别忘了在应用的入口 `EntryAbility` 中初始化 `HttpDnsManager`:
-
-```typescript
-import { HttpDnsManager } from '@jzdy/common'; // 你的模块路径
-
-export default class EntryAbility extends UIAbility {
- onCreate(want, launchParam) {
- // ...
- // 初始化 HTTPDNS
- HttpDnsManager.getInstance().init(this.context);
- // ...
- }
-}
-```
-
-## 总结
-
-通过以上方式,我们成功将阿里云 HTTPDNS 集成到了鸿蒙应用的 Axios 网络栈中。
-1. **HttpDnsManager**:负责与阿里云 SDK 交互,提供统一的解析接口。
-2. **Axios DNS Config**:利用 `config.dns.lookup` 钩子,拦截 DNS 解析过程,替换为 HTTPDNS 的结果。
-
-这种方案侵入性小,且充分利用了 Axios 的扩展能力,是鸿蒙网络优化的最佳实践之一。
-
-## 5. 参考资料
-
-* [阿里云 HTTPDNS 产品文档](https://help.aliyun.com/product/37963.html) - 获取 Account ID 及控制台配置
-* [OpenHarmony Axios 组件](https://ohpm.openharmony.cn/#/cn/detail/@ohos%2Faxios) - Axios for HarmonyOS
-* [阿里云 HTTPDNS HarmonyOS SDK (OHPM)](https://ohpm.openharmony.cn/#/cn/detail/@aliyun%2Fhttpdns) - SDK 下载及更新日志
-* [自定义 DNS 解析配置参考](https://gitee.com/openharmony-sig/ohos_axios#%E8%87%AA%E5%AE%9A%E4%B9%89dns) - Axios 自定义 DNS 文档
diff --git a/_posts/2026-01-04-flutter-plugin-httpdns-harmonyos.md b/_posts/2026-01-04-flutter-plugin-httpdns-harmonyos.md
deleted file mode 100644
index 638a27d..0000000
--- a/_posts/2026-01-04-flutter-plugin-httpdns-harmonyos.md
+++ /dev/null
@@ -1,229 +0,0 @@
----
-layout: post
-title: "Flutter 插件适配鸿蒙:阿里云 HTTPDNS SDK 集成实践"
-date: 2026-01-04 11:00:00 +0800
-categories: flutter harmonyos
-tags: flutter harmonyos httpdns aliyun plugin
----
-
-随着 HarmonyOS NEXT 的发展,越来越多的 Flutter 应用需要适配鸿蒙平台。本文记录了将阿里云 HTTPDNS Flutter 插件 (`aliyun_httpdns`) 适配到 HarmonyOS 的完整过程,包括遇到的问题和解决方案。
-
-## 背景
-
-阿里云 HTTPDNS 是一款基于 HTTP 协议的域名解析服务,能够有效防止 DNS 劫持、提升解析速度。官方已提供 Android、iOS 和 HarmonyOS 原生 SDK,但 Flutter 插件尚未支持 HarmonyOS 平台。
-
-## 技术架构
-
-```
-┌───────────────────────────────────────────────────┐
-│ Flutter App │
-├───────────────────────────────────────────────────┤
-│ aliyun_httpdns (Dart) │
-│ MethodChannel │
-├───────────────┬───────────────┬───────────────────┤
-│ Android │ iOS │ HarmonyOS │
-│ Kotlin │ Swift │ ArkTS │
-├───────────────┼───────────────┼───────────────────┤
-│ Aliyun SDK │ Aliyun SDK │ @aliyun/httpdns │
-└───────────────┴───────────────┴───────────────────┘
-```
-
-## 实现步骤
-
-### 1. 配置 pubspec.yaml
-
-在插件的 `pubspec.yaml` 中添加 HarmonyOS 平台支持:
-
-```yaml
-flutter:
- plugin:
- platforms:
- android:
- package: com.aliyun.ams.httpdns
- pluginClass: AliyunHttpDnsPlugin
- ios:
- pluginClass: AliyunHttpDnsPlugin
- ohos:
- package: com.aliyun.ams.httpdns
- pluginClass: AliyunHttpDnsPlugin
-```
-
-### 2. 创建 ohos 目录结构
-
-```
-ohos/
-├── src/main/
-│ ├── ets/components/plugin/
-│ │ └── AliyunHttpDnsPlugin.ets
-│ └── module.json5
-├── oh-package.json5
-├── build-profile.json5
-├── hvigorfile.ts
-└── index.ets
-```
-
-### 3. 配置依赖 (oh-package.json5)
-
-```json5
-{
- "name": "aliyun_httpdns",
- "version": "1.0.0",
- "description": "Aliyun HTTPDNS Flutter plugin for HarmonyOS",
- "main": "index.ets",
- "author": "",
- "license": "Apache-2.0",
- "dependencies": {
- "@aliyun/httpdns": "^1.2.2"
- }
-}
-```
-
-### 4. 实现插件核心代码
-
-```typescript
-import { FlutterPlugin, FlutterPluginBinding } from '@ohos/flutter_ohos/...';
-import { httpdns, HttpDnsConfig, IHttpDnsService, IpType } from '@aliyun/httpdns';
-
-export default class AliyunHttpDnsPlugin implements FlutterPlugin, MethodCallHandler {
- private httpDnsService: IHttpDnsService | null = null;
-
- private async initialize(args: ESObject, result: MethodResult): Promise {
- // 解析参数
- let accountId: string | undefined;
- if (args instanceof Map) {
- if (args.has('accountId')) accountId = args.get('accountId') as string;
- } else {
- if (args['accountId']) accountId = args['accountId'] as string;
- }
-
- // 配置服务
- const config: HttpDnsConfig = { useHttps: true };
- httpdns.configService(accountId, config);
-
- // 获取服务实例
- const service = await httpdns.getService(accountId);
- this.httpDnsService = service;
- result.success(true);
- }
-
- private async resolveHost(args: ESObject, result: MethodResult): Promise {
- const hostname = args['hostname'] as string;
- const dnsResult = await this.httpDnsService.getHttpDnsResultAsync(hostname, IpType.Both);
- result.success({
- "ipv4": dnsResult?.ipv4s || [],
- "ipv6": dnsResult?.ipv6s || []
- });
- }
-}
-```
-
-## 遇到的问题与解决方案
-
-### 问题 1:MissingPluginException
-
-**现象**:调用插件方法时抛出 `MissingPluginException`
-
-**原因**:HarmonyOS Flutter 插件未自动注册
-
-**解决方案**:在 Example 应用的 `GeneratedPluginRegistrant.ets` 中手动注册:
-
-```typescript
-import AliyunHttpDnsPlugin from 'aliyun_httpdns';
-
-export default class GeneratedPluginRegistrant {
- static registerWith(bindingBase: FlutterPluginBinding) {
- bindingBase.getFlutterEngine().getPlugins().add(new AliyunHttpDnsPlugin());
- }
-}
-```
-
-### 问题 2:ArkTS 类型检查错误
-
-**现象**:`Record` 类型报错
-
-**原因**:ArkTS 严格模式不允许 `any` 类型
-
-**解决方案**:使用 `ESObject` 替代 `any`,并使用 `instanceof Map` 检测参数类型:
-
-```typescript
-private async initialize(args: ESObject, result: MethodResult): Promise {
- if (args instanceof Map) {
- // Map 类型处理
- } else {
- // Object 类型处理
- }
-}
-```
-
-### 问题 3:字节码 HAR 兼容性问题
-
-**现象**:编译报错 `Specification Limit Violation`
-
-**原因**:阿里云 SDK 的 `.har` 文件需要特定编译配置
-
-**解决方案**:在 `build-profile.json5` 中启用:
-
-```json5
-{
- "app": {
- "products": [{
- "buildOption": {
- "strictMode": {
- "useNormalizedOHMUrl": true
- }
- }
- }]
- }
-}
-```
-
-### 问题 4:参数解析为 undefined
-
-**现象**:`hostname` 和 `ipType` 参数为 `undefined`
-
-**原因**:Flutter 传递的参数可能是 `Map` 对象,而非普通 `Object`
-
-**解决方案**:同时处理两种情况:
-
-```typescript
-if (args instanceof Map) {
- if (args.has('hostname')) hostname = args.get('hostname') as string;
-} else {
- if (args['hostname']) hostname = args['hostname'] as string;
-}
-```
-
-### 问题 5:同步解析返回空结果
-
-**现象**:`getHttpDnsResultSyncNonBlocking` 首次调用返回空
-
-**原因**:非阻塞方法在缓存未命中时立即返回空结果
-
-**解决方案**:改用异步方法 `getHttpDnsResultAsync`:
-
-```typescript
-const dnsResult = await this.httpDnsService.getHttpDnsResultAsync(hostname, ipType);
-```
-
-## 平台差异处理
-
-| 功能 | Android | iOS | HarmonyOS |
-|------|---------|-----|-----------|
-| accountId 类型 | int/String | Int | String |
-| useHttps 配置 | setHttpsRequestEnabled | setHTTPSRequestEnabled | configService.useHttps |
-| 日志开关 | HttpDnsLog.enable() | setLogEnabled() | httpdns.enableHiLog() |
-
-## 总结
-
-本次适配工作涉及:
-- Flutter 插件 HarmonyOS 平台配置
-- ArkTS 类型系统适配
-- 阿里云 HTTPDNS SDK 集成
-- 跨平台 API 差异处理
-
-HarmonyOS Flutter 插件开发与 Android/iOS 有一些差异,主要体现在:
-- 类型系统更严格(ArkTS)
-- 参数传递方式不同(Map vs Object)
-- SDK API 略有差异
-
-通过本文的实践,相信读者可以更顺利地适配其他 Flutter 插件到 HarmonyOS 平台。
diff --git a/_posts/2026-01-05-harmonyos-internal-testing-automation.md b/_posts/2026-01-05-harmonyos-internal-testing-automation.md
deleted file mode 100644
index 203b61b..0000000
--- a/_posts/2026-01-05-harmonyos-internal-testing-automation.md
+++ /dev/null
@@ -1,319 +0,0 @@
----
-layout: post
-title: "HarmonyOS 应用内测分发自动化实践"
-date: 2026-01-05 17:30:00 +0800
-categories: harmonyos python
-tags: harmonyos automation internal-testing python
----
-
-在 HarmonyOS 应用开发过程中,我们经常需要快速将测试包分发给测试人员进行验证。与 Android 的直接安装 APK 不同,HarmonyOS 采用了一套基于 **企业签名 + Manifest 清单** 的内部测试机制。本文将介绍如何构建一个自动化脚本,实现一键发布 HarmonyOS 内测包。
-
-## 核心流程
-
-整个内测分发流程包含以下步骤:
-
-```mermaid
-flowchart LR
- A[上传 HAP 包] --> B[计算文件哈希]
- B --> C[更新 Manifest]
- C --> D[签名 Manifest]
- D --> E[上传 Manifest]
- E --> F[生成下载页面]
- F --> G[分发给测试人员]
-```
-
-## 技术实现
-
-### 1. 文件上传到云存储
-
-首先需要将 `.hap` 安装包上传到可公开访问的云存储服务(如七牛云、阿里 OSS、腾讯 COS 等)。核心步骤:
-
-1. 从后端接口获取上传凭证(Token)和 URL 前缀
-2. 使用 UUID 生成唯一文件名,避免冲突
-3. 通过 `multipart/form-data` 上传文件
-4. 拼接返回的 key 和 URL 前缀,得到文件的公开访问地址
-
-```python
-def upload_file(file_path, token, url_prefix):
- """上传文件到云存储,返回公开访问URL"""
- unique_key = f"{uuid.uuid4().hex}{os.path.splitext(file_path)[1]}"
- with open(file_path, 'rb') as file:
- response = requests.post(upload_url,
- files={'file': (unique_key, file)},
- data={'token': token, 'key': unique_key})
- return f"{url_prefix}/{response.json()['key']}"
-```
-
-### 2. 计算文件哈希值
-
-HarmonyOS 内测机制要求在 Manifest 中提供安装包的 **SHA256 哈希值**,用于安装时的完整性校验:
-
-```python
-import subprocess
-
-def calculate_sha256(file_path):
- """计算文件的SHA256哈希值"""
- try:
- result = subprocess.run(['shasum', '-a', '256', file_path],
- capture_output=True, text=True, check=True)
- return result.stdout.split()[0]
- except subprocess.CalledProcessError as e:
- raise Exception(f"计算哈希值失败: {e.stderr}")
-```
-
-> 在 Windows 系统上,可以使用 `certutil -hashfile SHA256` 或 Python 的 `hashlib` 模块实现相同功能。
-
-### 3. 更新 Manifest 配置
-
-Manifest 文件是内测分发的核心配置,包含应用信息和下载地址:
-
-```json5
-{
- "app": {
- "bundleName": "com.example.app",
- "bundleType": "app",
- "versionCode": 1000000,
- "versionName": "1.0.0",
- "label": "示例应用",
- "deployDomain": "cdn.example.com",
- "icons": {
- "normal": "https://cdn.example.com/icon.png",
- "large": "https://cdn.example.com/icon_large.png"
- },
- "minAPIVersion": "5.0.1(13)",
- "targetAPIVersion": "5.1.0(18)",
- "modules": [
- {
- "name": "示例应用",
- "type": "entry",
- "deviceTypes": ["tablet", "phone"],
- "packageUrl": "https://cdn.example.com/app.hap",
- "packageHash": "sha256_hash_value"
- }
- ]
- }
-}
-```
-
-更新 Manifest 的关键代码:
-
-```python
-import json
-
-def update_manifest_file(manifest_path, package_url, package_hash, version=None):
- """更新manifest文件"""
- with open(manifest_path, 'r', encoding='utf-8') as f:
- manifest_data = json.load(f)
-
- # 可选:更新版本号
- if version:
- manifest_data['app']['versionName'] = version
- # 将版本号转换为版本代码,如 6.142.00 -> 61420200
- major, minor, patch = version.split('.')
- version_code = f"{major}{minor.zfill(2)}{patch.zfill(2)}0"
- manifest_data['app']['versionCode'] = int(version_code)
-
- # 更新下载地址和哈希值
- for module in manifest_data['app']['modules']:
- if module['type'] == 'entry':
- module['packageUrl'] = package_url
- module['packageHash'] = package_hash
-
- with open(manifest_path, 'w', encoding='utf-8') as f:
- json.dump(manifest_data, f, indent=2, ensure_ascii=False)
-```
-
-### 4. 签名 Manifest 文件
-
-这是最关键的一步。HarmonyOS 要求 Manifest 必须使用开发者证书进行签名,华为提供了官方的签名工具:
-
-```python
-def sign_manifest_file(manifest_path, keystore_path):
- """使用华为官方工具签名manifest文件"""
- command = [
- 'java', '-jar', 'manifest-sign-tool-1.0.0.jar',
- '-operation', 'sign',
- '-mode', 'localjks',
- '-inputFile', manifest_path,
- '-outputFile', manifest_path,
- '-keystore', keystore_path,
- '-keystorepasswd', 'your_keystore_password',
- '-keyaliaspasswd', 'your_alias_password',
- '-privatekey', 'your_key_alias'
- ]
- subprocess.run(command, check=True)
-```
-
-签名后的 Manifest 会包含 `sign` 字段,格式如下:
-
-```json
-{
- "app": { ... },
- "sign": "MEYCIQDC+JmpxzuKrNlH1vu...(Base64编码的签名)"
-}
-```
-
-> **注意**:签名工具需要依赖以下 JAR 包:
-> - `bcprov-jdk18on-1.75.jar` (Bouncy Castle 加密库)
-> - `commons-codec-1.15.jar`
-> - `gson-2.9.1.jar`
-> - `log4j-api-2.23.1.jar` / `log4j-core-2.23.1.jar`
-
-### 5. 生成下载页面
-
-为了方便测试人员安装,我们生成一个包含 **DeepLink** 和 **二维码** 的 HTML 下载页面:
-
-```python
-import qrcode
-import io
-import base64
-from datetime import datetime
-
-def generate_download_page(manifest_url, output_path, version=None, app_icon_url=None):
- """生成包含下载按钮和二维码的HTML页面"""
- deep_link = f'store://enterprise/manifest?url={manifest_url}'
- html_url = f'https://your-server.com/downloads/{os.path.basename(output_path)}'
-
- # 生成二维码
- qr = qrcode.QRCode(version=1, box_size=10, border=4)
- qr.add_data(html_url)
- qr.make(fit=True)
- qr_img = qr.make_image(fill_color="black", back_color="white")
-
- # 转换为 Base64 内嵌到 HTML
- buffered = io.BytesIO()
- qr_img.save(buffered, format="PNG")
- qr_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8')
- qr_data_url = f'data:image/png;base64,{qr_base64}'
-
- # 生成 HTML(省略样式代码)
- html_content = f"""
-
-
-
- 下载应用 v{version}
-
-
- 应用名称
- 版本: {version}
-
-
-
-
- """
-
- with open(output_path, 'w', encoding='utf-8') as f:
- f.write(html_content)
-```
-
-**DeepLink 说明**:
-
-`store://enterprise/manifest?url=` 是 HarmonyOS 的内测安装协议,可以在华为应用市场 App 中解析并触发安装流程。
-
-## 完整工作流
-
-将上述步骤整合,形成一键发布脚本:
-
-```python
-def main():
- parser = argparse.ArgumentParser(description='发布HarmonyOS内测包')
- parser.add_argument('hap_file', type=str, help='.hap文件路径')
- parser.add_argument('-v', '--version', type=str, help='版本号 (如: 1.0.0)')
- args = parser.parse_args()
-
- # 步骤1: 上传 HAP 文件
- print("步骤1: 上传.hap文件...")
- token, url_prefix = get_upload_token()
- hap_url = upload_file(args.hap_file, token, url_prefix)
-
- # 步骤2: 计算哈希值
- print("步骤2: 计算SHA256哈希值...")
- hash_value = calculate_sha256(args.hap_file)
-
- # 步骤3: 更新 Manifest
- print("步骤3: 更新manifest配置...")
- update_manifest_file("manifest_sign.json5", hap_url, hash_value, args.version)
-
- # 步骤4: 签名 Manifest
- print("步骤4: 签名manifest文件...")
- sign_manifest_file("manifest_sign.json5", "your_keystore.p12")
-
- # 步骤5: 上传 Manifest
- print("步骤5: 上传manifest文件...")
- manifest_url = upload_file("manifest_sign.json5", token, url_prefix)
-
- # 步骤6: 生成下载页面
- print("步骤6: 生成下载页面...")
- html_filename = f"app_v{args.version}_{datetime.now().strftime('%m%d_%H%M')}.html"
- generate_download_page(manifest_url, html_filename, args.version)
-
- print(f"\n✅ 发布完成!下载链接: https://your-server.com/downloads/{html_filename}")
-
-if __name__ == "__main__":
- main()
-```
-
-## 使用方式
-
-```bash
-# 安装依赖
-pip3 install requests pillow qrcode
-
-# 发布内测包
-python3 upload_version.py app-release.hap -v 1.2.0
-```
-
-执行后输出:
-```
-步骤1: 上传.hap文件...
-.hap文件上传成功,URL: https://cdn.xxx.com/abc123.hap
-
-步骤2: 计算SHA256哈希值...
-哈希值: 9734d7dac55a4a8aa23a241f9de289773eafa27e...
-
-步骤3: 更新manifest配置...
-manifest文件更新成功
-
-步骤4: 签名manifest文件...
-manifest文件签名成功
-
-步骤5: 上传manifest文件...
-manifest文件上传成功
-
-步骤6: 生成下载页面...
-HTML文件生成成功
-
-✅ 发布完成!下载链接: https://your-server.com/downloads/app_v1.2.0_0105_1730.html
-```
-
-## 下载页面效果
-
-生成的下载页面支持:
-
-- 📱 **一键安装**:点击按钮通过 DeepLink 唤起应用市场安装
-- 📷 **扫码下载**:其他测试人员扫码访问下载页
-- 🌙 **暗黑模式**:自动适配系统深色主题
-- 📐 **响应式布局**:适配手机和平板等不同屏幕
-
-## 安全注意事项
-
-1. **密钥保护**:证书文件 (`.p12`) 和密码不应提交到代码仓库,建议使用环境变量或密钥管理服务
-2. **内网分发**:下载页面和 Manifest 文件建议部署在内网服务器,避免公网泄露
-
-## 总结
-
-通过这套自动化脚本,我们将 HarmonyOS 内测包的发布流程从手动操作简化为一条命令,大大提高了开发效率。核心技术点包括:
-
-- 云存储文件上传
-- SHA256 文件完整性校验
-- Manifest 配置与企业签名
-- DeepLink 协议触发安装
-- QR Code 二维码生成
-
-希望本文对正在开发 HarmonyOS 应用的团队有所帮助!
-
----
-
-**参考资料**:
-- [HarmonyOS 应用内部测试官方文档](https://developer.huawei.com/consumer/cn/doc/app/agc-help-harmonyos-internaltest-0000001937800101)
-- [内部测试验签工具](https://gitee.com/arkin-internal-testing/internal-testing)
diff --git a/_posts/2026-01-08-harmonyos-file-preview-helper.md b/_posts/2026-01-08-harmonyos-file-preview-helper.md
deleted file mode 100644
index b66cb11..0000000
--- a/_posts/2026-01-08-harmonyos-file-preview-helper.md
+++ /dev/null
@@ -1,238 +0,0 @@
----
-layout: post
-title: "HarmonyOS 文件预览工具类:支持远程文件下载与本地预览"
-date: 2026-01-08 19:50:00 +0800
-categories: [HarmonyOS, ArkTS]
-tags: [harmonyos, arkts, filepreview, file-download]
----
-
-在 HarmonyOS 应用开发中,`filePreview.openPreview` 是系统提供的文件预览 API,但它**仅支持本地文件**。当我们需要预览网络上的文件(如 PDF、PPT、Word 文档)时,需要先将文件下载到本地,再调用预览接口。
-
-本文将介绍如何封装一个 `FilePreviewHelper` 工具类,实现:
-- 自动判断本地/远程文件
-- 远程文件自动下载并缓存
-- 正确处理文件 URI 权限
-- 自动识别 MIME 类型
-
-## 遇到的问题与解决方案
-
-### 问题 1:filePreview.openPreview 不支持网络 URL
-
-**现象**:直接传入 `https://` 开头的 URL,预览失败。
-
-**解决方案**:检测 URL 类型,如果是网络 URL,先下载到应用沙箱目录。
-
-### 问题 2:文件 URI 权限问题
-
-**现象**:下载完成后,使用 `file://` + 路径的方式构造 URI,预览仍然失败。
-
-**原因**:`filePreview` 在新窗口中打开文件,应用的临时权限无法传递给预览窗口。
-
-**解决方案**:使用 `fileUri.getUriFromPath()` 将沙箱路径转换为正确的 URI 格式:
-
-```typescript
-import { fileUri } from '@kit.CoreFileKit';
-
-// ❌ 错误方式
-const uri = `file://${targetPath}`;
-
-// ✅ 正确方式
-const uri = fileUri.getUriFromPath(targetPath);
-// 生成格式:file://bundleName/path/to/file
-```
-
-### 问题 3:缓存文件可能不完整
-
-**现象**:之前下载中断,文件大小为 0 或很小,但仍被当作有效缓存使用。
-
-**解决方案**:检查文件大小,如果过小则删除重新下载:
-
-```typescript
-const stat = fs.statSync(targetPath);
-if (stat.size > 1024) {
- // 有效文件,直接使用
- return fileUri.getUriFromPath(targetPath);
-} else {
- // 文件太小,删除重新下载
- fs.unlinkSync(targetPath);
-}
-```
-
-### 问题 4:MIME 类型处理
-
-**说明**:`PreviewInfo` 的 `mimeType` 字段用于指定文件的媒体资源类型。
-
-> **官方文档**:若无法确定文件格式,该项可直接赋值空字符串(`""`),系统会通过 URI 后缀进行文件格式判断。
-
-**两种处理方式**:
-
-```typescript
-// 方式 1:让系统自动判断(推荐)
-const fileInfo: filePreview.PreviewInfo = {
- uri: filePath,
- title: fileName,
- mimeType: "" // 空字符串,系统自动通过后缀判断
-};
-
-// 方式 2:手动指定(可选)
-const MIME_TYPES: Record = {
- 'pdf': 'application/pdf',
- 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
- // ...
-};
-```
-
-## 完整代码实现
-
-```typescript
-import { filePreview } from '@kit.PreviewKit';
-import { common } from '@kit.AbilityKit';
-import { fileIo as fs, fileUri } from '@kit.CoreFileKit';
-import axios, { AxiosResponse } from '@ohos/axios';
-
-export class FilePreviewHelper {
-
- /**
- * 打开文件预览。如果是远程 URL,会先下载到本地。
- */
- static async openPreview(
- context: common.UIAbilityContext,
- url: string,
- title?: string
- ): Promise {
-
- let filePath = url;
- let fileName = title || "文件预览";
-
- // 检查是否为远程 URL
- if (url.startsWith("http")) {
- filePath = await FilePreviewHelper.downloadFile(context, url);
- }
-
- // 构造 PreviewInfo
- const fileInfo: filePreview.PreviewInfo = {
- uri: filePath,
- title: fileName,
- mimeType: "" // 让系统自动判断
- };
-
- // 调用系统预览
- filePreview.openPreview(context, fileInfo);
- }
-
- /**
- * 下载文件到缓存目录
- */
- private static async downloadFile(
- context: common.UIAbilityContext,
- url: string
- ): Promise {
-
- const cacheDir = context.cacheDir;
- const fileName = FilePreviewHelper.getFileNameFromUrl(url);
- const targetPath = `${cacheDir}/${fileName}`;
-
- // 检查文件是否已存在且有效
- try {
- const stat = fs.statSync(targetPath);
- if (stat.size > 1024) {
- return fileUri.getUriFromPath(targetPath);
- } else {
- fs.unlinkSync(targetPath);
- }
- } catch (e) {
- // 文件不存在
- }
-
- // 下载文件
- const response: AxiosResponse = await axios.get(url, {
- responseType: 'array_buffer'
- });
-
- // 写入文件
- const file = fs.openSync(targetPath,
- fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC);
- fs.writeSync(file.fd, response.data);
- fs.closeSync(file);
-
- // 返回正确格式的 URI
- return fileUri.getUriFromPath(targetPath);
- }
-
- /**
- * 从 URL 中提取文件名
- */
- private static getFileNameFromUrl(url: string): string {
- const split = url.split('/');
- let fileName = split[split.length - 1] || "temp_file";
-
- // 去除查询参数
- const queryIndex = fileName.indexOf('?');
- if (queryIndex !== -1) {
- fileName = fileName.substring(0, queryIndex);
- }
-
- // 解码 & 清理文件名
- try {
- fileName = decodeURIComponent(fileName);
- } catch (e) {}
-
- const lastDot = fileName.lastIndexOf('.');
- let baseName = lastDot > 0 ? fileName.substring(0, lastDot) : fileName;
- let ext = lastDot > 0 ? fileName.substring(lastDot) : '';
-
- baseName = baseName.replace(/[^a-zA-Z0-9_-]/g, "_");
- if (baseName.trim().length === 0) {
- baseName = `download_${Date.now()}`;
- }
-
- return baseName + ext;
- }
-}
-```
-
-## 使用示例
-
-```typescript
-// 预览网络 PPT 文件
-FilePreviewHelper.openPreview(
- context,
- 'https://example.com/presentation.pptx',
- '演示文稿'
-);
-
-// 预览本地文件
-FilePreviewHelper.openPreview(
- context,
- 'file://bundleName/data/storage/.../document.pdf',
- 'PDF文档'
-);
-```
-
-## 注意事项
-
-1. **Office 文档预览**:HarmonyOS 预览 Office 文档(如 PPTX、DOCX)依赖系统中安装的 WPS 应用
-2. **文件权限**:使用 `fileUri.getUriFromPath()` 确保权限能正确传递给预览窗口
-3. **缓存策略**:文件下载到 `context.cacheDir`,系统在存储空间不足时会自动清理
-4. **备选方案**:如果本地预览不可用,可考虑使用微软在线预览:
- ```typescript
- const onlineUrl = `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(url)}`;
- ```
-
-## 总结
-
-通过封装 `FilePreviewHelper`,我们解决了 HarmonyOS 中预览网络文件的痛点。关键点包括:
-
-- 使用 `axios` 下载文件到应用沙箱
-- 使用 `fileUri.getUriFromPath()` 生成正确的 URI
-- 系统自动识别 MIME 类型
-- 实现文件缓存机制
-
-希望这篇文章对你有所帮助!
-
-## 参考资料
-
-- [openPreview - PreviewKit API 参考](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/preview-arkts#section144826162913)
-- [fileUri.getUriFromPath - CoreFileKit API](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-file-fileuri-V5#fileurigeturifrompath)
-- [应用沙箱路径详解 - HarmonyOS 开发指南](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/app-sandbox-directory-V5)
-- [@ohos/axios - OpenHarmony 三方库](https://ohpm.openharmony.cn/#/cn/detail/@ohos%2Faxios)
diff --git a/_posts/2026-01-18-protobuf-packed-encoding.md b/_posts/2026-01-18-protobuf-packed-encoding.md
deleted file mode 100644
index b676d84..0000000
--- a/_posts/2026-01-18-protobuf-packed-encoding.md
+++ /dev/null
@@ -1,174 +0,0 @@
----
-layout: post
-title: "Protobuf 性能优化:深入理解 packed=true 编码"
-date: 2026-01-18 15:26:00 +0800
-categories: [技术, Protobuf]
-tags: [protobuf, 性能优化, 序列化]
----
-
-> 在移动端和网络通信场景中,数据传输的效率至关重要。本文将深入探讨 Protocol Buffers 中的 `packed` 编码选项,帮助你在实际项目中优化数据传输性能。
-
-## 一、什么是 packed 编码?
-
-Protocol Buffers(简称 Protobuf)是 Google 开发的一种高效的数据序列化格式。对于 `repeated` 类型的数值字段,Protobuf 提供了两种编码方式:
-
-- **非 packed 编码**:每个元素独立编码,都带有自己的 tag
-- **packed 编码**:所有元素打包成一个连续的字节块,只有一个 tag
-
-## 二、编码原理对比
-
-### 2.1 非 packed 编码结构
-
-```
-[tag][value1][tag][value2][tag][value3]...
-```
-
-每个元素都需要重复写入 tag,造成大量冗余。
-
-### 2.2 packed 编码结构
-
-```
-[tag][total_length][value1][value2][value3]...
-```
-
-所有元素共享一个 tag,通过 length 字段标识数据总长度。
-
-### 2.3 直观对比(100 个 uint64 元素,每个值平均 5 字节)
-
-| 特性 | 不使用 packed | 使用 packed=true |
-|------|:-------------:|:----------------:|
-| **编码方式** | 每个元素单独编码,每个值都带 tag+type+value | 所有元素打包成连续字节块,仅需一个 tag+length 头部,后跟所有 value |
-| **Wire Type** | Varint (0) | Length-delimited (2) |
-| **Tag 开销** | 100 × 1 = **100 字节** | 1 字节 |
-| **Length 开销** | 无 | 2 字节 |
-| **元数据总开销** | **100 字节** | **3 字节** |
-| **Value 大小** | 500 字节 | 500 字节 |
-| **总大小** | **600 字节** | **503 字节** |
-| **节省空间** | — | **97 字节 (16%)** |
-
-> 💡 元素数量越多,`packed=true` 节省的空间越显著!
-
-## 三、如何使用 packed 编码
-
-### 3.1 Proto2 语法
-
-在 proto2 中,`packed` 默认是关闭的,需要显式声明:
-
-```protobuf
-syntax = "proto2";
-
-message StockData {
- // 非 packed(默认)
- repeated uint64 prices = 1;
-
- // 启用 packed
- repeated uint64 volumes = 2 [packed=true];
-}
-```
-
-### 3.2 Proto3 语法
-
-在 proto3 中,数值类型的 `repeated` 字段**默认启用** packed:
-
-```protobuf
-syntax = "proto3";
-
-message StockData {
- // 默认 packed
- repeated uint64 prices = 1;
-
- // 显式关闭 packed(不常用)
- repeated uint64 volumes = 2 [packed=false];
-}
-```
-
-### 3.3 支持的字段类型
-
-`packed` 编码**仅适用于标量数值类型**:
-
-| ✅ 支持 | ❌ 不支持 |
-|--------|----------|
-| int32, int64 | string |
-| uint32, uint64 | bytes |
-| sint32, sint64 | 嵌套 message |
-| fixed32, fixed64, sfixed32, sfixed64 | |
-| float, double | |
-| bool, enum | |
-
-## 四、兼容性问题
-
-### 4.1 客户端和服务端不同步升级会有问题吗?
-
-**不会!** Protobuf 解析器设计了向后兼容机制:
-
-| 场景 | 兼容性 |
-|------|--------|
-| 服务端发送 packed,客户端期望非 packed | ✅ 兼容 |
-| 服务端发送非 packed,客户端期望 packed | ✅ 兼容 |
-
-原因是 Protobuf 解析器会根据 wire type 自动判断数据格式:
-- wire type = 0 → 非 packed,逐个读取
-- wire type = 2 → packed,读取整块后解析
-
-### 4.2 推荐的升级策略
-
-虽然理论上兼容,但建议采用稳妥策略:
-
-1. **先升级客户端**(接收方)—— 确保能解析 packed 格式
-2. **再升级服务端**(发送方)—— 开始发送 packed 数据
-
-### 4.3 Protobuf 库版本 vs Proto 语法版本
-
-这是两个独立的概念:
-
-| 概念 | 说明 |
-|------|------|
-| **库版本** | 运行时库版本(如 3.21.0, 4.25.0) |
-| **语法版本** | `.proto` 文件中的 `syntax = "proto2"` 或 `"proto3"` |
-
-使用 Protobuf 库 3.x/4.x 编译 proto2 语法文件**完全正常**,packed 行为遵循 proto 文件中的声明。
-
-## 五、实战案例:股票筹码分布数据
-
-以一个实际的金融数据场景为例:
-
-```protobuf
-syntax = "proto2";
-
-message CyqData {
- required double min_price = 1; // 最低价格
- required double step_price = 2; // 价格间隔
- required double volume = 3; // 总手数
- required double avg_cost_price = 4; // 平均成本
-
- // 筹码分布数据:每个价格段的筹码数
- // 假设有 200 个价格段
- repeated uint64 datas = 11 [packed=true];
-}
-```
-
-**优化效果**:
-- 200 个价格段,每个 `uint64` 平均 5 字节
-- 非 packed:200 字节 tag 开销
-- packed:3 字节 tag+length 开销
-- **节省 197 字节(约 20%)**
-
-在移动端弱网环境下,这种优化对用户体验的提升是实实在在的!
-
-## 六、总结
-
-| 要点 | 说明 |
-|------|------|
-| **适用场景** | 数值类型的 repeated 字段,尤其是元素较多时 |
-| **Proto2** | 需要显式声明 `[packed=true]` |
-| **Proto3** | 默认启用,无需额外声明 |
-| **兼容性** | 解析器自动兼容两种格式 |
-| **性能提升** | 元素越多,节省越明显 |
-
-> 🎯 **一句话总结**:如果你的项目使用 proto2 语法,且有数值类型的 repeated 字段,记得加上 `[packed=true]`,这是几乎零成本的性能优化!
-
----
-
-*参考资料:*
-- [Protocol Buffers 官方文档](https://protobuf.dev/programming-guides/encoding/)
-- [Protobuf Encoding Guide](https://protobuf.dev/programming-guides/encoding/#packed)
diff --git a/_posts/2026-01-26-google-gemini-api-guide.md b/_posts/2026-01-26-google-gemini-api-guide.md
deleted file mode 100644
index 30e6d02..0000000
--- a/_posts/2026-01-26-google-gemini-api-guide.md
+++ /dev/null
@@ -1,404 +0,0 @@
----
-layout: post
-title: "Google Gemini API 功能说明与测试报告"
-date: 2026-01-26 14:53:00 +0800
-categories: [技术, AI]
-tags: [gemini, google, api, ai, 人工智能]
-published: false
----
-
-> Google Gemini API 是 Google 提供的强大多模态人工智能 API,能够处理文本、图像、音频和视频等多种数据类型。本文将深入介绍 Gemini API 的核心功能,并通过实际测试展示其应用场景。
-
-## 一、概述
-
-**Google Gemini API** 是 Google 最先进的 AI 模型接口,允许开发者将 Gemini 模型集成到自己的应用程序中。Gemini 模型具备强大的多模态处理能力,适用于从简单的文本生成到复杂的多轮对话等多种场景。
-
-### 1.1 核心特性
-
-| 特性 | 说明 |
-|------|------|
-| **多模态支持** | 支持文本、图像、音频、视频处理 |
-| **上下文理解** | 能够理解长文本和复杂的上下文 |
-| **多轮对话** | 支持连续的对话交互 |
-| **实时处理** | 快速响应,适合实时应用 |
-| **安全性** | 内置内容过滤和安全机制 |
-| **可扩展性** | 支持大规模应用部署 |
-| **多语言** | 支持 100+ 种语言 |
-
----
-
-## 二、核心功能详解
-
-### 2.1 文本生成 (Text Generation)
-
-**功能描述**:根据用户提供的提示词(prompt)生成相关的文本内容。
-
-**应用场景**:
-- 内容创作(文章、博客、社交媒体文案)
-- 代码生成
-- 创意写作
-- 学术论文辅助
-
-**示例代码**:
-```python
-from google import genai
-
-client = genai.Client(api_key=api_key)
-response = client.models.generate_content(
- model="gemini-2.5-flash",
- contents="请用一句话解释什么是人工智能"
-)
-print(response.text)
-```
-
-**测试结果**:
-```
-输入:请用一句话解释什么是人工智能
-输出:人工智能是让机器能够像人类一样思考、学习和解决问题的技术。
-```
-
----
-
-### 2.2 多轮对话 (Multi-turn Conversation)
-
-**功能描述**:支持与 AI 进行连续的多轮对话,AI 能够理解对话上下文并给出相应的回复。
-
-**应用场景**:
-- 智能客服系统
-- 教育辅导机器人
-- 虚拟助手
-- 交互式问答系统
-
-**示例代码**:
-```python
-chat = client.chats.create(model="gemini-2.5-flash")
-response1 = chat.send_message("Python 中的列表和元组有什么区别?")
-print(response1.text)
-
-response2 = chat.send_message("能给我一个具体的代码示例吗?")
-print(response2.text)
-```
-
-**特点**:
-- 能够记住对话历史
-- 理解上下文关联
-- 提供连贯的回复
-
----
-
-### 2.3 代码生成与编程辅助 (Code Generation)
-
-**功能描述**:生成各种编程语言的代码片段,包括函数、类、完整程序等。
-
-**应用场景**:
-- 代码补全
-- 算法实现
-- 代码优化建议
-- 编程教学
-
-**示例代码**:
-```python
-response = client.models.generate_content(
- model="gemini-2.5-flash",
- contents="用 Python 写一个函数,计算一个数字列表的平均值。"
-)
-print(response.text)
-```
-
-**生成的代码特点**:
-- 包含详细的注释和文档字符串
-- 提供多个使用示例
-- 包含错误处理机制
-- 代码规范且易读
-
----
-
-### 2.4 文本分类与情感分析 (Text Classification)
-
-**功能描述**:对文本进行分类,如情感分析(正面/负面/中立)、主题分类等。
-
-**应用场景**:
-- 社交媒体监听
-- 评论情感分析
-- 内容审核
-- 用户反馈分析
-
-**示例代码**:
-```python
-response = client.models.generate_content(
- model="gemini-2.5-flash",
- contents="""请将以下文本分类为:正面、负面、中立
- 文本:"这部电影太棒了!演员的表演令人印象深刻。"
- 请只回复分类结果。"""
-)
-print(response.text)
-```
-
-**测试结果**:
-```
-文本:这部电影太棒了!演员的表演令人印象深刻,剧情引人入胜。
-分类结果:正面
-```
-
----
-
-### 2.5 内容总结 (Text Summarization)
-
-**功能描述**:将长文本浓缩为简洁的摘要,保留核心信息。
-
-**应用场景**:
-- 新闻摘要生成
-- 文档摘要
-- 会议记录总结
-- 论文摘要提取
-
-**示例代码**:
-```python
-long_text = """
-机器学习是人工智能的一个重要分支,它使计算机能够从数据中学习和改进,
-而无需明确编程。机器学习算法通过识别数据中的模式和规律,来做出预测或决策。
-常见的机器学习应用包括图像识别、自然语言处理、推荐系统等。
-深度学习是机器学习的一个子领域,使用神经网络来处理复杂的数据模式。
-"""
-response = client.models.generate_content(
- model="gemini-2.5-flash",
- contents=f"请用一句话总结以下文本:\n{long_text}"
-)
-print(response.text)
-```
-
-**测试结果**:
-```
-总结:机器学习是人工智能的一个重要分支,它使计算机能够无需明确编程,
-即可从数据中学习模式并做出预测或决策。
-```
-
----
-
-### 2.6 翻译功能 (Translation)
-
-**功能描述**:支持多种语言之间的翻译,包括英文、中文、日文、法文等。
-
-**应用场景**:
-- 国际化应用
-- 多语言内容管理
-- 文档翻译
-- 实时翻译
-
-**示例代码**:
-```python
-response = client.models.generate_content(
- model="gemini-2.5-flash",
- contents="请将以下英文翻译成中文:'Artificial Intelligence is transforming the world.'"
-)
-print(response.text)
-```
-
-**特点**:
-- 翻译准确,保留原意
-- 支持多种语言对
-- 理解上下文语境
-
----
-
-### 2.7 令牌计数 (Token Counting)
-
-**功能描述**:计算文本内容对应的令牌数量,用于估算 API 成本和限制。
-
-**应用场景**:
-- 成本预估
-- 请求优化
-- 配额管理
-- 性能监控
-
-**示例代码**:
-```python
-text = "Google Gemini 是一个强大的多模态 AI 模型,可以处理文本、图像和其他多种数据类型。"
-count_response = client.models.count_tokens(
- model="gemini-2.5-flash",
- contents=text
-)
-print(f"令牌数: {count_response.total_tokens}")
-```
-
-**测试结果**:
-```
-文本:Google Gemini 是一个强大的多模态 AI 模型,可以处理文本、图像和其他多种数据类型。
-令牌数:29
-```
-
----
-
-## 三、支持的模型
-
-### 3.1 gemini-2.5-flash(推荐)
-
-| 特性 | 说明 |
-|------|------|
-| **速度** | 最快 |
-| **成本** | 最低 |
-| **适用场景** | 大多数实时应用、聊天机器人、内容生成 |
-
-### 3.2 gemini-2.0-pro(高级)
-
-| 特性 | 说明 |
-|------|------|
-| **推理能力** | 更强大 |
-| **准确性** | 更高 |
-| **适用场景** | 复杂分析、深度推理、学术应用 |
-
----
-
-## 四、API 配额与限制
-
-### 4.1 免费层限制
-
-| 限制项 | 数值 |
-|--------|------|
-| **每分钟请求数** | 5 |
-| **每天请求数** | 1,500 |
-| **并发请求数** | 1 |
-| **输入令牌/分钟** | 30,000 |
-| **输出令牌/分钟** | 30,000 |
-
-### 4.2 注意事项
-
-- 免费层适合开发和测试
-- 生产环境建议升级到付费计划
-- 可设置重试机制处理 429 错误(超配额)
-
----
-
-## 五、安装与使用
-
-### 5.1 安装 SDK
-
-```bash
-sudo pip3 install google-genai
-```
-
-### 5.2 设置 API 密钥
-
-```bash
-export GEMINI_API_KEY="your-api-key-here"
-```
-
-### 5.3 基础使用
-
-```python
-import os
-from google import genai
-
-api_key = os.environ.get("GEMINI_API_KEY")
-client = genai.Client(api_key=api_key)
-
-# 简单文本生成
-response = client.models.generate_content(
- model="gemini-2.5-flash",
- contents="你好,请介绍一下自己"
-)
-print(response.text)
-```
-
----
-
-## 六、最佳实践
-
-### 6.1 提示工程 (Prompt Engineering)
-
-- 使用清晰、具体的提示词
-- 提供上下文和示例
-- 指定输出格式
-
-### 6.2 错误处理
-
-```python
-try:
- response = client.models.generate_content(...)
-except Exception as e:
- print(f"API 错误: {e}")
- # 实现重试逻辑
-```
-
-### 6.3 配额管理
-
-- 监控令牌使用量
-- 实现请求队列
-- 设置速率限制
-
-### 6.4 安全性
-
-- 不在代码中硬编码 API 密钥
-- 使用环境变量存储敏感信息
-- 验证和清理用户输入
-
----
-
-## 七、常见用例
-
-### 7.1 智能客服系统
-
-```python
-chat = client.chats.create(model="gemini-2.5-flash")
-user_message = "我想退货"
-response = chat.send_message(user_message)
-print(response.text)
-```
-
-### 7.2 内容生成平台
-
-```python
-response = client.models.generate_content(
- model="gemini-2.5-flash",
- contents="为一个科技博客生成一篇关于 AI 的文章"
-)
-print(response.text)
-```
-
-### 7.3 代码助手
-
-```python
-response = client.models.generate_content(
- model="gemini-2.5-flash",
- contents="用 Python 实现快速排序算法"
-)
-print(response.text)
-```
-
----
-
-## 八、测试总结
-
-### 8.1 成功测试项目
-
-✓ **基础文本生成** - 能够生成准确、自然的文本
-✓ **多轮对话** - 能够理解上下文并提供详细回答
-✓ **创意生成** - 能够生成创意的产品名称和想法
-✓ **文本分类** - 准确识别文本的情感倾向
-✓ **内容总结** - 能够提取文本的核心信息
-✓ **翻译** - 准确的多语言翻译
-✓ **令牌计数** - 正确计算令牌数量
-
-### 8.2 注意事项
-
-⚠ **API 配额**:免费层限制较严格,多轮对话容易触发 429 错误
-⚠ **响应时间**:某些复杂请求可能需要较长时间
-⚠ **成本**:生产环境需要付费 API 密钥
-
----
-
-## 九、资源链接
-
-- **官方文档**:[https://ai.google.dev/gemini-api/docs](https://ai.google.dev/gemini-api/docs)
-- **API 参考**:[https://ai.google.dev/api/rest](https://ai.google.dev/api/rest)
-- **Python SDK**:[https://github.com/googleapis/python-genai](https://github.com/googleapis/python-genai)
-- **定价信息**:[https://ai.google.dev/pricing](https://ai.google.dev/pricing)
-- **配额管理**:[https://ai.dev/rate-limit](https://ai.dev/rate-limit)
-
----
-
-## 十、总结
-
-Google Gemini API 是一个功能强大、易于使用的 AI API,适合各种应用场景。通过合理的提示工程和错误处理,可以构建高效的 AI 驱动的应用程序。建议从免费层开始测试,然后根据需求升级到付费计划。
-
-无论是构建智能客服、内容生成平台,还是代码助手,Gemini API 都能提供强大的支持。随着 AI 技术的不断发展,Gemini API 将继续为开发者提供更多创新的可能性。
diff --git a/_posts/2026-01-30-astro-islands-architecture.md b/_posts/2026-01-30-astro-islands-architecture.md
deleted file mode 100644
index 1264d76..0000000
--- a/_posts/2026-01-30-astro-islands-architecture.md
+++ /dev/null
@@ -1,115 +0,0 @@
----
-layout: post
-title: "深度解析:为何 WebView 混合开发应拥抱 Astro 岛屿架构"
-date: 2026-01-30 16:40:00 +0800
-categories: [技术, 前端]
-tags: [astro, webview, 性能优化, hydration, 混合开发]
----
-
-> 在移动端混合开发(Hybrid App)场景中,WebView 的加载速度直接决定了用户体验的"生死线"。本文将从渲染管线、水合成本(Hydration Cost)及工程实践维度,深度解析为何 Astro 的岛屿架构是比传统 SPA/SSG 更优的工程选择。
-
-## 一、核心矛盾:移动端 CPU 算力瓶颈与 Hydration 开销
-
-在 WebView 环境下,JavaScript 执行主线程与 UI 渲染互斥。传统的 SPA 或基于 Nuxt/Next.js 的 SSR 方案,虽然解决了首屏内容到达(FCP)的问题,但面临着一个不可避免的性能黑洞——**全量水合(Full Hydration)**。
-
-### 1.1 什么是"水合" (Hydration)?
-
-Hydration(水合)是指**客户端 JavaScript "激活" 服务端渲染(SSR)生成的静态 HTML 的过程**。它主要包含三个昂贵的计算步骤:
-
-1. **DOM 重建**:框架(Vue/React)在内存中根据 JS 代码重新构建完整的虚拟 DOM 树。
-2. **节点匹配**:算法将内存中的虚拟 DOM 与浏览器现有的真实 DOM 进行一一比对(Diff),确保结构一致。
-3. **事件挂载**:将 `click`、`input` 等事件监听器绑定到对应的真实 DOM 节点上,使其具备交互能力。
-
-**只有完成水合,页面才能从"即读的 HTML"变成"可交互的应用"。**
-
-### 1.2 传统架构 (SSR/SSG) 的性能陷阱
-
-无论是动态的服务端渲染 (SSR) 还是静态站点生成 (SSG),它们在"水合"阶段的代价本质上是一致的。
-
-**特别点名:SSG (Static Site Generation)**
-
-SSG(如 Nuxt `generate` 或 Next `export`)常被误认为是 WebView 场景的救星,因为它在 **构建时 (Build Time)** 就生成了 HTML,这让首屏加载(TTFB/FCP)极快。然而,这种架构存在显著的 **"恐怖谷"效应**:
-
-**SSG 的"水合"全流程:**
-1. **加载 HTML (满分)**:WebView 瞬间渲染出完美的静态页面,用户立刻看到内容。
-2. **JS 介入 (0分)**:浏览器必须下载 Vue Runtime 和当前页面的全量 JS Bundle。
-3. **重复计算 (Re-execution)**:虽然 DOM 已经存在,但框架**不知道**这些 DOM 是构建时生成的。它必须在客户端内存中重新运行一遍组件逻辑,生成一份全新的 Virtual DOM。
-4. **全量比对 (Hydration Check)**:算法拿着内存里的 vDOM 去和页面上的真实 DOM 逐个比对。即使完全一致,这个 O(N) 的遍历计算也不可避免。
-5. **事件绑定**:直到点击事件挂载完成,页面才真正可交互。
-
-**问题所在**:用户看到按钮了,手指疯狂点击却没反应(Long TBT),因为此时主线程正忙着做无意义的 Virtual DOM 比对。这种"看得见吃不着"的体验比白屏更让用户沮丧。
-
-### 1.3 Astro 的解法:局部水合(Partial Hydration)
-
-Astro 引入了 **Islands Architecture**。其核心理念是:**HTML 优先,JS 按需**。
-
-- **默认零 JS**:Astro 编译产出的静态组件(`.astro` 或无指令的 `.vue`)会被编译为纯 HTML 字符串。不仅没有 Virtual DOM 开销,甚至连 Vue Runtime 都不需要加载。
-- **交互隔离**:只有明确标记了 `client:*` 指令的组件(如 `client:visible`)才会独立触发 hydration。
-- **复杂度**:O(M),M 为交互组件的数量(而非页面总节点数)。
-
-**代码示例:**
-
-```astro
----
-import Header from '../components/Header.astro'; // 静态,无 JS
-import CommentSection from '../components/CommentSection.vue';
----
-
-...5000 字正文...
-
-
-
-```
-
-## 二、工程实战:WebView 场景下的架构优势
-
-在原生应用(Android/HarmonyOS/iOS)中嵌入 WebView 时,Astro 展现出独特的工程优势。
-
-### 2.1 内存与进程开销优化
-
-- **SPA 痛点**:SPA 是一套长期驻留内存的 JS 应用程序。Router 实例、全局 Store、未销毁的监听器都会随着 WebView 的存活持续占用内存。在多 Tab 场景下(如用户快速打开多个详情页),内存压力巨大。
-- **Astro 优势**:基于 MPA(多页应用)模型。每次导航不仅是浏览器级别的页面刷新,更是一次**内存的彻底重置**。对于资讯类页面,这种"用完即走"的策略大幅降低了 WebView 进程发生 OOM(内存溢出)的风险。
-
-### 2.2 启动速度与 Cold Start
-
-- **Bundle 体积**:只需传输关键交互代码。对于一个典型的图文详情页,Vue SPA 可能需要 200KB+ (Gzip) 的 Vendor Bundle,而 Astro 页面通常仅需 <10KB(仅包含点赞、评论组件逻辑)。
-- **JS 执行时机**:利用 `client:visible`,将评论区等非首屏组件的 JS 执行推迟到滚动可见时。这直接释放了首屏加载时的 CPU 算力,让 WebView 更快渲染出 Content。
-
-### 2.3 存量代码复用 (Legacy Compatibility)
-
-Astro 是**框架无关 (Framework Agnostic)** 的。
-
-- **直接复用**:你现有的 Vue 组件库(复杂图表、轮播图、互动组件)可以直接引入 `.astro` 页面。
-- **降级策略**:对于极度复杂的交互页面(如沉浸式复杂单页应用),仍可退化为单页挂载整个 Vue App;但对于 80% 的展示型页面,零成本享受 Astro 的架构红利。
-
-## 三、性能数据对比 (Benchmark)
-
-以一个典型的"资讯详情页"为例(*注:以下数据基于行业典型场景估算,仅供参考*):
-
-| 指标 | Vue SPA / Nuxt SSR | Astro | 差异根源 |
-|------|:------------------:|:-----:|----------|
-| **JS Bundle Size** | 240 KB | **15 KB** | 移除 Vue Runtime (静态页) + 移除无用代码 |
-| **TTI (Time to Interactive)** | 1.8s | **0.6s** | 消除全量水合的 CPU 阻塞 |
-| **TBT (Total Blocking Time)** | 350ms | **20ms** | 主线程空闲,响应更快 |
-| **Lighthouse Performance** | 65 | **98** | - |
-
-## 四、架构选型建议
-
-在移动混合开发体系中,建议采用 **Astro + Vue** 的组合拳:
-
-1. **Astro 主导**:负责所有页面的路由(File-based Routing)、布局(Layouts)和静态内容渲染。
-2. **Vue 辅助**:仅作为"岛屿"(Islands)嵌入,处理动态数据面板、复杂表单等高交互逻辑。
-
-这种架构既保留了 Vue 的开发效率和组件生态,又彻底解决了移动端 H5 性能难以优化的顽疾。对于追求极致体验的 Native App 而言,Astro 是目前性价比最高的 H5 容器化方案。
-
----
-
-## 结语
-
-移动端 WebView 的性能优化,本质上是在和有限的 CPU 算力赛跑。**Astro 的价值不在于让我们写更少的代码,而在于让浏览器执行更少的代码**。当你发现页面上 90% 的内容根本不需要 JavaScript 时,是时候重新思考你的技术选型了。
-
----
-
-*参考资料:*
-- [Astro 官方文档 - Islands Architecture](https://docs.astro.build/en/concepts/islands/)
-- [Web.dev - Rendering on the Web](https://web.dev/rendering-on-the-web/)
diff --git a/_posts/2026-02-04-android-honor-channel-attribution.md b/_posts/2026-02-04-android-honor-channel-attribution.md
deleted file mode 100644
index 98bb102..0000000
--- a/_posts/2026-02-04-android-honor-channel-attribution.md
+++ /dev/null
@@ -1,132 +0,0 @@
----
-layout: post
-title: "Android 接入荣耀应用市场广告归因指南"
-date: 2026-02-04 17:35:00 +0800
-categories: [技术, Android]
-tags: [荣耀, 广告归因, Honor, Android]
----
-
-随着荣耀(Honor)品牌独立,其应用市场的广告归因逻辑也与华为(Huawei)发生了分离。对于开发者而言,在适配荣耀设备时,不能简单复用原有的华为归因代码(如 `InstallReferrer` 或 `ContentProvider`),需要接入荣耀专属的归因查询接口。
-
-本文将介绍如何在 Android 应用中接入荣耀应用市场的广告归因功能,帮助开发者准确获取推广渠道和转化数据。
-
-## 背景
-
-在进行荣耀渠道推广时,App 需要通过荣耀应用市场提供的 `ContentProvider` 接口查询下载归因信息(如广告计划 ID、创意 ID 等)。这些数据对于后续的效果评估和转化回传(Conversion Tracking)至关重要。
-
-## 接入步骤
-
-荣耀归因的核心是通过 `ContentResolver` 查询特定的 `Uri`,并解析返回的 `Cursor` 数据。
-
-### 1. 核心常量定义
-
-荣耀归因的 Uri 固定为:`content://com.hihonor.appmarket.commondata/item/wisepackage`。
-我们需要关注的关键数据是 `wise_params`,它通常位于 Cursor 的第 4 列(索引为 3)。
-
-```kotlin
-private const val PROVIDER_URI = "content://com.hihonor.appmarket.commondata/item/wisepackage"
-private const val INDEX_WISE_PARAMS = 3 // 归因参数在 Cursor 中的索引
-```
-
-### 2. 归因检测器实现
-
-我们可以封装一个单例对象 `HonorChannelDetector`,专门负责查询和解析荣耀的归因数据。
-
-返回的 `wise_params` 是一个 JSON 格式的字符串,包含以下关键字段:
-* **`itgSctChannel`**: 智能分包渠道号(我们在荣耀广告后台绑定的渠道标识)。
-* **`trackId`**: 点击事件追踪 ID,用于回传转化。
-* **`callback`**: 归因回传地址。
-
-**代码示例**:
-
-```kotlin
-import android.content.Context
-import android.database.Cursor
-import android.net.Uri
-import android.text.TextUtils
-import android.util.Log // 或使用你的日志工具
-import org.json.JSONException
-import org.json.JSONObject
-
-object HonorChannelDetector {
- private const val TAG = "HonorChannelDetector"
- private const val PROVIDER_URI = "content://com.hihonor.appmarket.commondata/item/wisepackage"
- private const val INDEX_WISE_PARAMS = 3
-
- /**
- * 查询荣耀归因数据
- * @return Triple(channelCode, taskId, callback)
- */
- fun getTrackId(context: Context): Triple {
- val packageName = arrayOf(context.packageName)
-
- var channelCode = ""
- var taskId = ""
- var callback = ""
-
- var cursor: Cursor? = null
- try {
- cursor = context.contentResolver.query(
- Uri.parse(PROVIDER_URI),
- null, null, packageName, null
- )
-
- if (cursor != null && cursor.moveToFirst()) {
- // 读取 wise_params 字段
- val wiseParams = cursor.getString(INDEX_WISE_PARAMS)
- Log.i(TAG, "Honor wiseParams: $wiseParams")
-
- if (!TextUtils.isEmpty(wiseParams)) {
- try {
- val json = JSONObject(wiseParams)
- // 解析关键字段
- // itgSctChannel: 智能分包渠道号
- if (json.has("itgSctChannel")) {
- channelCode = json.getString("itgSctChannel")
- }
-
- // trackId: 点击事件 trackId
- if (json.has("trackId")) {
- taskId = json.getString("trackId")
- }
-
- // callback: 广告主回传转化地址
- if (json.has("callback")) {
- callback = json.getString("callback")
- }
-
- } catch (e: JSONException) {
- Log.e(TAG, "Honor wiseParams json parse error", e)
- }
- }
- }
- } catch (e: Exception) {
- Log.e(TAG, "Honor getTrackId failed", e)
- } finally {
- cursor?.close()
- }
-
- return Triple(channelCode, taskId, callback)
- }
-}
-```
-
-## 常见问题与注意事项
-
-1. **Cursor 判空与包含性检查**:
- 查询返回的 Cursor 可能为 `null`,或者行数为空,务必进行 `cursor != null && cursor.moveToFirst()` 检查。
-
-2. **数据时效性**:
- 荣耀端侧存储的归因信息有有效期(通常为 90 天,付费推广 trackId 可能更短)。如果用户安装很久后才打开 App,或者清除了应用市场缓存,可能无法查询到数据。
-
-3. **调试建议**:
- * 在荣耀开发者后台创建测试用的“智能分包”和“广告计划”。
- * 通过广告链接下载并安装测试包。
- * 观察 Logcat 输出,确保 `wise_params` 能被正确解析。
-
-## 参考文档
-
-* [荣耀开发者服务平台 - 归因回传对接](https://developer.hihonor.com/) (请参考官方最新文档)
-
----
-*本文代码仅供参考,请根据实际项目需求进行调整。*
diff --git a/_posts/2026-03-02-ai-gtd-openclaw.md b/_posts/2026-03-02-ai-gtd-openclaw.md
deleted file mode 100644
index 56d3894..0000000
--- a/_posts/2026-03-02-ai-gtd-openclaw.md
+++ /dev/null
@@ -1,92 +0,0 @@
----
-layout: post
-title: "告别“任务坟墓”:我用 OpenClaw 打造全自动 AI GTD 引擎"
-date: 2026-03-02 11:07:08 +0800
-categories: [技术, AI, GTD]
-tags: [OpenClaw, GTD, AI, 效率]
-description: "传统 GTD 系统太难维护?本文分享如何利用 OpenClaw AI 智能体框架,打造全自动、零摩擦的“赛博 GTD 引擎”,让大模型成为你的超级大脑外挂。"
----
-
-如果你也是个时间管理(GTD)的信徒,你一定体会过那种“从入门到放弃”的无力感。
-
-大卫·艾伦(David Allen)在《Getting Things Done》里描绘的“心如止水(Mind Like Water)”的状态确实很迷人。对 GTD 稍感陌生的朋友可以这样理解:**它的核心思想是把大脑里的所有杂事、待办、灵感全部清空,倒腾到一个外部的“外脑系统”中去,通过严格的分类和回顾流程,让大脑只负责思考和执行,而不是用来记忆。**
-
-但现实往往是骨感的:我们花在整理 OmniFocus、Things 或者 Notion 这些软件上的时间,甚至超过了干活的时间。每天要清空收件箱、打标签、设优先级……久而久之,GTD 系统就变成了一个让人充满负罪感的“任务坟墓”。
-
-直到我开始深入使用 OpenClaw 这套 AI 智能体(Agent)框架,我突然意识到:**为什么还要自己去维护系统?把 GTD 交给 AI 来跑不就行了吗?**
-
-*(对于不熟悉的朋友来说,OpenClaw 是一款强大的开源 AI 智能体框架,它允许大模型接入本地文件系统、执行终端命令甚至调用外部工具,宛如一个运行在后台的全能超级秘书。)*
-
-于是,我在极短的时间内,利用 OpenClaw 打造了一套全自动的“赛博 GTD 系统”。今天就来分享一下,当 AI 成为你的 GTD 引擎时,体验有多么颠覆。
-
----
-
-## 科普:到底什么是 GTD?
-
-GTD,全称 **Getting Things Done(把事情做完)**,是由管理顾问大卫·艾伦(David Allen)在 2001 年提出的一套时间管理与个人生产力系统。
-
-它的核心哲学只有一句话:**“你的大脑是用来思考的,不是用来记事的。”**
-GTD 认为,我们之所以会感到焦虑和拖延,是因为大脑里堆积了太多“悬而未决”的念头(比如“下周要去旅游”、“冰箱里没牛奶了”、“那个难搞的客户还没回复”)。只要把这些念头全部从大脑里清空,放到一个可靠的外部系统中,大脑就能恢复到“心如止水”的专注状态。
-
-为了实现这个状态,GTD 规定了极其严密的五个步骤:
-1. **收集(Capture)**:把脑子里所有的想法、任务、灵感,毫无遗漏地全部写进一个“收件箱(Inbox)”。
-2. **理清(Clarify)**:定期清空收件箱,判断每件事“是不是可行动的?”如果不可行动,就丢弃或归档;如果可行动,就拆解成第一步要做的具体动作(下一步行动 Next Action)。
-3. **组织(Organize)**:把理清后的具体动作,分门别类放到不同的清单里(比如:项目清单、等待清单、具体情境清单)。
-4. **回顾(Reflect)**:每天或每周检查这些清单,确保系统没有崩溃,任务没有遗漏。
-5. **执行(Engage)**:根据你当前的时间、精力和所处的情境,从清单里挑出一件事,专注地去干。
-
-## 痛点:传统 GTD 为什么难坚持?
-
-这五个步骤看起来很完美,而且其中只有“执行”是我们真正产出价值的环节。但现实的问题是:传统的工具(App 或纸笔)需要我们靠**极大的人力**去维持前四个步骤:
-1. **收集摩擦力大**:灵感来了,需要打开特定 App,点加号,输入,保存。
-2. **理清极其耗脑**:把“想去旅游”这种模糊的念头,拆解成“查机票”、“定酒店”等具体行动,是一件极其消耗意志力的事情。
-3. **组织维护繁琐**:要手动把任务分配到各个列表和项目里。
-
-**传统 GTD 与 AI GTD 的核心差异就在于:**
-* **传统 GTD 苦工**:打字记录 $\rightarrow$ 晚间整理归类 $\rightarrow$ 烧脑拆解步骤 $\rightarrow$ 靠意志力执行。
-* **AI GTD 引擎**:发条语音/消息 $\rightarrow$ AI 自动归放到指定清单 $\rightarrow$ AI 自动拆小步骤 $\rightarrow$ 核心动作人来做,机械动作交给子 Agent 半自动完成。
-
-## 破局:AI 智能体如何重塑 GTD?
-
-借助 OpenClaw 框架以及大语言模型强大的逻辑分析和任务执行能力,我把这套重体力的活儿完全外包了出去。我的新系统架构是这样的:
-
-### 1. 收集 (Capture):聊天即输入,无感记录
-我将自己常用的即时通讯软件作为统一收件箱。走在路上,突然想起一件事,我不需要打开任何复杂的 To-Do 软件,直接对着我的 AI 助理(我管他叫鲁博特)发一条消息:
-> *“inbox:车子快到期了,得安排做个保养。”*
-
-我的 Agent 接收到消息后,会自动在后台的服务器工作区里,将这句话无声无息地写入 `gtd/inbox.md` 文件中。**零摩擦,发完就忘,大脑立刻清空。**
-
-### 2. 理清与组织 (Clarify & Organize):大模型自动拆解
-这是 AI 发挥“魔法”的地方。传统的 GTD 需要我自己每天晚上去审视收件箱。现在,我只需对 Agent 说一句:“整理收件箱”。
-
-大模型会自动读取 `inbox.md`,对里面的每一条“原始念头”进行逻辑分析:
-* **2分钟法则**:如果是“查一下明天北京的天气”这种小事,Agent 直接在后台调用工具(比如网页搜索),瞬间做完并把结果发给我。
-* **复杂项目**:如果是“给老婆准备生日礼物”,它会自动把这个念头转移到 `projects.md`,并帮我拆解成具体的【下一步行动 (Next Actions)】,比如:1. 确认周末行程;2. 搜索热门餐厅;3. 预订蛋糕。然后把这些具体的 Action 写入 `next_actions.md`。
-
-这就相当于我拥有了一个极其聪明的真人秘书,每天帮我把乱七八糟的想法梳理成可以直接执行的动作清单。
-
-### 3. 回顾 (Reflect):心跳机制的完美应用
-GTD 的灵魂在于“回顾”,但人类往往最讨厌做回顾。
-在 OpenClaw 中,有一个非常棒的 `HEARTBEAT.md` 机制。这相当于系统的定时任务轮询。
-
-我在其中写了一条规则:
-> `[GTD_REVIEW] 每天早上 9 点,读取 gtd/next_actions.md,汇总今天需要关注的任务,并通过即时通讯软件发送《今日行动晨报》。`
-
-这样,我不需要自己去强迫自己打开任务列表,系统会自动把整理好的、最高优先级的任务送到我眼前。
-
-### 4. 执行 (Engage):让人机协同发挥到极致
-到了“执行”环节,清单上已经全是具体的动作了。
-最爽的是,如果这个动作带有 `@agent` 标签(比如“写一个 Python 脚本来抓取某某网页的数据”),我甚至不用自己动手。我的主 Agent 会自动 spawn(衍生)出一个子代理(Sub-agent)去后台执行这个编码任务,执行完了把代码丢给我。
-
-而我,只需要专注去完成那些“只有人类能做、需要情感和战略判断”的高价值工作。
-
----
-
-## 结语:从工具到助理
-
-如果说曾经的 GTD 软件只是一个精美的“电子笔记本”,那结合了 OpenClaw 的 AI GTD 系统,就是一个活生生的“大脑外挂引擎”。
-
-大卫·艾伦说:“你的大脑是用来思考的,不是用来记事的。”
-现在我想加一句:**“你的大脑是用来做决策的,那些繁琐的分类和整理,就交给 AI 吧。”**
-
-这套系统目前在我的 Linux 服务器上跑得非常丝滑。如果你也想把自己的大模型变成一个能真正帮你推进生活的“超级秘书”,不妨试试用这种思路来重构你的 GTD 体系。
diff --git a/_posts/2026-03-05-macos-dd-speed-trick.md b/_posts/2026-03-05-macos-dd-speed-trick.md
deleted file mode 100644
index 12391ca..0000000
--- a/_posts/2026-03-05-macos-dd-speed-trick.md
+++ /dev/null
@@ -1,72 +0,0 @@
----
-layout: post
-title: 'macOS dd 命令提速:理解 rdisk 与 disk 的区别'
-date: 2026-03-05 09:15:00 +0800
-categories: [技术, macOS]
-tags: [dd, macOS, NAS, 效率, 命令行]
-description: '在 macOS 上使用 dd 写盘时,将 /dev/diskN 替换为 /dev/rdiskN 可显著提升写入速度。本文从 BSD 设备模型的角度解析 disk 与 rdisk 的区别,并给出完整的操作流程。'
----
-
-## 问题现象
-
-在 macOS 上使用 `dd` 制作系统盘或写入大镜像时,默认使用 `/dev/diskN` 作为目标设备,写入速度往往极慢(例如 4GB 镜像需耗时 15 分钟以上)。
-
-## 根本原因
-
-macOS 基于 Darwin 内核(继承自 BSD),其 `/dev` 目录下每块物理磁盘通常暴露两个设备节点:`diskN` 和 `rdiskN`。
-
-- **`/dev/diskN` (Buffered Device)**:走内核 Buffer Cache。数据从用户空间先拷贝到内核缓存,再由内核调度写入物理设备。带来额外的内存拷贝和管理开销。
-- **`/dev/rdiskN` (Raw Device)**:绕过内核缓存,直接进行块级物理 I/O。
-
-对于 `dd` 这种大块连续顺序写入的工具,内核缓存毫无益处,只会成为性能瓶颈。
-
-## 解决方案
-
-将目标设备路径从 `diskN` 替换为带 `r` 前缀的原始设备 `rdiskN`(如 `/dev/rdisk4`)。速度提升可达 10 倍以上。
-
-### 最佳实践流程
-
-1. **确认目标磁盘**
- ```bash
- diskutil list
- ```
- > 注意:务必反复确认目标设备编号(如 `disk4`),避免覆盖重要数据。
-
-2. **卸载磁盘(非推出)**
- 必须先卸载卷才能使用 `dd` 进行底层覆盖:
- ```bash
- diskutil unmountDisk /dev/disk4
- ```
-
-3. **执行高速写入**
- 使用 `rdisk` 结合合理的 `bs`(Block Size)写入。macOS(BSD)下 `bs` 单位标志为小写 `m`。
- ```bash
- sudo dd if=TrueNAS-13.3.iso of=/dev/rdisk4 bs=1m
- ```
-
-4. **查看进度**
- 写入过程中按 Ctrl + T 发送 `SIGINFO` 信号,可查看当前写入进度与速率。
-
-5. **推出设备**
- 完成写入后安全弹出:
- ```bash
- diskutil eject /dev/disk4
- ```
-
-## 拓展说明
-
-### Linux 环境差异
-
-Linux 的块设备架构与 BSD 不同,没有 `rdisk` 的概念。在 Linux 环境下要实现绕过缓存的 Direct I/O 提速,需通过 `oflag=direct` 参数实现:
-```bash
-sudo dd if=image.iso of=/dev/sdX bs=1M oflag=direct
-```
-
-### 结合 pv 实现可视化进度条
-
-如果希望拥有直观的动态进度条,可结合 `pv` 工具使用:
-```bash
-brew install pv
-# pv 负责读取并显示进度,通过管道交给 dd 裸写
-sudo sh -c 'pv TrueNAS-13.3.iso | dd of=/dev/rdisk4 bs=1m'
-```
diff --git a/_posts/2026-03-09-jekyll-giscus-comments.md b/_posts/2026-03-09-jekyll-giscus-comments.md
deleted file mode 100644
index 3c35be5..0000000
--- a/_posts/2026-03-09-jekyll-giscus-comments.md
+++ /dev/null
@@ -1,101 +0,0 @@
----
-layout: post
-title: "为 Jekyll 博客接入 Giscus 评论系统"
-date: 2026-03-09
----
-
-静态博客天然不具备评论功能,常见的解决方案是引入第三方评论系统。本文记录为 Jekyll 博客接入 [Giscus](https://giscus.app/) 的完整过程。
-
-## 为什么选择 Giscus
-
-评论系统主要有以下几类选择:
-
-- **Disqus**:老牌产品,但免费版有广告,数据在第三方服务器上。
-- **Utterances**:基于 GitHub Issues,轻量,但接口功能有限。
-- **Giscus**:基于 GitHub Discussions,功能更完整,支持回复和表情,无广告,数据完全在自己的仓库。
-
-对于技术博客而言,Giscus 是目前最合适的选择:读者本来就有 GitHub 账号,数据自己掌控,界面简洁无广告。
-
-## Giscus 工作原理
-
-Giscus 的核心思路是**把博客评论托管在 GitHub Discussions 中**,整个链路如下:
-
-1. 在博客页面嵌入一个跨域 `