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 ---- - - - -
-

404

- -

Page not found :(

-

The requested page could not be found.

-
diff --git a/AGENTS.md b/AGENTS.md deleted file mode 120000 index e3c5a92..0000000 --- a/AGENTS.md +++ /dev/null @@ -1 +0,0 @@ -GEMINI.md \ No newline at end of file diff --git a/GEMINI.md b/GEMINI.md deleted file mode 100644 index eb08096..0000000 --- a/GEMINI.md +++ /dev/null @@ -1,138 +0,0 @@ -# Jekyll Blog Project Context - -## 1. 项目概览 (Project Overview) -这是 Donglu (DL) 的个人技术博客。基于 [Jekyll](https://jekyllrb.com/) (Ruby 静态网站生成器) 构建,使用默认的 `minima` 主题,通过 Markdown 文件生成静态页面。 - -- **主要语言:** Ruby, Markdown, HTML, YAML -- **框架版本:** Jekyll ~> 4.4.1 -- **使用主题:** minima -- **使用插件:** jekyll-feed, nokogiri, mermaid_processor.rb (自定义插件) - -## 2. 核心行为准则 (Core Directives) - -### 2.1 写作风格 -- **硬核技术风**:行文简洁客观。避免夸张营销用语和过多 emoji,保持清晰干练的技术叙述。 -- **逻辑严密**:文章结构必须具备强逻辑性。通常遵循「问题现象 → 根本原因 → 解决方案 → 原理扩展/总结」的金字塔结构,层层递进。 -- **精炼准确**:结论要一针见血,避免啰嗦和无关紧要的发散。如果问题出在特定场景/特定前置条件,必须在开篇明确指出,不泛泛而谈。 -- **写作目标优先级**:准确先于修辞,清晰先于热闹,可扫读先于堆砌解释。 - -### 2.2 语气规范 -- 使用克制、直接、可执行的中文;以说明、界定、引导为主,不用夸张宣传语。 -- 不使用第二人称(`你`、`您`、`同学`),如无必要不直接点名读者,用无主句或说明句即可。 -- 避免问候式开场(`Hello`、`Hi`)和口号式抽象表达。 -- **禁用黑话词**(除非该词在当前语境中有严格业务定义):`赋能`、`抓手`、`闭环`、`沉淀`、`对齐`、`对标`、`拉通`、`打通`、`协同`、`联动`、`洞察`、`赛道`、`心智`、`调性`、`战役`、`链路`、`势能`、`兜底`。 - - 优先替换为更具体表达:`赋能` → `提供`,`抓手` → `关键措施`,`闭环` → `完整流程`,`对齐` → `统一`,`兜底` → `保障机制`。 - -### 2.3 标点规范 -- 中文引号统一使用直角引号:`「」`,不使用中文双引号 `""`。 -- 正文中避免连续使用多个感叹号、省略号、宣传口号式断句;避免使用感叹号。 - -### 2.4 中英文留白 -在可见正文中,中文与半角英文单词、英文缩写、独立数字和版本号之间保留空格,提高可读性。 - -- 正确:`获取批量 ID`、`HTTP 请求`、`版本 2.0`、`AI 服务` -- **不要**对以下内容机械加空格:行内代码块、JSON 键名、URL、API 路径、数据库字段名。 - - 例:`user_id`、`/api/example`、`statusCode` 保持原样。 - -### 2.5 术语大小写归一 -仅对可见正文中的自然语言短语做归一,不作用于代码、路径、字段和配置项字面量。 - -| 错写 | 推荐 | -|------|------| -| `id` / `Id` | `ID` | -| `http` / `Http` | `HTTP` | -| `url` / `Url` | `URL` | -| `json` / `Json` | `JSON` | -| `api` / `Api` | `API` | -| `yaml` / `Yaml` | `YAML` | -| `java` | `Java` | -| `kotlin` | `Kotlin` | -| `python` | `Python` | -| `ruby` | `Ruby` | -| `swift` | `Swift` | -| `dart` | `Dart` | -| `javascript` / `JS` | `JavaScript` | -| `typescript` | `TypeScript` | -| `go`(语言名) | `Go` | -| `sql` | `SQL` | -| `bash` | `Bash` | -| `github` | `GitHub` | -| `docker` | `Docker` | -| `redis` | `Redis` | -| `mysql` | `MySQL` | -| `grpc` | `gRPC` | -| `graphql` | `GraphQL` | -| `websocket` | `WebSocket` | -| `H5`(移动 Web 页面) | `移动 Web 页面` | - -### 2.6 中文易错词表 -| 错误写法 | 正确写法 | -|----------|----------| -| `阀值` | `阈值` | -| `登陆系统` | `登录系统` | -| `布署` | `部署` | -| `配制参数` | `配置参数` | -| `回朔` | `回溯` | -| `标示字段` | `标识字段` | -| `帐户`(金融语境) | `账户` | -| `帐号`(平台用户语境) | `账号` | -| `做为` | `作为` | -| `截止 X 日` | `截至 XXXX 年 X 月 X 日` | -| `缩小了 3 倍` | `缩小到原来的 1/3` | -| `翻了 1 倍` | `变为原来的 2 倍` | -| `不超过 100 以上` | `不超过 100` | - -### 2.7 内容规范 -- 除非有特殊要求,文章内容及代码注释请优先使用简体中文。 - -### 2.8 文章创建规范 -- 新文章必须存放在 `_posts/` 目录下,文件命名格式必须为 `YYYY-MM-DD-title.md`。 -- 必须在文件顶部使用 YAML Front Matter 指定 `layout`、`title` 等元数据。 - -### 2.9 排版与结构规则 -- 一个段落只承载一个主要信息点。 -- 长句可以保留,但避免连续堆叠两个以上长句。 -- 列表项要平行,不要有的写定义、有的写宣传、有的写结论。 -- 标题要能反映用途,不要只写抽象名词。 - -### 2.10 最终检查清单 -交付文章前检查: - -- [ ] 是否出现 `你`、`您`、`同学` -- [ ] 是否出现中文双引号 `""`(应改为直角引号 `「」`) -- [ ] 中文与英文、数字之间是否需要留白 -- [ ] 是否存在禁用黑话词 -- [ ] 是否存在过强的宣传口吻或感叹号 -- [ ] 文案是否便于扫读 -- [ ] 术语大小写是否归一 - -## 3. 构建与运行 (Building and Running) -本项目使用 Bundler 管理 Ruby gem 依赖。每次执行 Jekyll 相关命令前,必须使用 `bundle exec` 以确保使用正确的 gem 版本。 - -- **安装依赖:** - ```bash - bundle install - ``` -- **本地开发服务器运行:** - ```bash - bundle exec jekyll serve --livereload - ``` - *(注:开启 livereload 会自动刷新,但修改 `_config.yml` 后需要重启服务器)* -- **构建静态站点:** - ```bash - bundle exec jekyll build - ``` - *(生成的静态文件会存放在 `_site/` 目录下)* - -## 4. 项目结构 (Project Structure) -- `_config.yml`: 站点全局配置 (title, url, theme, plugins 等)。 -- `Gemfile` / `Gemfile.lock`: Ruby 依赖管理文件。 -- `_posts/`: Markdown 格式的博客文章存放目录。 -- `_layouts/`: 自定义 HTML 布局模板 (如 `post.html`)。 -- `_includes/`: 可复用的 HTML 代码片段 (如 `google-analytics.html`, `mermaid.html`)。 -- `_plugins/`: 自定义 Ruby 插件 (如用于渲染 Mermaid 图表的 `mermaid_processor.rb`)。 -- `_site/`: 生成的静态站点目录 (已被 Git 忽略)。 -- `.github/workflows/jekyll.yml`: 用于自动化构建和部署的 GitHub Actions 工作流。 - -## 5. 特定功能 (Specific Features) -- **Mermaid 支持:** 站点内置了对 Mermaid 的支持。通过 `_plugins/mermaid_processor.rb` 和 `_includes/mermaid.html` 渲染图表,在编写 Markdown 时可直接使用 mermaid 语法块。 \ No newline at end of file diff --git a/Gemfile b/Gemfile index 002aadb..eb90aea 100644 --- a/Gemfile +++ b/Gemfile @@ -1,35 +1,6 @@ +# A sample Gemfile source "https://rubygems.org" -# Hello! This is where you manage which Jekyll version is used to run. -# When you want to use a different version, change it below, save the -# file and run `bundle install`. Run Jekyll with `bundle exec`, like so: -# -# bundle exec jekyll serve -# -# This will help ensure the proper Jekyll version is running. -# Happy Jekylling! -gem "jekyll", "~> 4.4.1" -gem "logger" # Ruby 4.0 需要显式添加 -# This is the default theme for new Jekyll sites. You may change this to anything you like. -gem "minima", "~> 2.5" -# If you want to use GitHub Pages, remove the "gem "jekyll"" above and -# uncomment the line below. To upgrade, run `bundle update github-pages`. -# gem "github-pages", group: :jekyll_plugins -# If you have any plugins, put them here! -group :jekyll_plugins do - gem "jekyll-feed", "~> 0.12" -end -# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem -# and associated library. -platforms :mingw, :x64_mingw, :mswin, :jruby do - gem "tzinfo", ">= 1", "< 3" - gem "tzinfo-data" -end +gem "jekyll", ">= 3.8.5" -# Performance-booster for watching directories on Windows -gem "wdm", "~> 0.1", :platforms => [:mingw, :x64_mingw, :mswin] - -# Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem -# do not have a Java counterpart. -gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] -gem "nokogiri" +gem 'jekyll-admin', group: :jekyll_plugins diff --git a/Gemfile.lock b/Gemfile.lock index e052441..422bbc0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,199 +1,88 @@ GEM remote: https://rubygems.org/ specs: - addressable (2.8.8) - public_suffix (>= 2.0.2, < 8.0) - base64 (0.3.0) - bigdecimal (4.0.1) + addressable (2.6.0) + public_suffix (>= 2.0.2, < 4.0) + backports (3.12.0) colorator (1.1.0) - concurrent-ruby (1.3.6) - csv (3.3.5) - em-websocket (0.5.3) + concurrent-ruby (1.1.5) + em-websocket (0.5.1) eventmachine (>= 0.12.9) - http_parser.rb (~> 0) + http_parser.rb (~> 0.6.0) eventmachine (1.2.7) - ffi (1.17.4) - ffi (1.17.4-aarch64-linux-gnu) - ffi (1.17.4-aarch64-linux-musl) - ffi (1.17.4-arm-linux-gnu) - ffi (1.17.4-arm-linux-musl) - ffi (1.17.4-arm64-darwin) - ffi (1.17.4-x86-linux-gnu) - ffi (1.17.4-x86-linux-musl) - ffi (1.17.4-x86_64-darwin) - ffi (1.17.4-x86_64-linux-gnu) - ffi (1.17.4-x86_64-linux-musl) + ffi (1.10.0) forwardable-extended (2.6.0) - google-protobuf (4.34.1) - bigdecimal - rake (~> 13.3) - google-protobuf (4.34.1-aarch64-linux-gnu) - bigdecimal - rake (~> 13.3) - google-protobuf (4.34.1-aarch64-linux-musl) - bigdecimal - rake (~> 13.3) - google-protobuf (4.34.1-arm64-darwin) - bigdecimal - rake (~> 13.3) - google-protobuf (4.34.1-x86-linux-gnu) - bigdecimal - rake (~> 13.3) - google-protobuf (4.34.1-x86-linux-musl) - bigdecimal - rake (~> 13.3) - google-protobuf (4.34.1-x86_64-darwin) - bigdecimal - rake (~> 13.3) - google-protobuf (4.34.1-x86_64-linux-gnu) - bigdecimal - rake (~> 13.3) - google-protobuf (4.34.1-x86_64-linux-musl) - bigdecimal - rake (~> 13.3) - http_parser.rb (0.8.1) - i18n (1.14.8) + http_parser.rb (0.6.0) + i18n (0.9.5) concurrent-ruby (~> 1.0) - jekyll (4.4.1) + jekyll (3.8.5) addressable (~> 2.4) - base64 (~> 0.2) colorator (~> 1.0) - csv (~> 3.0) em-websocket (~> 0.5) - i18n (~> 1.0) - jekyll-sass-converter (>= 2.0, < 4.0) + i18n (~> 0.7) + jekyll-sass-converter (~> 1.0) jekyll-watch (~> 2.0) - json (~> 2.6) - kramdown (~> 2.3, >= 2.3.1) - kramdown-parser-gfm (~> 1.0) + kramdown (~> 1.14) liquid (~> 4.0) - mercenary (~> 0.3, >= 0.3.6) + mercenary (~> 0.3.3) pathutil (~> 0.9) - rouge (>= 3.0, < 5.0) + rouge (>= 1.7, < 4) safe_yaml (~> 1.0) - terminal-table (>= 1.8, < 4.0) - webrick (~> 1.7) - jekyll-feed (0.17.0) - jekyll (>= 3.7, < 5.0) - jekyll-sass-converter (3.1.0) - sass-embedded (~> 1.75) - jekyll-seo-tag (2.8.0) - jekyll (>= 3.8, < 5.0) - jekyll-watch (2.2.1) + jekyll-admin (0.8.1) + addressable (~> 2.4) + jekyll (~> 3.3) + sinatra (~> 1.4) + sinatra-contrib (~> 1.4) + jekyll-sass-converter (1.5.2) + sass (~> 3.4) + jekyll-watch (2.2.0) listen (~> 3.0) - json (2.18.0) - kramdown (2.5.1) - rexml (>= 3.3.9) - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - liquid (4.0.4) - listen (3.9.0) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) - logger (1.7.0) - mercenary (0.4.0) - mini_portile2 (2.8.9) - minima (2.5.2) - jekyll (>= 3.5, < 5.0) - jekyll-feed (~> 0.9) - jekyll-seo-tag (~> 2.1) - nokogiri (1.19.0) - mini_portile2 (~> 2.8.2) - racc (~> 1.4) - nokogiri (1.19.0-aarch64-linux-gnu) - racc (~> 1.4) - nokogiri (1.19.0-aarch64-linux-musl) - racc (~> 1.4) - nokogiri (1.19.0-arm-linux-gnu) - racc (~> 1.4) - nokogiri (1.19.0-arm-linux-musl) - racc (~> 1.4) - nokogiri (1.19.0-arm64-darwin) - racc (~> 1.4) - nokogiri (1.19.0-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.19.0-x86_64-linux-gnu) - racc (~> 1.4) - nokogiri (1.19.0-x86_64-linux-musl) - racc (~> 1.4) + kramdown (1.17.0) + liquid (4.0.3) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + mercenary (0.3.6) + multi_json (1.13.1) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (7.0.0) - racc (1.8.1) - rake (13.3.1) - rb-fsevent (0.11.2) - rb-inotify (0.11.1) + public_suffix (3.0.3) + rack (1.6.11) + rack-protection (1.5.5) + rack + rack-test (1.1.0) + rack (>= 1.0, < 3) + rb-fsevent (0.10.3) + rb-inotify (0.10.0) ffi (~> 1.0) - rexml (3.4.4) - rouge (4.6.1) + rouge (3.3.0) + ruby_dep (1.5.0) safe_yaml (1.0.5) - sass-embedded (1.97.1) - google-protobuf (~> 4.31) - rake (>= 13) - sass-embedded (1.97.1-aarch64-linux-android) - google-protobuf (~> 4.31) - sass-embedded (1.97.1-aarch64-linux-gnu) - google-protobuf (~> 4.31) - sass-embedded (1.97.1-aarch64-linux-musl) - google-protobuf (~> 4.31) - sass-embedded (1.97.1-arm-linux-androideabi) - google-protobuf (~> 4.31) - sass-embedded (1.97.1-arm-linux-gnueabihf) - google-protobuf (~> 4.31) - sass-embedded (1.97.1-arm-linux-musleabihf) - google-protobuf (~> 4.31) - sass-embedded (1.97.1-arm64-darwin) - google-protobuf (~> 4.31) - sass-embedded (1.97.1-riscv64-linux-android) - google-protobuf (~> 4.31) - sass-embedded (1.97.1-riscv64-linux-gnu) - google-protobuf (~> 4.31) - sass-embedded (1.97.1-riscv64-linux-musl) - google-protobuf (~> 4.31) - sass-embedded (1.97.1-x86_64-darwin) - google-protobuf (~> 4.31) - sass-embedded (1.97.1-x86_64-linux-android) - google-protobuf (~> 4.31) - sass-embedded (1.97.1-x86_64-linux-gnu) - google-protobuf (~> 4.31) - sass-embedded (1.97.1-x86_64-linux-musl) - google-protobuf (~> 4.31) - terminal-table (3.0.2) - unicode-display_width (>= 1.1.1, < 3) - unicode-display_width (2.6.0) - webrick (1.9.2) + sass (3.7.3) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sinatra (1.4.8) + rack (~> 1.5) + rack-protection (~> 1.4) + tilt (>= 1.3, < 3) + sinatra-contrib (1.4.7) + backports (>= 2.0) + multi_json + rack-protection + rack-test + sinatra (~> 1.4.0) + tilt (>= 1.3, < 3) + tilt (2.0.9) PLATFORMS - aarch64-linux-android - aarch64-linux-gnu - aarch64-linux-musl - arm-linux-androideabi - arm-linux-gnu - arm-linux-gnueabihf - arm-linux-musl - arm-linux-musleabihf - arm64-darwin - riscv64-linux-android - riscv64-linux-gnu - riscv64-linux-musl ruby - x86-linux-gnu - x86-linux-musl - x86_64-darwin - x86_64-linux-android - x86_64-linux-gnu - x86_64-linux-musl DEPENDENCIES - http_parser.rb (~> 0.6.0) - jekyll (~> 4.4.1) - jekyll-feed (~> 0.12) - logger - minima (~> 2.5) - nokogiri - tzinfo (>= 1, < 3) - tzinfo-data - wdm (~> 0.1) + jekyll (>= 3.8.5) + jekyll-admin BUNDLED WITH - 2.4.19 + 1.16.2 diff --git a/README.md b/README.md new file mode 100644 index 0000000..40cf2f5 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# jekyll + +[![Netlify Status](https://api.netlify.com/api/v1/badges/6c16f430-ee29-40be-a971-b0361987b829/deploy-status)](https://app.netlify.com/sites/donglua/deploys) + +Fresh Jekyll install from running "jekyll new ..." diff --git a/_config.yml b/_config.yml index b747962..b8c5664 100644 --- a/_config.yml +++ b/_config.yml @@ -1,63 +1,14 @@ -# Welcome to Jekyll! -# -# This config file is meant for settings that affect your whole blog, values -# which you are expected to set up once and rarely edit after that. If you find -# yourself editing this file very often, consider using Jekyll's data files -# feature for the data you need to update frequently. -# -# For technical reasons, this file is *NOT* reloaded automatically when you use -# 'bundle exec jekyll serve'. If you change this file, please restart the server process. -# -# If you need help with YAML syntax, here are some quick references for you: -# https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml -# https://learnxinyminutes.com/docs/yaml/ -# # Site settings -# These are used to personalize your new site. If you look in the HTML files, -# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. -# You can create any custom variable you would like, and they will be accessible -# in the templates via {{ site.myvariable }}. - -title: Donglu's Blog -email: your-email@example.com -description: >- # this means to ignore newlines until "baseurl:" - A personal tech blog by DL. -baseurl: "" # the subpath of your site, e.g. /blog -url: "" # the base hostname & protocol for your site, e.g. http://example.com -# twitter_username: jekyllrb -github_username: donglua +title: Donglua +email: flyntcat@live.cn +description: > # this means to ignore newlines until "baseurl:" + Write an awesome description for your new site here. You can edit this + line in _config.yml. It will appear in your document head meta (for + Google search results) and in your feed.xml site description. +baseurl: "" # the subpath of your site, e.g. /blog/ +url: "http://yourdomain.com" # the base hostname & protocol for your site +twitter_username: donglua +github_username: donglua # Build settings -theme: minima -plugins: - - jekyll-feed - -# 固定链接格式:去掉 :categories,避免中文出现在 URL 中 -permalink: /:year/:month/:day/:title/ - -# Google Analytics -google_analytics: G-R2012JP3KW - -# Giscus 评论区(填写后自动启用) -giscus: - repo: donglua/donglua.github.io - -# Exclude from processing. -# The following items will not be processed, by default. -# Any item listed under the `exclude:` key here will be automatically added to -# the internal "default list". -# -# Excluded items can be processed by explicitly listing the directories or -# their entries' file path in the `include:` list. -# -# exclude: -# - .sass-cache/ -# - .jekyll-cache/ -# - gemfiles/ -# - Gemfile -# - Gemfile.lock -# - node_modules/ -# - vendor/bundle/ -# - vendor/cache/ -# - vendor/gems/ -# - vendor/ruby/ +markdown: kramdown diff --git a/_includes/footer.html b/_includes/footer.html new file mode 100644 index 0000000..be3976f --- /dev/null +++ b/_includes/footer.html @@ -0,0 +1,55 @@ + diff --git a/_includes/giscus.html b/_includes/giscus.html deleted file mode 100644 index 21b6293..0000000 --- a/_includes/giscus.html +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/_includes/google-analytics.html b/_includes/google-analytics.html deleted file mode 100644 index 2ab3071..0000000 --- a/_includes/google-analytics.html +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/_includes/head.html b/_includes/head.html new file mode 100644 index 0000000..47057b5 --- /dev/null +++ b/_includes/head.html @@ -0,0 +1,12 @@ + + + + + + {% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %} + + + + + + diff --git a/_includes/header.html b/_includes/header.html new file mode 100644 index 0000000..cfe381f --- /dev/null +++ b/_includes/header.html @@ -0,0 +1,27 @@ + diff --git a/_includes/mermaid.html b/_includes/mermaid.html deleted file mode 100644 index c94a26a..0000000 --- a/_includes/mermaid.html +++ /dev/null @@ -1,14 +0,0 @@ - - \ No newline at end of file diff --git a/_layouts/default.html b/_layouts/default.html new file mode 100644 index 0000000..e4ab96f --- /dev/null +++ b/_layouts/default.html @@ -0,0 +1,20 @@ + + + + {% include head.html %} + + + + {% include header.html %} + +
+
+ {{ content }} +
+
+ + {% include footer.html %} + + + + diff --git a/_layouts/page.html b/_layouts/page.html new file mode 100644 index 0000000..74c1a11 --- /dev/null +++ b/_layouts/page.html @@ -0,0 +1,14 @@ +--- +layout: default +--- +
+ +
+

{{ page.title }}

+
+ +
+ {{ content }} +
+ +
diff --git a/_layouts/post.html b/_layouts/post.html index 7adbab7..a2b4e52 100644 --- a/_layouts/post.html +++ b/_layouts/post.html @@ -1,36 +1,15 @@ --- layout: default --- -
+
-
-

{{ page.title | escape }}

- -
+
+

{{ page.title }}

+ +
-
- {{ content }} -
+
+ {{ content }} +
- {%- if site.disqus.shortname -%} - {%- include disqus_comments.html -%} - {%- endif -%} - - {%- if site.giscus.repo -%} - {%- include giscus.html -%} - {%- endif -%} - - -
- -{% if content contains 'language-mermaid' %} -{% include mermaid.html %} -{% endif %} \ No newline at end of file + diff --git a/_plugins/mermaid_processor.rb b/_plugins/mermaid_processor.rb deleted file mode 100644 index 02ffc03..0000000 --- a/_plugins/mermaid_processor.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'nokogiri' - -Jekyll::Hooks.register [:posts, :pages], :post_render do |doc| - # Only process HTML documents - if doc.output_ext == '.html' - content = doc.output - # Using Nokogiri to parse the HTML - fragment = Nokogiri::HTML::DocumentFragment.parse(content) - - # Find all
 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. 在博客页面嵌入一个跨域 `