[{"content":"","externalUrl":"https://jiangwan.ink/","permalink":"/friends/jiangwan/","section":"友情链接","summary":"base上海前端开发","title":"Jiangwan","type":"friends"},{"content":"","externalUrl":"https://jimyag.com/","permalink":"/friends/jimyag/","section":"友情链接","summary":"base上海golang程序员","title":"Jimyag","type":"friends"},{"content":"","externalUrl":"https://zhangzqs.cn/","permalink":"/friends/zzq/","section":"友情链接","summary":"base上海golang程序员","title":"Zhangzqs","type":"friends"},{"content":"","externalUrl":"https://liusir521.github.io/","permalink":"/friends/liusir/","section":"友情链接","summary":"base北京golang程序员","title":"你怂你mua","type":"friends"},{"content":"","date":"2026-06-03","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"3 June 2026","externalUrl":null,"permalink":"/en/tags/wechat-mini-program/","section":"Tags","summary":"","title":"WeChat Mini Program","type":"tags"},{"content":" ","date":"2026-06-03","externalUrl":null,"permalink":"/","section":"欢迎来到 Blowfish ！","summary":"","title":"欢迎来到 Blowfish ！","type":"page"},{"content":"在小程序开发中，以下场景都需要先拿到目标页面的页面路径（path）：\n跳转到其他小程序：wx.navigateToMiniProgram 设置分享卡片的跳转页面 生成指向指定页面的小程序二维码 下面介绍两种获取页面路径的方法，区别在于能否获取他人小程序的路径：\n方法 入口 适用范围 公众号后台 新建图文 → 小程序 任意小程序 小程序后台 小程序码 → 获取小程序页面小程序码 自有小程序 方法一：通过公众号后台获取任意小程序页面路径 # 该方法借助公众号图文编辑器的小程序插入功能，可获取任意小程序的页面路径。\n登入公众号后台，进入「新的创作」→「文章」。\n在工具栏点击「小程序」。\n点击「去搜索」。\n搜索想要获取页面路径的小程序。\n进入小程序页面选择页，点击「获取更多页面路径」。\n随后按下文「复制页面链接」步骤即可获取任意页面的路径。\n方法二：通过小程序后台获取自有小程序页面路径 # 该方法仅适用于你拥有管理权限的自有小程序。\n登入小程序后台，进入「小程序码」→「获取小程序页面小程序码」，点击「获取更多页面路径」。\n复制页面链接 # 无论使用上述哪种方法，开启「获取更多页面路径」后，操作步骤一致：\n输入有权限的微信号（方法一为已关注公众号的微信号，方法二为小程序项目成员的微信号）并开启。 使用手机进入该小程序，点击右上角「更多」按钮。 在菜单中点击「复制页面链接」，即可获取当前页面的页面路径。 ","date":"2026-06-03","externalUrl":null,"permalink":"/posts/wechat/miniappuri/geturi/","section":"文章","summary":"介绍微信小程序开发中获取任意小程序页面路径的两种实用方法","title":"获取微信小程序页面路径","type":"posts"},{"content":"","date":"2026-06-03","externalUrl":null,"permalink":"/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/","section":"Tags","summary":"","title":"微信小程序","type":"tags"},{"content":"","date":"2026-06-03","externalUrl":null,"permalink":"/posts/","section":"文章","summary":"","title":"文章","type":"posts"},{"content":"","date":"23 May 2026","externalUrl":null,"permalink":"/en/tags/game-development/","section":"Tags","summary":"","title":"Game Development","type":"tags"},{"content":" 订阅消息功能介绍 # 官方文档：https://developers.weixin.qq.com/minigame/dev/guide/open-ability/subscribe-message.html\n微信小游戏/微信小程序中，用户订阅消息后，主动推送消息将通过\u0026quot;服务通知\u0026quot;推送给用户。\n订阅消息分为三种：一次性订阅消息、关系链互动提醒、游戏更新提醒\n一次性订阅消息 # 客户端 # 订阅消息 # 官方文档：https://developers.weixin.qq.com/minigame/dev/api/open-api/subscribe-message/wx.requestSubscribeMessage.html\n// 在小游戏中，只有在以下接口的回调中才能正常使用，即响应用户的点击行为： wx.onTouchEnd wx.showModal wx.showActionSheet 提示：一次性订阅消息，订阅一次消息(即调用一次wx.requestSubscribeMessage())后，用户只能收到一条消息（即累计可接收消息次数+1）\n开发者要推送多次消息，有如下两种方案：\n方案一：引导玩家勾选“总是保持以上选择，不再询问”，在每次登录相关按钮静默触发订阅。\n方案二：显式增加订阅消息次数的按钮，用户点击按钮后，可增加订阅次数（参考微信小程序：狐友）。\n查询订阅消息状态 # 官方文档：https://developers.weixin.qq.com/minigame/dev/api/open-api/setting/wx.getSetting.html\nwx.getSetting() // 带上withSubscriptions 同时获取用户订阅消息的订阅状态。 // 注意：只返回用户勾选过订阅面板中的“总是保持以上选择，不再询问”的订阅消息。 服务端 # 发送订阅消息 # 官方文档：https://developers.weixin.qq.com/minigame/dev/api-backend/subscribe-message/api_sendmessage.html\n接口query参数 必填 access_token 接口调用凭据\n接口请求体参数：\n参数名 类型 必填 说明 template_id string 是 所需下发的订阅模板id page string 否 点击模板卡片后的跳转页面，仅限本小程序内的页面。支持带参数,（示例index?foo=bar）。该字段不填则模板无跳转 touser string 是 接收者（用户）的 openid data object 是 模板内容，格式形如{ \u0026ldquo;phrase3\u0026rdquo;: { \u0026ldquo;value\u0026rdquo;: \u0026ldquo;审核通过\u0026rdquo; }, \u0026ldquo;name1\u0026rdquo;: { \u0026ldquo;value\u0026rdquo;: \u0026ldquo;订阅\u0026rdquo; }, \u0026ldquo;date2\u0026rdquo;: { \u0026ldquo;value\u0026rdquo;: \u0026ldquo;2019-12-25 09:42\u0026rdquo; } } miniprogram_state string 否 跳转小程序类型：developer为开发版；trial为体验版；formal为正式版；默认为正式版 lang string 否 进入小程序查看”的语言类型，支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文)，默认为zh_CN 错误码 # 错误码 错误描述 解决方案 40001 invalid credential，access_token无效 获取access_token时AppSecret错误，或access_token无效。需核对AppSecret，或确认是否为正确的账号调用接口 40003 invalid openid 不合法的openid，需确认touser是否为本小游戏下的openid 40014 invalid access_token 不合法的access_token，需核对其有效性（如是否过期） 40037 invalid template_id 不合法的template_id 43101 用户未订阅消息 检查订阅弹窗回调结果或确认是否订阅成功，检查一次性订阅次数是否已下发完 43107 订阅消息能力封禁 检查账号是否被封禁订阅消息能力，检查模板id对应的模板是否被封禁 43108 并发下发消息给同一个用户 检查是否同时下发多个消息给同一用户 45168 命中敏感词 检查下发消息中是否带有敏感词 47003 参数错误 检查data结构格式是否正确，检查各关键词是否满足对应规则 建议方案 # 维护用户订阅状态 # 服务端只维护每个用户对不同模版消息的订阅次数 服务端向客户端提供两个API\n订阅成功上报(订阅次数+1)，在客户端订阅消息成功时上报\n拒绝消息上报(订阅次数清零)，在客户端查询订阅消息状态\u0026ldquo;订阅消息总开关关闭\u0026quot;或者\u0026quot;订阅状态变更为refuse\u0026quot;时上报\n微信小游戏设置中关闭订阅消息总开关、或者拒绝某个订阅消息模版后，该模板累计订阅次数会清零\n发送消息 # 检验模板参数规范\n检验订阅次数\n发送成功后扣订阅次数\n微信API # openid 用户唯一标识（不同小游戏/小程序独立） # 获取方式：客户端login()获取code后，传入服务端调用code2Session()获取\n官方文档：https://developers.weixin.qq.com/minigame/dev/api/open-api/login/wx.login.html\n接口调用凭据 access_token # 官方文档：https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html\n有两个API可以获取access_token接口调用凭据，推荐使用稳定版接口。 通过appid和secret调用接口，获取access_token。access_token 的有效期为 2 个小时，需定时刷新。稳定版接口有效期内重复调用不会更新 access_token(不强制刷新)，若需要强制刷新可带force_refresh参数，强制刷新至少间隔30s。\n封装SDK # github仓库：https://github.com/muzimu/wechat-sdk\n基础使用方法：\nimport ( \u0026#34;github.com/muzimu/wechat-sdk/cache\u0026#34; \u0026#34;github.com/muzimu/wechat-sdk/client\u0026#34; \u0026#34;github.com/muzimu/wechat-sdk/config\u0026#34; \u0026#34;github.com/muzimu/wechat-sdk/subscribe\u0026#34; ) func main() { // 创建配置 cfg := \u0026amp;config.Config{ AppID: \u0026#34;your_app_id\u0026#34;, AppSecret: \u0026#34;your_app_secret\u0026#34;, Cache: cache.NewMemory(), // 使用内置内存缓存 } // 创建客户端 cli := client.NewClient(cfg) sub := cli.GetSubscribe() // 发送订阅消息 msg := \u0026amp;subscribe.Message{ ToUser: \u0026#34;user_openid\u0026#34;, TemplateID: \u0026#34;template_id\u0026#34;, Page: \u0026#34;pages/index/index\u0026#34;, Data: map[string]*subscribe.DataItem{ \u0026#34;thing1\u0026#34;: {Value: \u0026#34;订单内容\u0026#34;}, \u0026#34;amount2\u0026#34;: {Value: \u0026#34;100.00\u0026#34;}, }, } err := sub.Send(msg) if err != nil { // 处理错误 } } ","date":"2026-05-23","externalUrl":null,"permalink":"/posts/wechat/subscribemessage/","section":"文章","summary":"介绍微信小游戏订阅消息功能开发及建议设计方案","title":"微信小游戏订阅消息","type":"posts"},{"content":"","date":"2026-05-23","externalUrl":null,"permalink":"/tags/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91/","section":"Tags","summary":"","title":"游戏开发","type":"tags"},{"content":"","date":"2026-03-03","externalUrl":null,"permalink":"/tags/go/","section":"Tags","summary":"","title":"Go","type":"tags"},{"content":"","date":"2026-03-03","externalUrl":null,"permalink":"/tags/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91/","section":"Tags","summary":"","title":"后端开发","type":"tags"},{"content":" 安装并配置 dlv # 在 VS Code 中按 Shift + Ctrl（Command） + P，选择 Go: Install/Update Tools，全选（或仅勾选 dlv）后安装调试工具。\n调试 Go 程序 # 打开 Go 文件后，在 VS Code 左侧选择 运行和调试（Debug），点击“运行和调试”即可开始。支持断点、变量监视等常用功能。\n调试命令行程序（标准输入） # 可以借助 dlv 的远程调试能力，调试需要标准输入的命令行程序。\n启动调试服务 dlv debug --headless --listen=:2345 --log --api-version=2 配置调试项 在 VS Code 左侧选择 运行和调试（Debug），点击创建一个 launch.json 文件，填入以下内容：\n{ \u0026#34;version\u0026#34;: \u0026#34;0.2.0\u0026#34;, \u0026#34;configurations\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;Connect to server\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;go\u0026#34;, \u0026#34;request\u0026#34;: \u0026#34;attach\u0026#34;, \u0026#34;mode\u0026#34;: \u0026#34;remote\u0026#34;, \u0026#34;remotePath\u0026#34;: \u0026#34;${fileDirname}\u0026#34;, \u0026#34;port\u0026#34;: 2345, \u0026#34;host\u0026#34;: \u0026#34;127.0.0.1\u0026#34; } ] } 点击绿色三角形按钮即可开始调试，此时可在命令行窗口进行输入输出。\n","date":"2026-03-03","externalUrl":null,"permalink":"/posts/go/debug/","section":"文章","summary":"介绍如何在 VS Code 中使用 dlv（Delve）调试 Go 程序，并演示命令行程序（标准输入）的调试方式","title":"在 VS Code 中调试 Go 程序","type":"posts"},{"content":"这份教程采用 Go 官方推荐的目录结构，全程适配 Zsh 终端（.zshrc），步骤清晰可直接复制执行。\n前置说明（规范路径） # 环境变量 路径 作用 GOROOT ~/.go/go Go 语言本身的安装目录（存放二进制、标准库），仅存放 Go 官方文件 GOPATH ~/go Go 工作区，存放你的项目源码、编译后的包、go install 安装的可执行文件 GOBIN ~/go/bin 专门存放 go install 生成的可执行程序，已加入系统 PATH 分步操作教程 # 步骤 1：创建规范的目录结构 # 先创建 Go 安装目录和工作区目录，确保层级清晰：\n# 创建 GOROOT 目录（存放 Go 安装文件） mkdir -p ~/.go # 创建 GOPATH 工作区（bin=可执行文件、pkg=编译包、src=项目源码） mkdir -p ~/go/{bin,pkg,src} # 进入 Go 安装目录，准备下载安装包 cd ~/.go 步骤 2：下载 Go 1.26 安装包 # 下载适配 Ubuntu amd64 架构的 Go 1.26 安装包（二选一即可）：\n# 方式 1：使用 wget 下载（推荐，大部分 Ubuntu 预装） wget https://go.dev/dl/go1.26.0.linux-amd64.tar.gz # 方式 2：如果没有 wget，用 curl 下载 curl -LO https://go.dev/dl/go1.26.0.linux-amd64.tar.gz 步骤 3：解压安装包到 GOROOT # 将下载的压缩包解压到 ~/.go 目录（会自动生成 ~/.go/go 目录，即 GOROOT）：\n# 先删除旧版本（如有） sudo rm -rf ~/.go/go # 解压到 ~/.go 目录（-C 指定解压根目录） tar -C ~/.go -xzf go1.26.0.linux-amd64.tar.gz 步骤 4：配置 Zsh 环境变量 # 将 Go 环境变量写入 ~/.zshrc（Zsh 终端的配置文件），确保永久生效：\n# 追加环境变量配置到 .zshrc，使用 \u0026#39;EOF\u0026#39; 避免变量转义 cat \u0026gt;\u0026gt; ~/.zshrc \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; # Go 1.26 环境变量（官方规范配置） export GOROOT=$HOME/.go/go export GOPATH=$HOME/go export GOBIN=$GOPATH/bin export PATH=$GOROOT/bin:$GOBIN:$PATH EOF 步骤 5：生效环境变量 # 让刚配置的 .zshrc 立即生效（无需重启终端）：\nsource ~/.zshrc 如果没有使用zsh，把步骤4和步骤5中的.zshrc替换成.bashrc即可\n步骤 6：验证安装是否成功 # 执行以下命令，检查 Go 环境是否配置正确：\n# 1. 检查 Go 版本（应输出 go version go1.26.0 linux/amd64） go version # 2. 检查 GOROOT 路径（应输出 /home/你的用户名/.go/go） echo $GOROOT # 3. 检查 GOPATH 路径（应输出 /home/你的用户名/go） echo $GOPATH # 4. 检查 PATH 中是否包含 GOBIN（应能看到 ~/go/bin 路径） echo $PATH | grep $GOBIN ","date":"2026-02-25","externalUrl":null,"permalink":"/posts/go/install/","section":"文章","summary":"介绍如何在Linux中快速配置Golang环境","title":"Linux中Go环境配置","type":"posts"},{"content":"","date":"2026-02-03","externalUrl":null,"permalink":"/tags/git/","section":"Tags","summary":"","title":"Git","type":"tags"},{"content":"","date":"3 February 2026","externalUrl":null,"permalink":"/en/series/git-tutorial/","section":"Series","summary":"","title":"Git Tutorial","type":"series"},{"content":"","date":"2026-02-03","externalUrl":null,"permalink":"/series/git%E6%95%99%E7%A8%8B/","section":"Series","summary":"","title":"Git教程","type":"series"},{"content":" Git 提交后如何修改用户名 # 当提交 commit 后发现本地 git 配置中的 name 和 github 的用户名不一致，应该如何修改\n仅修改最近一次 Commit # 如果只需要修改最近一次 commit 的作者信息（无需批量修改多个 commit），可使用 --reset-author，它会读取当前 git 配置，一次性同时重置 author 与 committer：\ngit commit --amend --reset-author --no-edit --reset-author：将 author（及 committer）重置为当前 git 配置中的 user.name 和 user.email，并刷新提交时间； --no-edit：保持原 commit message 不变。 前提：当前仓库的 user.name/user.email 需已是目标值。若尚未配置，请先参考文末配置后续新提交默认用新用户名一节。\n修改后执行强制推送即可：\ngit push --force 需求情景 # 在日常开发中，我们常遇到这样的场景：\n本地 Git 初始化时配置了临时用户名（如OldName），提交代码并推送到 fork 仓库的功能分支（如fix_typo），甚至发起了 PR 后，才发现该用户名与 GitHub 账号用户名（如muzimu）不一致；\n不一致的问题会导致：GitHub 上的 commit 作者名显示混乱，无法正常关联个人账号贡献统计，或不符合团队代码提交规范；\n核心诉求：在PR 未合并、已提交多个 commit的前提下，批量修改历史 commit 的作者名，且不影响 PR 状态、无需重建 PR。\n完整教程 # 前提准备 # 确认当前分支：执行git branch，确保处于需要修改的功能分支（如fix_typo，即发起 PR 的分支）；\n确认远程仓库：执行git remote -v，验证origin指向自己的 fork 仓库（而非原仓库），输出类似：\norigin git@github.com:你的GitHub用户名/仓库名.git (fetch) origin git@github.com:你的GitHub用户名/仓库名.git (push) 记录关键信息： 旧用户名：执行git log，查看 commit 中Author:后的名称（如OldName）； 新用户名：目标 GitHub 用户名（如muzimu）； 绑定邮箱：GitHub 账号绑定的邮箱 步骤 1：批量修改历史 commit 的作者名 # 针对已提交多个 commit的场景，使用git filter-branch批量改写当前分支的所有 commit 作者信息，命令直接复制执行（仅需修改 3 个参数）：\n# 批量修改当前分支所有commit的作者名和邮箱 git filter-branch --env-filter \u0026#39; # 需修改的3个参数 OLD_NAME=\u0026#34;你的旧Git名称\u0026#34; NEW_NAME=\u0026#34;你的GitHub用户名\u0026#34; NEW_EMAIL=\u0026#34;你的GitHub绑定邮箱\u0026#34; # 无需修改以下内容 if [ \u0026#34;$GIT_COMMITTER_NAME\u0026#34; = \u0026#34;$OLD_NAME\u0026#34; ]; then export GIT_COMMITTER_NAME=\u0026#34;$NEW_NAME\u0026#34; export GIT_COMMITTER_EMAIL=\u0026#34;$NEW_EMAIL\u0026#34; fi if [ \u0026#34;$GIT_AUTHOR_NAME\u0026#34; = \u0026#34;$OLD_NAME\u0026#34; ]; then export GIT_AUTHOR_NAME=\u0026#34;$NEW_NAME\u0026#34; export GIT_AUTHOR_EMAIL=\u0026#34;$NEW_EMAIL\u0026#34; fi \u0026#39; -- --all 执行后终端显示Rewrite xxx (x/x)代表正在改写 commit，最终提示Ref 'refs/heads/你的分支名' was rewritten即修改成功；\n若提示缓存冲突，先执行rm -rf .git/refs/original/清除缓存，再重新运行上述命令。\n步骤 2：安全强制推送到远程分支 # 修改本地 commit 历史后，本地与远程分支记录不一致，需通过强制推送覆盖远程分支（PR 会自动同步）：\n# 第一步：刷新本地缓存的远程分支信息（避免stale info错误） git fetch origin 你的功能分支名 # 如git fetch origin fix_typo # 第二步：安全强制推送（优先推荐，防止误覆盖） git push --force-with-lease origin 你的功能分支名 # 如git push --force-with-lease origin fix_typo # 备用方案：若上述命令仍报错，直接强制推送（仅自己开发的分支可用） # git push -f origin 你的功能分支名 推送成功后，终端会提示rewriting history或Total 0 (delta 0)。 步骤 3：验证修改效果 # 本地验证：执行git log，查看所有 commit 的Author:是否已更新为新用户名；\n远程验证：打开 GitHub 的 PR 页面，刷新后所有 commit 的作者名会同步更新，且能正常关联你的 GitHub 账号头像。\n步骤 4：配置后续新提交默认用新用户名 # 为避免后续新提交再次使用旧用户名，配置当前仓库或全局 Git 信息：\n# 仅当前仓库生效（推荐，不影响其他仓库） git config user.name \u0026#34;你的GitHub用户名\u0026#34; git config user.email \u0026#34;你的GitHub绑定邮箱\u0026#34; # 全局生效（所有本地仓库新提交均用此配置） # git config --global user.name \u0026#34;你的GitHub用户名\u0026#34; # git config --global user.email \u0026#34;你的GitHub绑定邮箱\u0026#34; 容易踩坑 # 1. 强制推送报错「stale info」 # 原因：本地缓存的远程分支信息与 GitHub 实际状态不一致，--force-with-lease的安全机制拒绝推送；\n解决：先执行git fetch origin 你的分支名刷新缓存，再重新推送。\n2. 修改后 commit 无法关联 GitHub 账号 # 原因：新配置的user.email未绑定 GitHub 账号；\n解决：在 GitHub「Settings → Emails」中添加并验证该邮箱。\n","date":"2026-02-03","externalUrl":null,"permalink":"/posts/git/rename/","section":"文章","summary":"当提交commit后发现本地git配置中的name和github的用户名不一致，应该如何修改","title":"Git提交后如何修改用户名","type":"posts"},{"content":"","date":"2026-02-03","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"2026-01-24","externalUrl":null,"permalink":"/tags/actor/","section":"Tags","summary":"","title":"Actor","type":"tags"},{"content":" 一、什么是Actor模型？ # Actor模型是并发计算的概念模型，起源于1973年。在Actor模型中，Actor是基本的计算单元，认为一切皆为Actor。\nActor的基本操作：\n创建另一个Actor 发送消息 指定如何处理下一条消息 Actor可以维护自己的状态，根据当前状态来决定如何处理下一条消息。\n举例：一个Actor维护账户总余额，收到新的交易消息，则会更新Actor中状态的余额。对于下一条消息，Actor的状态将改变。\nActor非常轻量，可以轻松创建成千上万个Actor，因为它需要的资源比线程少。 Actor之间彼此隔离，不共享内存。它们都有自己的状态，但改变状态的唯一方法是接收消息。 每个Actor都有自己的邮箱，类似于消息队列，消息存储在邮箱中，Actor按照FIFO（先进先出）的顺序处理消息。 Actor之间只能通过消息进行通信，消息是简单的不可变数据结构，可以轻松进行网络传输。 一个Actor一次只能处理一条消息。Actor之间是解耦的，它们异步工作，不需要等待其他Actor的响应。 Actor拥有很多地址，它只能和拥有地址的Actor发送消息。它拥有自己的地址、子Actor的地址、接收到消息发送方Actor的地址、其他相同Actor实例的地址。 一个Actor可以有多个地址（实例）。地址不等同于Actor唯一标识identity。\nActor可以运行在本地或者远程(在别的机器上)，因为Actor模型通过地址通信，所以本地或者远程调用对系统来说是完全透明的。 二、容错机制 # 在Actor模型中，Actor可以监管它的子Actor，包括存活检测、重启及消息失败后的重定向，实现自修复功能。\n三、优缺点总结 # 优点 # 易于扩展 强大的容错能力 支持分布式部署 有效避免传统并发问题 缺点 # 存在死锁的风险 消息队列可能溢出 Actor模型作为一种概念模型，其具体性能和特性取决于具体的实现框架。目前较为知名的实现框架包括Akka和Elixir。\n文章内容来自：https://www.youtube.com/watch?v=ELwEdb_pD0k\n","date":"2026-01-24","externalUrl":null,"permalink":"/posts/game/actor/","section":"文章","summary":"不少游戏开发框架采用Actor模型，本文介绍Actor模型的概念和实现原理","title":"Actor模型 | 介绍","type":"posts"},{"content":"","date":"2026-01-07","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"Git仓库中以前的commit中有readme.md文件，如果想要把文件名修改为README.md，直接修改会发现Git仓库不会识别到修改。\nGit默认是大小写不敏感的，本文介绍了如何设置Git区分大小写，并把文件名修正为正确的大小写。\nGit设置 # Git默认是忽略大小写的，可以通过命令查看当前配置\ngit config core.ignorecase # 输出结果为 true 通过下面的命令修改为Git大小写敏感\ngit config core.ignorecase false 修正文件名 # git mv -f readme.md README.md -f参数表示强制修改。\n提交修改 # 修改完成后，可以使用以下命令提交更改：\ngit add README.md git commit -m \u0026#34;docs: 修正README.md文件名大小写\u0026#34; 如果需要查看修改状态：\ngit status # 输出结果会显示重命名操作 # renamed: readme.md -\u0026gt; README.md ","date":"2026-01-07","externalUrl":null,"permalink":"/posts/git/ignorecase/","section":"文章","summary":"Git默认是大小写不敏感的，本文介绍了如何设置Git区分大小写，并把文件名修正为正确的大小写。","title":"Git区分大小写 修正文件名","type":"posts"},{"content":"","date":"2026-01-07","externalUrl":null,"permalink":"/authors/muzimu/","section":"Authors","summary":"","title":"Muzimu","type":"authors"},{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/tags/mac/","section":"Tags","summary":"","title":"Mac","type":"tags"},{"content":"","date":"11 December 2025","externalUrl":null,"permalink":"/en/tags/productivity/","section":"Tags","summary":"","title":"Productivity","type":"tags"},{"content":"","date":"11 December 2025","externalUrl":null,"permalink":"/en/tags/technology/","section":"Tags","summary":"","title":"Technology","type":"tags"},{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/tags/%E6%8A%80%E6%9C%AF/","section":"Tags","summary":"","title":"技术","type":"tags"},{"content":" Mac常用快捷键 # 🔧 系统基础 # 快捷键 功能描述 符号表示 cmd+tab 切换应用程序 ⌘+⇥ cmd+` 切换同应用窗口 ⌘+` cmd+w 关闭当前窗口/标签页 ⌘+w cmd+m 最小化当前窗口 ⌘+m cmd+shift+3 全屏截图 ⌘+⇧+3 cmd+shift+4 局部截图 ⌘+⇧+4 cmd+q 退出当前应用 ⌘+q cmd+backspace 删除当前行/所选内容 ⌘+⌫ cmd+ctrl+f 全屏模式切换 ⌘+⌃+f 💻 命令行环境 # 快捷键 功能描述 符号表示 ctrl+u 删除光标前整行 ⌃+u ctrl+w 删除光标前一个单词 ⌃+w ctrl+a 光标移至行首 ⌃+a ctrl+e 光标移至行尾 ⌃+e ctrl+k 删除光标至行尾内容 ⌃+k 📝 VS Code # 快捷键 功能描述 符号表示 cmd+, 打开设置 ⌘+, shift+alt+f 格式化文档 ⇧+⌥+f ctrl+` 切换集成终端 ⌃+` cmd+shift+p 打开命令面板 ⌘+⇧+p cmd+w 关闭当前编辑标签 ⌘+w cmd+shift+w 关闭当前窗口 ⌘+⇧+w u-tools # 功能名称 快捷键 符号表示 启动主页面 alt+space ⌥+␣ 爱盯盘 alt+s ⌥+s 起个变量名 alt+n ⌥+n 划词翻译 alt+f ⌥+f Karabiner自定义脚本 # 修饰符：alt（按住alt再按下列）\n功能按键 映射后功能 符号表示 j 左方向键 ⌥+j → ← k 下方向键 ⌥+k → ↓ i 上方向键 ⌥+i → ↑ l 右方向键 ⌥+l → → u backspace ⌥+u → ⌫ m enter ⌥+m → ↵ p ( ⌥+p → ( / = ⌥+/ → = , - ⌥+, → - . + ⌥+. → + { \u0026#34;summary\u0026#34;: \u0026#34;mzm yyds\u0026#34;, \u0026#34;manipulators\u0026#34;: [ { \u0026#34;from\u0026#34;: { \u0026#34;key_code\u0026#34;: \u0026#34;j\u0026#34;, \u0026#34;modifiers\u0026#34;: { \u0026#34;mandatory\u0026#34;: [\u0026#34;left_option\u0026#34;], \u0026#34;optional\u0026#34;: [\u0026#34;any\u0026#34;] } }, \u0026#34;to\u0026#34;: [{ \u0026#34;key_code\u0026#34;: \u0026#34;left_arrow\u0026#34; }], \u0026#34;type\u0026#34;: \u0026#34;basic\u0026#34; }, { \u0026#34;from\u0026#34;: { \u0026#34;key_code\u0026#34;: \u0026#34;k\u0026#34;, \u0026#34;modifiers\u0026#34;: { \u0026#34;mandatory\u0026#34;: [\u0026#34;left_option\u0026#34;], \u0026#34;optional\u0026#34;: [\u0026#34;any\u0026#34;] } }, \u0026#34;to\u0026#34;: [{ \u0026#34;key_code\u0026#34;: \u0026#34;down_arrow\u0026#34; }], \u0026#34;type\u0026#34;: \u0026#34;basic\u0026#34; }, { \u0026#34;from\u0026#34;: { \u0026#34;key_code\u0026#34;: \u0026#34;i\u0026#34;, \u0026#34;modifiers\u0026#34;: { \u0026#34;mandatory\u0026#34;: [\u0026#34;left_option\u0026#34;], \u0026#34;optional\u0026#34;: [\u0026#34;any\u0026#34;] } }, \u0026#34;to\u0026#34;: [{ \u0026#34;key_code\u0026#34;: \u0026#34;up_arrow\u0026#34; }], \u0026#34;type\u0026#34;: \u0026#34;basic\u0026#34; }, { \u0026#34;from\u0026#34;: { \u0026#34;key_code\u0026#34;: \u0026#34;l\u0026#34;, \u0026#34;modifiers\u0026#34;: { \u0026#34;mandatory\u0026#34;: [\u0026#34;left_option\u0026#34;], \u0026#34;optional\u0026#34;: [\u0026#34;any\u0026#34;] } }, \u0026#34;to\u0026#34;: [{ \u0026#34;key_code\u0026#34;: \u0026#34;right_arrow\u0026#34; }], \u0026#34;type\u0026#34;: \u0026#34;basic\u0026#34; }, { \u0026#34;from\u0026#34;: { \u0026#34;key_code\u0026#34;: \u0026#34;u\u0026#34;, \u0026#34;modifiers\u0026#34;: { \u0026#34;mandatory\u0026#34;: [\u0026#34;left_option\u0026#34;], \u0026#34;optional\u0026#34;: [\u0026#34;any\u0026#34;] } }, \u0026#34;to\u0026#34;: [{ \u0026#34;key_code\u0026#34;: \u0026#34;delete_or_backspace\u0026#34; }], \u0026#34;type\u0026#34;: \u0026#34;basic\u0026#34; }, { \u0026#34;from\u0026#34;: { \u0026#34;key_code\u0026#34;: \u0026#34;m\u0026#34;, \u0026#34;modifiers\u0026#34;: { \u0026#34;mandatory\u0026#34;: [\u0026#34;left_option\u0026#34;], \u0026#34;optional\u0026#34;: [\u0026#34;any\u0026#34;] } }, \u0026#34;to\u0026#34;: [{ \u0026#34;key_code\u0026#34;: \u0026#34;keypad_enter\u0026#34; }], \u0026#34;type\u0026#34;: \u0026#34;basic\u0026#34; }, { \u0026#34;from\u0026#34;: { \u0026#34;key_code\u0026#34;: \u0026#34;p\u0026#34;, \u0026#34;modifiers\u0026#34;: { \u0026#34;mandatory\u0026#34;: [\u0026#34;left_option\u0026#34;], \u0026#34;optional\u0026#34;: [\u0026#34;any\u0026#34;] } }, \u0026#34;to\u0026#34;: { \u0026#34;key_code\u0026#34;: \u0026#34;9\u0026#34;, \u0026#34;modifiers\u0026#34;: [\u0026#34;shift\u0026#34;] }, \u0026#34;type\u0026#34;: \u0026#34;basic\u0026#34; }, { \u0026#34;from\u0026#34;: { \u0026#34;key_code\u0026#34;: \u0026#34;slash\u0026#34;, \u0026#34;modifiers\u0026#34;: { \u0026#34;mandatory\u0026#34;: [\u0026#34;left_option\u0026#34;], \u0026#34;optional\u0026#34;: [\u0026#34;any\u0026#34;] } }, \u0026#34;to\u0026#34;: [{ \u0026#34;key_code\u0026#34;: \u0026#34;equal_sign\u0026#34; }], \u0026#34;type\u0026#34;: \u0026#34;basic\u0026#34; }, { \u0026#34;from\u0026#34;: { \u0026#34;key_code\u0026#34;: \u0026#34;comma\u0026#34;, \u0026#34;modifiers\u0026#34;: { \u0026#34;mandatory\u0026#34;: [\u0026#34;left_option\u0026#34;], \u0026#34;optional\u0026#34;: [\u0026#34;any\u0026#34;] } }, \u0026#34;to\u0026#34;: [{ \u0026#34;key_code\u0026#34;: \u0026#34;hyphen\u0026#34; }], \u0026#34;type\u0026#34;: \u0026#34;basic\u0026#34; }, { \u0026#34;from\u0026#34;: { \u0026#34;key_code\u0026#34;: \u0026#34;period\u0026#34;, \u0026#34;modifiers\u0026#34;: { \u0026#34;mandatory\u0026#34;: [\u0026#34;left_option\u0026#34;], \u0026#34;optional\u0026#34;: [\u0026#34;any\u0026#34;] } }, \u0026#34;to\u0026#34;: { \u0026#34;key_code\u0026#34;: \u0026#34;equal_sign\u0026#34;, \u0026#34;modifiers\u0026#34;: [\u0026#34;shift\u0026#34;] }, \u0026#34;type\u0026#34;: \u0026#34;basic\u0026#34; } ] } ","date":"2025-12-11","externalUrl":null,"permalink":"/posts/mac/","section":"文章","summary":"","title":"提高Mac工作效率，自定义快捷键","type":"posts"},{"content":"","date":"2025-12-11","externalUrl":null,"permalink":"/tags/%E6%95%88%E7%8E%87/","section":"Tags","summary":"","title":"效率","type":"tags"},{"content":"FlatBuffers是一种高效的二进制序列化格式，它使用一种简单的描述语言来定义数据结构，并生成相应的C++、Java、C#、Go、Dart、JavaScript、Lua、Rust、Swift、Kotlin、Dart、Ruby、PHP、C#、Objective-C、C、C++、Rust、Java、Python、Lua、Dart、Ruby、PHP、C#、Objective代码。\nFlatBuffers 环境配置 # 安装编译依赖 # # Linux sudo apt update \u0026amp;\u0026amp; sudo apt upgrade -y sudo apt install -y build-essential cmake git # MacOS brew update \u0026amp;\u0026amp; brew upgrade brew install cmake git 编译安装 FlatBuffers # # 以下为Linux和MacOS下载源码进行编译流程 git clone git@github.com:google/flatbuffers.git --depth=1 cd flatbuffers # Linux cmake -G \u0026#34;Unix Makefiles\u0026#34; -DCMAKE_BUILD_TYPE=Release # MacOS cmake -G \u0026#34;Xcode\u0026#34; -DCMAKE_BUILD_TYPE=Release # Linux make -j$(nproc) # MacOS xcodebuild -toolchain clang -configuration Release # Linux sudo make install # MacOS sudo cp Release/flatc /usr/local/bin/ # Windows推荐直接下载预编译版 https://github.com/google/flatbuffers/releases 下载解压到指定目录，并将目录添加到环境变量中 测试安装成功 # flatc --version 定义 FlatBuffers Schema 文件 # FlatBuffers Schema 文件定义了数据结构，包含数据类型、字段、字段顺序等信息。\n// monster.fbs namespace MyGame.Sample; enum Color:byte { Red = 0, Green, Blue } struct Vec3 { x:float; y:float; z:float; } table Monster { pos:Vec3; // 结构体字段 mana:short = 150; // 默认值 hp:short = 100; name:string; color:Color = Blue;// 枚举字段 } root_type Monster; // 指定根类型 生成代码 # flatc --go monster.fbs flatc --ts monster.fbs ","date":"2025-11-30","externalUrl":null,"permalink":"/posts/game/flatbuffers/","section":"文章","summary":"","title":"FlatBuffers 使用教程","type":"posts"},{"content":"本文翻译自 Nakama 官方文档(Cocos2d-x JavaScript Client Guide)\n概述 # 官方Cocos2d JavaScript客户端：支持与服务器的实时通信，实现所有服务器功能，并适用于cocos2d-x js项目。客户端附带类型定义文件，支持TypeScript。\n开源地址：GitHub。欢迎报告问题和贡献代码以帮助改进。\n下载 # 下载最新版本：从GitHub发布页面下载包含Nakama-js模块（UMD模块加载器）和polyfill库的最新版本。\n更新提示：在升级前，请查阅CHANGELOG了解更新升级内容。\n设置 # 将下载的文件解压到src目录。\n在project.json中导入以下文件：\n\u0026#34;jsList\u0026#34; : [ ... \u0026#34;src/NakamaSDK/ThirdParty/polyfill.js\u0026#34;, \u0026#34;src/NakamaSDK/nakama-js.umd.js\u0026#34; ] 配置 # 创建客户端对象以执行针对服务器的所有逻辑：\n// 默认连接设置 var serverkey = \u0026#34;defaultkey\u0026#34;; var host = \u0026#34;127.0.0.1\u0026#34;; var port = \u0026#34;7350\u0026#34;; var useSSL = false; var timeout = 7000; // ms var client = new nakamajs.Client(serverkey, host, port, useSSL, timeout); Cocos2d 不支持await及promisesJavaScript功能。如果想要使用promises，可以选择使用polyfill第三方库。\n认证 # 使用客户端对象进行认证。可以注册或登录用户，采用推荐模式编写代码。\nconst email = \u0026#34;hello@example.com\u0026#34;; const password = \u0026#34;somesupersecretpassword\u0026#34;; client.authenticateEmail(email, password).then(function(session) { cc.log(\u0026#34;认证成功。用户ID:\u0026#34;, session.user_id); cc.sys.localStorage.setItem(\u0026#34;nakamaToken\u0026#34;, session.token); }, function(error) { cc.error(\u0026#34;认证失败:\u0026#34;, JSON.stringify(error)); }); 发送消息 # 用户认证后，使用会话连接服务器并发送各种消息，如添加好友、加入群组、提交排行榜成绩等。你可以通过RPC执行服务器远程代码。服务器同样提供了一个存储引擎来维护用户记录及保存游戏数据，我们将通过存储来介绍如何发送消息。\nconst objects = [{ \u0026#34;collection\u0026#34;: \u0026#34;collection\u0026#34;, \u0026#34;key\u0026#34;: \u0026#34;key1\u0026#34;, \u0026#34;value\u0026#34;: {\u0026#34;jsonKey\u0026#34;: \u0026#34;jsonValue\u0026#34;} }, { \u0026#34;collection\u0026#34;: \u0026#34;collection\u0026#34;, \u0026#34;key\u0026#34;: \u0026#34;key2\u0026#34;, \u0026#34;value\u0026#34;: {\u0026#34;jsonKey\u0026#34;: \u0026#34;jsonValue\u0026#34;} }]; client.writeStorageObjects(session, objects) .then(function(storageWriteAck) { cc.log(\u0026#34;Storage成功写入:\u0026#34;, JSON.stringify(storageWriteAck)); }, function(error) { cc.error(\u0026#34;Storage写入失败:\u0026#34;, JSON.stringify(error)); }); 在文档的其他部分可以找到更多示例代码。\n实时数据交换 # 你可以通过WebSocket连接来保持与服务器的实时数据交换，包括接收聊天消息、接收通知、匹配进入多玩家比赛。你同样可以通过RPC来执行远程服务器代码。你首先应该和服务器创建一个实时的socker连接：\nconst useSSL = false; const verboseLogging = false; const createStatus = false; // set `true` to send presence events to subscribed users. const socket = client.createSocket(useSSL, verboseLogging); var session = \u0026#34;\u0026#34;; // obtained by authentication. socket.connect(session, createStatus) .then( function() { cc.log(\u0026#34;连接成功\u0026#34;); }, function(error) { cc.error(\u0026#34;连接失败:\u0026#34;, JSON.stringify(error)); } ); 然后接下来加入一个聊天频道并发送一条消息：\nsocket.onchannelmessage = function (channelMessage) { cc.log(\u0026#34;成功接收聊天消息:\u0026#34;, JSON.stringify(channelMessage)); }; const channelId = \u0026#34;pineapple-pizza-lovers-room\u0026#34;; const persistence = false; const hidden = false; socket.joinChat(channelId, 1, persistence, hidden).then( function(response) { cc.log(\u0026#34;成功加入聊天频道:\u0026#34;, response.channel.id); sendChatMessage(response.channel.id); }, function(error) { cc.error(\u0026#34;join channel failed:\u0026#34;, JSON.stringify(error)); } ); function sendChatMessage(channelId) { socket.writeChatMessage(channelId, {\u0026#34;message\u0026#34;: \u0026#34;Pineapple doesn\u0026#39;t belong on a pizza!\u0026#34;}).then( function(messageAck) { cc.log(\u0026#34;成功发送聊天消息:\u0026#34;, JSON.stringify(messageAck)); }, function(error) { cc.error(\u0026#34;发送聊天消息失败:\u0026#34;, JSON.stringify(error)); } ); } 你可以在这里了解更多不同的聊天功能信息。\n处理事件 # 客户端的Socket连接拥有事件监听器，这些事件监听器处理从服务器接收到的各种事件。\nsocket.ondisconnect = function (event) { cc.log(\u0026#34;与服务器断开连接。事件：\u0026#34;, JSON.stringify(event)); }; socket.onnotification = function (notification) { cc.log(\u0026#34;收到通知：\u0026#34;, JSON.stringify(notification)); }; socket.onchannelpresence = function (presence) { cc.log(\u0026#34;收到存在状态更新：\u0026#34;, JSON.stringify(presence)); }; socket.onchannelmessage = function (message) { cc.log(\u0026#34;收到新的聊天消息：\u0026#34;, JSON.stringify(message)); }; socket.onmatchdata = function (matchdata) { cc.log(\u0026#34;收到匹配数据：\u0026#34;, JSON.stringify(matchdata)); }; socket.onmatchpresence = function (matchpresence) { cc.log(\u0026#34;收到匹配存在状态更新：\u0026#34;, JSON.stringify(matchpresence)); }; socket.onmatchmakermatched = function (matchmakerMatched) { cc.log(\u0026#34;收到匹配器更新：\u0026#34;, JSON.stringify(matchmakerMatched)); }; socket.onstatuspresence = function (statusPresence) { cc.log(\u0026#34;收到状态存在更新：\u0026#34;, JSON.stringify(statusPresence)); }; socket.onstreampresence = function (streamPresence) { cc.log(\u0026#34;收到流存在状态更新：\u0026#34;, JSON.stringify(streamPresence)); }; socket.onstreamdata = function (streamdata) { cc.log(\u0026#34;收到流数据：\u0026#34;, JSON.stringify(streamdata)); }; 有些事件监听只需要根据您想要使用的功能选择实现。\n回调(callbacks) 描述(summary) onDisconnect 客户端被服务器断开连接 onNotification 服务器推送的app通知 onChannelMessage 其他用户发送的实时聊天信息 onChannelPresence 在聊天中加入离开频道事件 onMatchState 实时多玩家匹配程序匹配数据 onMatchPresence 接收实时多玩家匹配程序加入离开的事件 onMatchmakerMatched 匹配程序找到合适匹配 onStatusPresence 订阅用户状态源时接收状态更新 onStreamPresence 流加入离开事件 onStreamState 服务器发送的流数据 日志和报错 # 服务器和客户端都可以生成日志，来帮助我们debug代码。当你构建一个客户端的时候，为了把该客户端发送的所有消息都打日志，你可以打开verbose。\nconst verboseLogging = true; const useSSL = false; var client = new nakamajs.Client(\u0026#34;defaultkey\u0026#34;); client.verbose = verboseLogging; socket = client.createSocket(useSSL, verboseLogging); 完整代码示例 # 一个通过cocos2d-x JavaScript客户端库来管理session的完整代码示例：\nvar client = new nakamajs.Client(\u0026#34;defaultkey\u0026#34;); var currentSession = null; function storeSession(session) { cc.sys.localStorage.setItem(\u0026#34;nakamaToken\u0026#34;, session.token); cc.log(\u0026#34;Session存储成功.\u0026#34;); } function getSessionFromStorage() { return cc.sys.localStorage.getItem(\u0026#34;nakamaToken\u0026#34;); } function restoreSessionOrAuthenticate() { const email = \u0026#34;hello@example.com\u0026#34;; const password = \u0026#34;somesupersecretpassword\u0026#34;; var session = null; try { var sessionString = getSessionFromStorage(); if (sessionString \u0026amp;\u0026amp; sessionString !== \u0026#34;\u0026#34;) { session = nakamajs.Session.restore(sessionString); var currentTimeInSec = new Date() / 1000; if (!session.isexpired(currentTimeInSec)) { cc.log(\u0026#34;Session恢复成功，UserID: \u0026#34;, session.user_id); return Promise.resolve(session); } } return new Promise(function(resolve, reject) { client.authenticateEmail(email, password) .then(function(session) { storeSession(session); cc.log(\u0026#34;认证成功，UserID:\u0026#34;, session.user_id); resolve(session); }, function(error) { cc.error(\u0026#34;认证失败:\u0026#34;, JSON.stringify(error)); reject(error); }); }); } catch(e) { cc.log(\u0026#34;当尝试恢复Session或者认证的时候，发生错误:\u0026#34;, JSON.stringify(e)); return Promise.reject(e); } } restoreSessionOrAuthenticate().then(function(session) { currentSession = session; return client.writeStorageObjects(currentSession, [{ \u0026#34;collection\u0026#34;: \u0026#34;collection\u0026#34;, \u0026#34;key\u0026#34;: \u0026#34;key1\u0026#34;, \u0026#34;value\u0026#34;: {\u0026#34;jsonKey\u0026#34;: \u0026#34;jsonValue\u0026#34;} }]); }).then(function(writeAck) { cc.log(\u0026#34;Storage写成功 - ack:\u0026#34;, JSON.stringify(writeAck)); }).catch(function(e) { cc.log(\u0026#34;发生错误:\u0026#34;, JSON.stringify(e)); }); ","date":"2025-11-24","externalUrl":null,"permalink":"/posts/game/nakama/cocos/javascript/","section":"文章","summary":"","title":"Cocos2d-x JavaScript 客户端指南","type":"posts"},{"content":"","date":"2025-11-24","externalUrl":null,"permalink":"/tags/coscos/","section":"Tags","summary":"","title":"Coscos","type":"tags"},{"content":"","date":"2025-11-24","externalUrl":null,"permalink":"/tags/nakama/","section":"Tags","summary":"","title":"Nakama","type":"tags"},{"content":"","date":"4 November 2025","externalUrl":null,"permalink":"/en/tags/backend-development/","section":"Tags","summary":"","title":"Backend Development","type":"tags"},{"content":"","date":"4 November 2025","externalUrl":null,"permalink":"/en/tags/devops/","section":"Tags","summary":"","title":"DevOps","type":"tags"},{"content":"💡 前言 高效优雅地查询日志是后端工程师的必修课。本篇文章详细介绍结构化日志(json)查询工具的使用方法，并提供多种工具结合的实际案例分析。\n我们假设当前目录下有多个日志文件，均为.log后缀，下面开始介绍日志查询命令。\n🔍 读取日志的命令 # cat 命令 # cat 是 Linux/Unix 系统中最基础且常用的命令之一，全称为 \u0026ldquo;concatenate\u0026rdquo;（连接），主要用于读取、显示、合并文件内容，也可用于创建简单文件。以下是其详细用法：\n读取单个日志文件：直接指定文件名，可查看该日志文件的全部内容。 cat test.log 读取多个指定日志文件：依次列出多个日志文件名，可一次性查看这些文件的内容（按输入顺序拼接展示）。 cat test1.log test2.log 读取当前目录下所有 .log 后缀的日志文件：使用通配符 * 匹配所有以 .log 结尾的文件，文件内容将按“数字\u0026gt;大写字母\u0026gt;小写字母”的字符排序规则展示。 cat *.log 读取当前目录下以特定前缀开头的日志文件：通过“前缀+通配符”的组合，精准匹配目标文件（如下例匹配以 20250928 开头的所有日志文件）。 cat 20250928*.log 合并多个文件内容到新文件：使用重定向符号 \u0026gt;，将多个文件的内容合并后写入指定新文件（注意：\u0026gt; 会覆盖目标文件的原有内容）。 cat test1.log test2.log \u0026gt; merged.log 追加文件内容到已有文件：若需保留目标文件原有内容，改用重定向符号 \u0026gt;\u0026gt;，将新文件内容追加到目标文件末尾。 cat file3.txt \u0026gt;\u0026gt; merged.txt # 将 file3.txt 的内容追加到 merged.txt 末尾 从标准输入（键盘）读取内容并保存到文件：不指定源文件时，cat 会默认从键盘读取输入，输入完成后按 Ctrl+d 结束并保存内容（适合快速创建短文本文件）。 cat \u0026gt; newfile.txt # 输入内容后按 Ctrl+d 保存 读取gz压缩文件：用zcat 命令可以直接读取gz压缩文件。 zcat test.log.gz 选项 功能说明 -n 显示内容时，为每一行加上行号（包括空行） -b 显示内容时，为非空行加上行号（忽略空行） -s 压缩连续的空行为一个空行（去除多余空行） -E 在每行末尾显示 $ 符号（方便区分行尾和空格） -T 将制表符（Tab）显示为 ^I（方便查看隐藏的制表符） -v 显示非打印字符（除换行和制表符外） 如果想要实时监测日志更新，可以使用tail -f命令查看\n如果单个日志文件过大，可以使用less命令分页查看\ncat是查询日志中最常用的命令，我们可以通过管道（|）将cat读取的内容传递给其他工具处理：\n查看文件并搜索关键词（结合grep）：\ncat test.log | grep \u0026#34;ERROR\u0026#34; # 从 test.log 中筛选包含 ERROR 的行 查看文件并排序（结合sort）：\ncat test.log | sort -n # 按数字排序文件内容 tail 命令 # 查看 test.log 文件的末尾内容（默认显示最后10行） tail test.log 实时跟踪当前目录下所有日志文件的更新 适用于持续写入的日志监控 tail -f *.log 增强版实时跟踪当日志文件被轮转切割（如按日期归档）时 自动跟随新的文件继续监控 tail -F *.log less 命令 # 打开文件：\nless test.log # 分页查看 test.log 也可结合管道接收其他命令的输出：\ncat *.log | less # 分页查看所有 .log 文件的合并内容 cat test.log | grep \u0026#34;error\u0026#34; | less # 分页查看包含 error 的日志行 核心操作快捷键：\n翻页： 鼠标滚轮：上下滑动。 空格 / f：向下翻一屏。 b：向上翻一屏。 回车键 / 方向键 ↑/↓：逐行上下滚动。 跳转： g：跳转到文件开头。 G：跳转到文件结尾。 50g：跳转到第 50 行。 搜索： /关键词：从当前位置向下搜索（如 /ERROR 搜索 ERROR）。 ?关键词：从当前位置向上搜索。 按 n：跳转到下一个匹配结果。 按 N：跳转到上一个匹配结果。 其他： q：退出查看。 v：在当前位置用默认编辑器打开文件（方便修改）。 Ctrl+f：向前滚动一屏（同空格）。 Ctrl+b：向后滚动一屏（同 b）。 想要在less中保留显示jq的语法高亮，可以在jq命令中加上-C参数，在less中加上-R参数 示例：cat test.log | jq -C | less -R\n🎯 筛选日志的命令 # jq 及其常用函数 # 基本取值 # .：获取整个JSON日志对象，用于格式化输出\n示例：cat test.log | jq '.'（格式化打印日志）\n-c开启紧凑模式，单行显示日志\n.key：提取指定字段值（如日志中的timestamp、level）\n示例：cat test.log | jq '.level'（提取所有日志级别）\n.array[索引]：获取数组类型字段的指定元素（如日志中的tags[0]）\n示例：cat test.log | jq '.tags[1]'（提取tags数组的第二个元素）\n.key1.key2：嵌套取值（如日志中user.name）\n示例：cat test.log | jq '.request.url'（提取请求URL）\n条件过滤 # select(条件)：筛选满足条件的日志条目\n示例：cat test.log | jq 'select(.level == \u0026quot;ERROR\u0026quot;)'（筛选所有错误级别日志）\nhas # 功能：判断对象是否包含指定字段（日志中常用于检查是否存在某属性）\n用法：select(has(\u0026quot;字段名\u0026quot;))\n示例1：筛选包含stackTrace字段的错误日志\ncat test.log | jq 'select(has(\u0026quot;stackTrace\u0026quot;) and .level == \u0026quot;ERROR\u0026quot;)'\n示例2：排除包含stackTrace字段的错误日志\ncat test.log | jq 'select(has(\u0026quot;stackTrace\u0026quot;) == false)'\ncontains # 功能：判断数组/字符串是否包含指定元素/子串\n用法：select(.字段 | contains(目标值))\n示例1（字符串包含）：筛选message含\u0026quot;timeout\u0026quot;的日志\ncat test.log | jq 'select(.message | contains(\u0026quot;timeout\u0026quot;))'\n示例2（字符串包含）：排除message含\u0026quot;timeout\u0026quot;的日志\ncat test.log | jq 'select(.message | contains(\u0026quot;timeout\u0026quot;) == false)'\n示例3（数组包含）：筛选tags数组含\u0026quot;payment\u0026quot;的日志\ncat test.log | jq 'select(.tags | contains([\u0026quot;payment\u0026quot;]))'\nindex # 功能：返回子串在字符串中的起始索引（不存在返回null），用于精确位置判断\n用法：select(.字段 | index(子串) != null)\n示例：筛选message中\u0026quot;error\u0026quot;出现在第5位及以后的日志\ncat test.log | jq 'select(.message | index(\u0026quot;error\u0026quot;) \u0026gt;= 5)'\ntest 正则表达式 # 功能：判断字符串是否匹配正则表达式\n用法：select(.字段 | test(正则表达式))\n示例：筛选name中第一个字为\u0026quot;李\u0026quot;的日志\ncat test.log | jq 'select(.name | test(\u0026quot;^李\u0026quot;))'\nsort/uniq/group_by(数组) # sort_by(字段)：按指定字段排序日志\n示例：按timestamp升序排列日志\ncat test.log | jq '.[] | sort_by(.timestamp)'\nunique：数组去重（常用于提取唯一值）\n示例：提取所有不重复的userId\ncat test.log | jq '.[] | .userId' | jq -s 'unique'\ngroup_by(字段)[]：按字段分组（统计前常用）\n示例：按level分组统计日志\ncat test.log | jq '.[] | group_by(.level)'\n多行独立json对象(不是数组) # 功能：将多行独立json对象(不是数组)转换成数组\n示例：不是数组需要使用-s和.[]\ncat test.log | jq -s 'sort_by(.timestamp) | .[]'\nlength # 功能：返回数组的长度或者字符串长度\n用法：\ncat test.log | jq '.[] | length' 获取日志数量\ncat test.log | jq '.message | length' 获取日志message字符数\n更改字段名 # 功能：将字段名改为其他名称 用法： # 示例：将字段code改为uid，字段cardnumber改为ID cat test.log | jq \u0026#39;with_entries(if .key == \u0026#34;code\u0026#34; then .key = \u0026#34;uid\u0026#34; elif .key == \u0026#34;cardnumber\u0026#34; then .key = \u0026#34;ID\u0026#34; else . end)\u0026#39; 📊 统计分析日志的命令 # sort/uniq # 功能：搭配使用对数组进行排序和去重，常用于统计日志数据 用法：cat test.log | jq '.level' | sort | uniq wc # 功能：统计日志行数、单词数、字节数 用法：cat test.log | wc -l 获取日志行数 awk # awk 是强大的文本处理工具,特别适合对日志进行字段提取、条件筛选和统计计算。它按行处理文本,并可基于分隔符将每行拆分为字段。\n基本语法:\nawk \u0026#39;pattern {action}\u0026#39; file # pattern 为筛选条件,action 为处理动作 按分隔符提取字段:假设日志按空格分隔,提取第1列和第3列\ncat test.log | awk \u0026#39;{print $1, $3}\u0026#39; # $1表示第1列,$3表示第3列 指定分隔符:使用 -F 指定分隔符(如逗号、冒号等)\ncat test.log | awk -F \u0026#39;,\u0026#39; \u0026#39;{print $2}\u0026#39; # 按逗号分隔,提取第2列 条件筛选:仅处理满足条件的行\ncat test.log | awk \u0026#39;$3 \u0026gt; 100 {print $0}\u0026#39; # 筛选第3列数值大于100的行 cat test.log | awk \u0026#39;/ERROR/ {print $0}\u0026#39; # 筛选包含ERROR的行 统计计数:使用内置变量 NR(行号)和 NF(字段数)\ncat test.log | awk \u0026#39;END {print NR}\u0026#39; # 统计总行数 cat test.log | awk \u0026#39;{sum+=$3} END {print sum}\u0026#39; # 累加第3列数值并输出总和 格式化输出:使用 printf 自定义输出格式\ncat test.log | awk \u0026#39;{printf \u0026#34;Level: %s, Message: %s\\n\u0026#34;, $1, $2}\u0026#39; 多条件组合:结合逻辑运算符(\u0026amp;\u0026amp;、||、!)\ncat test.log | awk \u0026#39;$1==\u0026#34;ERROR\u0026#34; \u0026amp;\u0026amp; $3\u0026gt;50 {print $0}\u0026#39; # 第1列为ERROR且第3列大于50 常用内置变量:\n变量 说明 $0 当前行的完整内容 $1, $2, $n 当前行的第1、2、n列 NR 当前处理的行号 NF 当前行的字段总数 FS 字段分隔符(默认为空格) OFS 输出字段分隔符 💼 实战实例分析 # 查询非 info 日志 # 通过contains排除部分无关日志\ncat test.log | jq \u0026#39;select(.level!=\u0026#34;info\u0026#34; and (.msg | contains(\u0026#34;config\u0026#34;) == false))\u0026#39; 查询指定用户日志 # cat test.log | jq \u0026#39;select(.uid == 123)\u0026#39; cat test.log | jq \u0026#39;select(.uid == 123 and (.message | contains(\u0026#34;succ\u0026#34;)))\u0026#39; 统计错误日志数 # cat test.log | jq \u0026#39;select(.level==\u0026#34;error\u0026#34;) | .request_id\u0026#39; | sort | uniq | wc -l ","date":"2025-11-04","externalUrl":null,"permalink":"/posts/log/","section":"文章","summary":"结合cat/grep/jq/sort/uniq/awk等工具进行日志查询与分析","title":"高效日志查询分析工具实战指南","type":"posts"},{"content":"","date":"2025-11-04","externalUrl":null,"permalink":"/tags/%E8%BF%90%E7%BB%B4/","section":"Tags","summary":"","title":"运维","type":"tags"},{"content":"","date":"2025-10-28","externalUrl":null,"permalink":"/tags/ai%E7%BC%96%E7%A8%8B/","section":"Tags","summary":"","title":"AI编程","type":"tags"},{"content":"","date":"2025-10-28","externalUrl":null,"permalink":"/tags/%E5%A4%A7%E6%A8%A1%E5%9E%8B/","section":"Tags","summary":"","title":"大模型","type":"tags"},{"content":"我们使用一个Github开源工具\u0026quot;ZCF\u0026quot;，安装步骤非常简单，只需要打开命令行输入\nnpx zcf 即可完成安装，选择语言为简体中文即可完成安装\n如果你的电脑没有安装Claude Code，那么选择1，根据指示即可完成安装。\n在命令行输入claude，可以正常打开Claude Code界面说明安装成功。\n我们想要替换Claude Code的关键步骤在\u0026quot;选择API配置模式\u0026quot;，ZCF帮我们简化了配置Claude Code API的步骤，这里我们选择\u0026quot;自定义API配置\u0026quot;，然后随便输入一个配置名字，认证类型选择API Key，然后输入我们的API对应URL和KEY即可。目前ZCF内置了Kimi和智谱GLM模型的URL配置，如果想使用这几个官方的API的话，只需要配置KEY就可以。如果想使用七牛云的API，我们的URL应该填https://openai.qiniu.com，想要通过七牛云使用claude-4.5-sonnet模型的话，模型填claude-4.5-sonnet即可，然后选择将此配置设为默认配置。\n如果想使用智谱GLM模型，点击购买套餐，注册新用户会赠送Token。\n添加完API，接下来可以按照ZCF的工具流自定义设置一些提高效果的模版和工具，这里建议可以配置一下MCP服务器，推荐安装Context7，通过获取最新的库和代码示例，可以有效提高AI代码能力。\n配置完成之后，就可以开始使用Claude Code了，常规用法是在命令行输入claude，即可打开Claude Code了。\n除此之外，如果使用VS Code开发，可以下载插件Claude Code for VS Code，和命令行的Claude Code共享同一套配置。\n","date":"2025-10-28","externalUrl":null,"permalink":"/posts/ai/claudecode/","section":"文章","summary":"配置Claude Code API及使用模版和MCP","title":"一键配置Claude Code替换国产API","type":"posts"},{"content":"点击下面链接领取1000万Token（无需代理访问claude-4.5-sonnet）\n七牛云AI推理》立即领取\n新用户还可领取300万免费Token资源包，支持多家大模型，包括ChatGPT、Deepseek、豆包、Kimi、通义千问以及Claude。\n具体模型可参考模型广场，其中claude-4.5-sonnet模型未显示在模型广场中，实测是可用的，只需要指定模型参数为claude-4.5-sonnet即可。\n领取Token后，可以在七牛云的控制台获取Key\n在七牛云中可以使用多家大模型，需要指定正确的模型名字，参考下图（模型广场中点击具体模型）\n无论是代码中使用API，还是对接Claude Code都是可以正常使用的，并且通过Request调用API还配套有Python、JS等示例代码。 不过在实际开发中，我们更习惯使用OpenAI的SDK，也都是兼容的，只是需要注意不同API的url不一样，使用API主要有下面三种情况：\n# 通过Request请求调用API https://openai.qiniu.com/v1/chat/completions # 通过OpenAI SDK调用 https://openai.qiniu.com/v1 # 在Claude Code中使用（即兼容 Anthropic 接口） https://openai.qiniu.com 在Python代码中通过OpenAI的SDK使用claude-4.5-sonnet模型完成简单会话，示例代码如下：\nfrom openai import OpenAI client = OpenAI( api_key = \u0026#34;把你的API_KEY复制到这里(上文有获取API_KEY的地址)\u0026#34;, base_url = \u0026#34;https://openai.qiniu.com/v1\u0026#34;, ) messages = [ { \u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;hello\u0026#34;, } ] completion = client.chat.completions.create( model=\u0026#34;claude-4.5-sonnet\u0026#34;, messages=messages, ) print(completion) 想要在Claude Code中使用API可参考\n一键配置Claude Code替换国产API 718 字 大模型 AI编程 配置Claude Code API及使用模版和MCP ","date":"2025-10-27","externalUrl":null,"permalink":"/posts/ai/qiniu/","section":"文章","summary":"支持无代理访问claude-4.5-sonnet","title":"七牛云免费领取1000万大模型Token","type":"posts"},{"content":"","date":"2025-10-27","externalUrl":null,"permalink":"/tags/%E7%BE%8A%E6%AF%9B/","section":"Tags","summary":"","title":"羊毛","type":"tags"},{"content":"","date":"2025-10-24","externalUrl":null,"permalink":"/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":" 安装命令 # # 安装zsh sudo apt install zsh # 下载oh my zsh git clone git@github.com:ohmyzsh/ohmyzsh.git ~/.oh-my-zsh git clone https://github.com/ohmyzsh/ohmyzsh.git ~/.oh-my-zsh # 复制oh my zsh配置 cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc # 设置默认终端为zsh chsh -s /bin/zsh # 打开zsh zsh # 下载补全插件 git clone git@github.com:zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions git clone https://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions # 编辑配置文件，修改 plugins=(git zsh-autosuggestions) nano ~/.zshrc # 重启zsh source .zshrc 参考 oh-my-zsh安装\n","date":"2025-10-24","externalUrl":null,"permalink":"/posts/zsh/","section":"文章","summary":"","title":"oh-my-zsh安装","type":"posts"},{"content":"","date":"2025-10-20","externalUrl":null,"permalink":"/tags/acm/","section":"Tags","summary":"","title":"ACM","type":"tags"},{"content":"本文为SIT2025新生赛Python版本题解，仅供参考，如有疑问可群里艾特\u0026quot;木子木\u0026quot;\n本文标题均可点击链接跳转题目\n比赛链接\nA 欢迎来到SIT算法竞赛社 # _ = input() print(sum(list(map(int, input().split())))) B 寻找平衡日 # n = int(input()) nums = list(map(int, input().split())) total = sum(nums) prefix = [sum(nums[:i]) for i in range(len(nums) + 1)] for i in range(1, n + 1): left_sum = prefix[i - 1] right_sum = total - prefix[i] if left_sum == right_sum: print(i) exit(0) print(-1) C 编程友谊赛 # from collections import defaultdict from math import comb count = defaultdict(int) for _ in range(int(input())): s = input().strip() freq = \u0026#39;\u0026#39;.join(sorted(f\u0026#34;{c}:{s.count(c)}\u0026#34; for c in set(s))) count[freq] += 1 print(sum(comb(v, 2) for v in count.values())) D 队伍分配问题 # n = int(input()) nums = list(map(int, input().split())) print(\u0026#34;true\u0026#34; if len(set(nums)) == n else \u0026#34;false\u0026#34;) E 能做到吗 # x, y, n, t, k = map(int, input().split()) a = list(map(int, input().split())) b = list(map(int, input().split())) dp = [0] * (y + 1) for i in range(n): for j in range(y, a[i] - 1, -1): dp[j] = max(dp[j], dp[j - a[i]] + b[i]) if x + dp[y] // t \u0026gt;= k: print(\u0026#34;YES\u0026#34;) else: print(\u0026#34;NO\u0026#34;) F 社长选举 # 暴力解法(TLE)\nn = int(input()) nums = list(map(int, input().split())) max_cnt, best_k = -1, 0 for k in range(n): count = 0 for i in range(n): if nums[i] \u0026lt;= (i + k) % n: count += 1 if count \u0026gt; max_cnt: max_cnt, best_k = count ,k print(best_k) J 你的故事你来写 # print(\u0026#34;你的故事你来写\u0026#34;) ","date":"2025-10-20","externalUrl":null,"permalink":"/posts/acm/sit/contest2025/","section":"文章","summary":"Python题解","title":"SIT算法竞赛社2025年新生赛","type":"posts"},{"content":"","date":"17 October 2025","externalUrl":null,"permalink":"/en/tags/interview/","section":"Tags","summary":"","title":"Interview","type":"tags"},{"content":" 为什么选用Python写算法题 # 毋庸置疑，Python是主流编程语言中语法最简洁的，再加上python内置函数丰富，熟练之后写算法题可以大幅减少代码量和用时。\n面向ICPC/CCPC的同学出于性能考虑往往会采用C++作为首选编程语言，但是在普通比赛或者面试中碰到的编程题并不会卡编程语言的时间，只要算法的时间复杂度达标即可通过，所以当习惯使用Python写算法题后可以提高效率，并且能够体会到Python的简洁之道。\n常用技巧 # Counter # c = Counter(list) for k, v in c.items(): print(k, v) for k in c.keys(): print(k, c[k]) for v in c.values(): print(v) for i in c.elements(): print(i) dict = defaultdict(list) #默认为空列表 dict = defaultdict(int) #默认为0 for k, v in dict: print(k, v) 推导式 # # 列表推导式 newnames = [name.upeer() for name in names if len(name) \u0026gt; 3] multiples = [i for i in range(30) if 3 % 3 == 0] # 字典推导式 newdict = {key:len(key) for key in listdemo} dic = {x:x**2 for x in (2, 4, 6)} # 集合推导式 newset = {i**2 for i in (1, 2, 3)} a = {x for x in \u0026#39;abracadabra\u0026#39; if x not in \u0026#39;abc\u0026#39;} 自定义排序 # data = [(\u0026#39;red\u0026#39;, 5), (\u0026#39;blue\u0026#39;, 1), (\u0026#39;yellow\u0026#39;, 8), (\u0026#39;black\u0026#39;, 0)] data.sort(key=lambda r: r[1]) # 自定义排序函数 def custom_sort_key(item): # 根据item的某种特性生成排序键 return item[\u0026#39;priority\u0026#39;] # 假设我们按照优先级排序 # 示例数据 items = [ {\u0026#39;name\u0026#39;: \u0026#39;A\u0026#39;, \u0026#39;priority\u0026#39;: 2}, {\u0026#39;name\u0026#39;: \u0026#39;B\u0026#39;, \u0026#39;priority\u0026#39;: 1}, {\u0026#39;name\u0026#39;: \u0026#39;C\u0026#39;, \u0026#39;priority\u0026#39;: 3}, ] # 使用sorted()进行排序 sorted_items = sorted(items, key=custom_sort_key) print(sorted_items) # 输出排序后的列表 # 使用list.sort()进行排序 items.sort(key=custom_sort_key) print(items) # 输出就地排序后的列表 记忆化函数 # from functools import cache # 在函数前使用@cache即可完成记忆化函数 二分查找函数 # import bisect my_list = [1, 3, 5, 5, 7, 9] # 使用 bisect_left 查找目标元素的插入位置 left_index = bisect.bisect_left(my_list, 5) print(\u0026#34;插入位置（最左边）:\u0026#34;, left_index) # 使用 bisect_right 查找目标元素的插入位置 right_index = bisect.bisect_right(my_list, 5) print(\u0026#34;插入位置（最右边）:\u0026#34;, right_index) 插入位置（最左边）: 2 插入位置（最右边）: 4 # 详细使用： bisect.bisect_left(a, x, lo=0, hi=len(a), *, key=None) 在列表a的[lo,hi)子集块中查找第一个大于等于x的下标 bisect.bisect_right(a, x, lo=0, hi=len(a), *, key=None) 在列表a的[lo,hi)子集块中查找第一个大于x的下标 # 自定义比较规则 my_list = [1, 3, 5, 7, 9] index = bisect.bisect_left(my_list, 5, key=lambda x: x * x) # 应用示例 成绩分组 def grade(score, breakpoints=[60, 70, 80, 90], grades=\u0026#39;FDCBA\u0026#39;): i = bisect(breakpoints, score) return grades[i] [grade(score) for score in [33, 99, 77, 70, 89, 90, 100]] [\u0026#39;F\u0026#39;, \u0026#39;A\u0026#39;, \u0026#39;C\u0026#39;, \u0026#39;C\u0026#39;, \u0026#39;B\u0026#39;, \u0026#39;A\u0026#39;, \u0026#39;A\u0026#39;] 通用模板 # 此模版面向ICPC/CCPC竞赛，如果对于python代码有性能需求可以考虑使用模板中封装的IO模块\nimport sys, os, math from math import gcd, sqrt from bisect import bisect_left, bisect_right from io import BytesIO, IOBase from collections import Counter, defaultdict, deque from functools import lru_cache, reduce, cmp_to_key from itertools import accumulate, combinations, permutations, product from heapq import nsmallest, nlargest, heapify, heappop, heappush BUFSIZE = 8192 def solve(): n = 1 def main(): tt = 1 tt = II() for _ in range(tt): solve() def qmi(a, b, p): res = 1 % p while b: if b \u0026amp; 1: res = (res * a) % p a = a * a % p b \u0026gt;\u0026gt;= 1 return res def comb(n, r): return factorial(n) // (factorial(r) * factorial(n - r)) if n \u0026gt;= r else 0 def I(): return input() def II(): return int(input()) def MII(): return map(int, input().split()) def LMII(): return list(map(int, input().split())) def YES(t = 1): print(\u0026#34;YES\u0026#34; if t else \u0026#34;NO\u0026#34;) def NO(t = 1): YES(t ^ 1) def Yes(t = 1): print(\u0026#34;Yes\u0026#34; if t else \u0026#34;No\u0026#34;) def No(t = 1): Yes(t ^ 1) def yes(t = 1): print(\u0026#34;yes\u0026#34; if t else \u0026#34;no\u0026#34;) def no(t = 1): yes(t ^ 1) class FastIO(IOBase): newlines = 0 def __init__(self, file): self._fd = file.fileno() self.buffer = BytesIO() self.writable = \u0026#34;x\u0026#34; in file.mode or \u0026#34;r\u0026#34; not in file.mode self.write = self.buffer.write if self.writable else None def read(self): while True: b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE)) if not b: break ptr = self.buffer.tell() self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr) self.newlines = 0 return self.buffer.read() def readline(self): while self.newlines == 0: b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE)) self.newlines = b.count(b\u0026#34;\\n\u0026#34;) + (not b) ptr = self.buffer.tell() self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr) self.newlines -= 1 return self.buffer.readline() def flush(self): if self.writable: os.write(self._fd, self.buffer.getvalue()) self.buffer.truncate(0), self.buffer.seek(0) class IOWrapper(IOBase): def __init__(self, file): self.buffer = FastIO(file) self.flush = self.buffer.flush self.writable = self.buffer.writable self.write = lambda s: self.buffer.write(s.encode(\u0026#34;ascii\u0026#34;)) self.read = lambda: self.buffer.read().decode(\u0026#34;ascii\u0026#34;) self.readline = lambda: self.buffer.readline().decode(\u0026#34;ascii\u0026#34;) sys.stdin = IOWrapper(sys.stdin) input = lambda: sys.stdin.readline().rstrip() if __name__ == \u0026#34;__main__\u0026#34;: main() ","date":"2025-10-17","externalUrl":null,"permalink":"/posts/acm/python/","section":"文章","summary":"","title":"Python算法题常用技巧","type":"posts"},{"content":"","date":"2025-10-17","externalUrl":null,"permalink":"/tags/%E9%9D%A2%E8%AF%95/","section":"Tags","summary":"","title":"面试","type":"tags"},{"content":" ACM输入输出模板 # 很多朋友在求职的笔试和面试中，因为对ACM模式的输入输出不熟悉，导致无法完成算法题。输入输出是写算法题的基础，大家可以根据自己的首选语言进行\u0026quot;背诵\u0026quot;。\n下面，针对常见的ACM输入与输出，给大家总结了模板写法，包括了C++、Python、Go、Java、JavaScript等主流编程语言。示例代码均为“A+B”问题，题目描述为输入A和B两个数，要求输出A+B。\n1.多组数据，每组第一行为n，之后输入n行两个整数 # 练习题：A+B问题I\n输入：第一行是一个整数N，表示后面会有N行a和b，通过空格隔开。\nC++ # #include\u0026lt;iostream\u0026gt; using namespace std; int main() { int n, a, b; while (cin \u0026gt;\u0026gt; n) { while (n--) { cin \u0026gt;\u0026gt; a \u0026gt;\u0026gt; b; cout \u0026lt;\u0026lt; a + b \u0026lt;\u0026lt; endl; } } } Python # n = int(input()) for _ in range(n): a, b = map(int, input().split()) print(a + b) Go # package main import \u0026#34;fmt\u0026#34; func main(){ var n, a, b int for { _, err := fmt.Scanf(\u0026#34;%d\u0026#34;, \u0026amp;n) if err != nil { break } for n \u0026gt; 0 { _, err := fmt.Scanf(\u0026#34;%d %d\u0026#34;, \u0026amp;a, \u0026amp;b) if err != nil { break } fmt.Println(a + b) n -- } } } Java # import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); while (scanner.hasNext()) { int n = scanner.nextInt(); while (n -- \u0026gt; 0) { int a = scanner.nextInt(); int b = scanner.nextInt(); System.out.println(a + b); } } } } JavaScript # const readline = require(\u0026#39;readline\u0026#39;); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); let n, count = 0; rl.on(\u0026#39;line\u0026#39;, (input) =\u0026gt; { if (!n) { n = +line; } else { const [a, b] = line.split(\u0026#39; \u0026#39;).map(Number); console.log(a + b)… if (++ count \u0026gt;= n) rl.close(); } }) 1.多行输入，每行两个整数 # 练习题：A+B问题II\n输入：包含一系列的a和b对，通过空格隔开。一对a和b占一行。\nC++ # #include\u0026lt;iostream\u0026gt; using namespace std; int main() { int a, b; while(cin \u0026gt;\u0026gt; a \u0026gt;\u0026gt; b) cout \u0026lt;\u0026lt; a + b \u0026lt;\u0026lt; endl; } Python # while True: try: a, b = map(int, input().split()) print(a + b) except: break Go # package main import \u0026#34;fmt\u0026#34; func main(){ var a, b int for { _, err := fmt.Scanf(\u0026#34;%d %d\u0026#34;, \u0026amp;a, \u0026amp;b) if err != nil { break } fmt.Println(a + b) } } Java # import java.util.Scanner; public class Main{ public static void main(String[] args){ Scanner in = new Scanner(System.in); while(in.hasNextInt()){ int a = in.nextInt(); int b = in.nextInt(); System.out.println( a + b ); } } } JavaScript # const readline = require(\u0026#39;readline\u0026#39;); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.on(\u0026#39;line\u0026#39;, (input) =\u0026gt; { const [a, b] = input.split(\u0026#39; \u0026#39;).map(Number); console.log(a + b); }) ","date":"2025-10-13","externalUrl":null,"permalink":"/posts/acm/io/","section":"文章","summary":"","title":"ACM输入输出模板","type":"posts"},{"content":"","date":"13 October 2025","externalUrl":null,"permalink":"/en/tags/programing/","section":"Tags","summary":"","title":"Programing","type":"tags"},{"content":" 使用Windows如何配置SSH连接Github # 在往github上push项目的时候，如果走https的方式，每次都需要输入账号密码，非常麻烦。而采用ssh的方式，就不再需要输入，只需要在github自己账号下配置一个ssh key即可。\n一、检查本地主机是否已经存在ssh key # win + R打开运行 ，输入cmd打开命令行\n输入以下命令\ncd .ssh dir 如果存在id_rsa 和 id_rsa.pub文件，如果存在，说明已经有SSH Key。下图即表示存在。\n如果存在，则直接执行第三步。\n二、生成ssh key # 如果不存在，使用下列命令生成。\nssh-keygen -t rsa -C \u0026#34;xxx@xxx.com\u0026#34; // 将 \u0026#34;xxx@xxx.com\u0026#34; 替换为你自己GitHub的邮箱地址 一直回车即可。\n三、获取ssh key公钥内容（id_rsa.pub） # cd .ssh type id_rsa.pub // Windows命令行 cat id_rsa.pub // Linux命令行 如下图所示，复制这段内容\n四、Github账号上添加公钥 # 1、点击右上角的头像，进入settings设置，添加SSH Keys。\n2、将刚刚复制的内容添加上去\n五、验证是否设置成功 # ssh -T git@github.com 显示如下则表示设置成功\n设置成功后，即可不需要账号密码clone和push代码\n注意之后在clone仓库的时候要使用ssh的url，而不是https！\n","date":"2025-09-27","externalUrl":null,"permalink":"/posts/git/gitssh/","section":"文章","summary":"使用Windows如何配置SSH连接Github","title":"GitHub SSH配置","type":"posts"},{"content":"","date":"2025-09-27","externalUrl":null,"permalink":"/tags/%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/","section":"Tags","summary":"","title":"环境配置","type":"tags"},{"content":" 基本介绍 # 在使用Git进行代码仓库管理的时候，我们不可避免需要提交Commit，即每次提交代码需要标注本次进行了哪些修改，方便让合作者快速阅览了解开发内容，提高合作效率。目前Angular规范是最广泛的Git Commit规范方法，本文参考下列文档，使用中文编写新手友好的实用教程。 Augular Github规范 参考博客\n规范内容 # Commit信息格式 # \u0026lt;type\u0026gt;(\u0026lt;scope\u0026gt;): \u0026lt;subject\u0026gt; \u0026lt;BLANK LINE\u0026gt; \u0026lt;body\u0026gt; \u0026lt;BLANK LINE\u0026gt; \u0026lt;footer\u0026gt; 其中type(提交类型)和subject(简短描述)是必须项，scope(影响范围)是可选项 第一行为Header(标题)，第二部分为Body(正文)，最后部分为Footer(脚注) 一般实际开发中主要遵循标题规范，即 type(scope): subject，示例如下\nfeat(第二课堂): 显示第二课堂学分 fix(第二课堂): 修复第二课堂学分显示问题 docs: Readme中增加新人环境配置教程 test: 教务系统API单元测试 Type (提交类型) # 常用的Type类型如下：\nfeat: 新功能 fix: 修复 bug docs: 文档修改 style: 不影响代码逻辑的格式调整 refactor: 既不是新功能也不是修复 bug 的代码重构 perf: 性能优化 test: 添加或修改测试代码 chore: 构建过程或辅助工具的变动 Scope (影响范围) # 指定提交影响的模块或文件范围，例如：\n第二课堂 教务系统 Subject (简短描述) # 建议使用中文 简单描述修改内容，使用陈述句，例如\u0026quot;修复了xx问题\u0026quot; 结尾不要使用句号或者其他标点符号 规范Git Commit工具 # 使用VSCode的用户可以安装git-commit-plugin插件，使用效果如下： ","date":"2025-09-24","externalUrl":null,"permalink":"/posts/git/gitcommit/","section":"文章","summary":"介绍最广泛的Angular Git Commit提交规范","title":"Git Commit规范","type":"posts"},{"content":"","date":"2025-09-24","externalUrl":null,"permalink":"/tags/%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83/","section":"Tags","summary":"","title":"技术规范","type":"tags"},{"content":"","date":"6 September 2025","externalUrl":null,"permalink":"/en/tags/blog-setup/","section":"Tags","summary":"","title":"Blog Setup","type":"tags"},{"content":"","date":"2025-09-06","externalUrl":null,"permalink":"/tags/hugo/","section":"Tags","summary":"","title":"Hugo","type":"tags"},{"content":"","date":"6 September 2025","externalUrl":null,"permalink":"/en/tags/tech/","section":"Tags","summary":"","title":"Tech","type":"tags"},{"content":"","date":"2025-09-06","externalUrl":null,"permalink":"/tags/%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/","section":"Tags","summary":"","title":"博客搭建","type":"tags"},{"content":" 前言 # 本文记录本站的完整搭建过程：使用 Hugo 作为静态站点生成器，搭配 Blowfish 主题，最终托管在 GitHub Pages 上，并借助 GitHub Actions 实现「推送即发布」的自动化部署。\n整体技术栈如下：\nHugo：基于 Go 的静态站点生成器，构建速度快。 Blowfish：功能丰富、可高度自定义的 Hugo 主题。 GitHub Pages：免费的静态网页托管服务。 GitHub Actions：自动构建并部署站点的 CI 工具。 环境准备 # 安装 Go # Hugo 本身基于 Go 开发。若使用 go install 方式安装 Hugo，需要先配置好 Go 环境。 可前往 Go 官网 下载安装，安装后验证：\ngo version 安装 Hugo # 在已有 Go 环境的前提下，直接通过 go install 安装指定版本的 Hugo：\ngo install github.com/gohugoio/hugo@v0.151.0 注意：Blowfish 主题依赖 Hugo 的 extended 版本（支持 SCSS 编译）。 若使用包管理器安装，请确认安装的是 extended 版本。\n安装完成后验证版本：\nhugo version 创建站点 # 在工作目录下新建一个 Hugo 站点：\nhugo new site muzimu cd muzimu git init 安装 Blowfish 主题 # Blowfish 推荐使用 Git Submodule 的方式引入主题，便于后续更新。\n引入主题 # git submodule add -b main https://github.com/nunocoracao/blowfish.git themes/blowfish 执行后会在项目根目录生成 .gitmodules 文件，内容类似：\n[submodule \u0026#34;themes/blowfish\u0026#34;] path = themes/blowfish url = git@github.com:nunocoracao/blowfish.git branch = main 更新主题 # 后续需要升级主题版本时，执行：\ngit submodule update --remote --merge 克隆仓库后初始化子模块 # 在新机器上克隆本仓库后，需要同步并拉取子模块内容：\ngit submodule sync git submodule update --init --remote 配置站点 # Blowfish 的配置文件位于 config/_default/ 目录下，按职责拆分为多个文件， 而非集中在单一的 hugo.toml 中。核心文件如下：\n文件 作用 hugo.toml 站点基础配置（主题、baseURL、语言、分类法等） params.toml 主题参数（外观、布局、功能开关等） languages.zh-cn.toml 中文语言及作者信息 languages.en.toml 英文语言配置 menus.zh-cn.toml 中文导航菜单 markup.toml Markdown 渲染与代码高亮设置 基础配置 # hugo.toml 中需指定主题与站点地址：\ntheme = \u0026#34;blowfish\u0026#34; baseURL = \u0026#34;https://muzimu.github.io/\u0026#34; defaultContentLanguage = \u0026#34;zh-cn\u0026#34; enableRobotsTXT = true enableEmoji = true [taxonomies] tag = \u0026#34;tags\u0026#34; category = \u0026#34;categories\u0026#34; author = \u0026#34;authors\u0026#34; series = \u0026#34;series\u0026#34; 作者信息 # 在 languages.zh-cn.toml 中配置作者头像、昵称与社交链接：\n[params.author] name = \u0026#34;muzimu\u0026#34; image = \u0026#34;img/avatar.jpg\u0026#34; headline = \u0026#34;木子木\u0026#34; links = [ { github = \u0026#34;https://github.com/muzimu\u0026#34; }, { bilibili = \u0026#34;https://space.bilibili.com/448045568\u0026#34; }, ] 更多主题参数说明可参考 Blowfish 官方文档： https://blowfish.page/zh-cn/docs/configuration/#theme-parameters\n创建文章 # 使用 hugo new 基于 archetype 模板创建文章。本站采用「页面包」（Page Bundle） 组织方式，每篇文章独占一个目录，便于管理配图等资源：\nhugo new content content/posts/Hugo/index.md 文章顶部的 Front Matter 示例（YAML 格式）：\n--- title: \u0026#34;文章标题\u0026#34; summary: \u0026#34;文章摘要\u0026#34; isCJKLanguage: true date: 2025-09-06T17:42:06+08:00 tags: - 标签 --- 设置 isCJKLanguage: true 可让 Hugo 正确统计中文字数与阅读时长。\n本地预览 # 启动本地开发服务器，默认地址为 http://localhost:1313/：\nhugo server 若文章仍处于草稿状态（draft: true），需加上 -D 参数才会被渲染：\nhugo server -D 部署到 GitHub Pages # 准备仓库 # 需要两个仓库：\n源码仓库：存放 Hugo 工程源码（如 muzimu/muzimu）。 发布仓库：存放构建产物，即 GitHub Pages 站点（如 muzimu/muzimu.github.io）。 配置自动部署 # 在源码仓库根目录创建 .github/workflows/hugo.yaml，每次向 main 分支推送时， GitHub Actions 会自动构建并将 public/ 目录发布到目标仓库：\nname: github pages on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: submodules: true # 拉取 Blowfish 主题子模块 fetch-depth: 0 # 获取完整历史，供 .GitInfo / .Lastmod 使用 - name: Setup Hugo uses: peaceiris/actions-hugo@v2 with: hugo-version: \u0026#39;latest\u0026#39; extended: true # 使用 extended 版本 - name: Build run: hugo --minify # 构建并压缩静态资源 - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: external_repository: muzimu/muzimu.github.io personal_token: ${{ secrets.PERSONAL_TOKEN }} publish_dir: ./public publish_branch: main 配置访问令牌 # 由于要发布到另一个仓库，不能使用默认的 GITHUB_TOKEN，需要：\n在 GitHub Personal Access Tokens 页面生成一个具有 repo 权限的令牌。 在源码仓库的 Settings → Secrets and variables → Actions 中， 新增一个名为 PERSONAL_TOKEN 的 Secret，值为上一步生成的令牌。 安全提示：令牌等同于账号密码，切勿直接明文写入工作流文件并提交， 否则会被 GitHub 自动吊销且存在泄露风险，务必通过 Secrets 引用。\n启用 Pages 并访问 # 推送代码后，等待 Actions 构建完成，在发布仓库的 Settings → Pages 中确认发布分支，随后即可通过 https://\u0026lt;用户名\u0026gt;.github.io/ 访问站点。\n小结 # 至此，一个基于 Hugo + Blowfish 的个人博客就搭建完成了。日常写作只需新建文章、 本地预览确认效果，再 git push 即可自动发布，整个流程清爽高效。\n","date":"2025-09-06","externalUrl":null,"permalink":"/posts/hugo/","section":"文章","summary":"从零开始，使用 Hugo 静态站点生成器与 Blowfish 主题搭建个人博客，并通过 GitHub Actions 实现自动化部署。","title":"如何使用Hugo\u0026Blowfish搭建博客","type":"posts"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","externalUrl":null,"permalink":"/tools/excel2json/","section":"工具","summary":"","title":"Excel 转 JSON","type":"tools"},{"content":"","externalUrl":null,"permalink":"/tools/jsonesc/","section":"工具","summary":"","title":"JSON 字符串转义/反转义","type":"tools"},{"content":"","externalUrl":null,"permalink":"/en/tags/personal-site/","section":"Tags","summary":"","title":"Personal Site","type":"tags"},{"content":"","externalUrl":null,"permalink":"/tags/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/","section":"Tags","summary":"","title":"个人博客","type":"tags"},{"content":"","externalUrl":null,"permalink":"/tools/","section":"工具","summary":"","title":"工具","type":"tools"},{"content":" Github # muzimu\u0026rsquo;s Github\n我的QQ # QQ号：2404580981\n我的微信 # ","externalUrl":null,"permalink":"/about/","section":"欢迎来到 Blowfish ！","summary":"","title":"关于我","type":"page"},{"content":"","externalUrl":null,"permalink":"/tools/py/","section":"工具","summary":"","title":"汉字转拼音首字母","type":"tools"},{"content":"","externalUrl":null,"permalink":"/resume/","section":"欢迎来到 Blowfish ！","summary":"","title":"我的简历","type":"resume"},{"content":" ","externalUrl":null,"permalink":"/friends/","section":"友情链接","summary":"","title":"友情链接","type":"friends"},{"content":"","externalUrl":null,"permalink":"/tools/jq-playground/","section":"工具","summary":"","title":"在线 jq 工具","type":"tools"}]