[{"content":"","externalUrl":"https://jiangwan.ink/","permalink":"/en/friends/jiangwan/","section":"Friends","summary":"","title":"Jiangwan","type":"friends"},{"content":"","externalUrl":"https://jimyag.com/","permalink":"/en/friends/jimyag/","section":"Friends","summary":"","title":"Jimyag","type":"friends"},{"content":"","externalUrl":"https://liusir521.github.io/","permalink":"/en/friends/liusir/","section":"Friends","summary":"","title":"liusir521","type":"friends"},{"content":"","externalUrl":"https://zhangzqs.cn/","permalink":"/en/friends/zzq/","section":"Friends","summary":"","title":"Zhangzqs","type":"friends"},{"content":"","date":"3 June 2026","externalUrl":null,"permalink":"/en/posts/","section":"Blog","summary":"","title":"Blog","type":"posts"},{"content":"During Mini Program development, the following scenarios all require getting the target page\u0026rsquo;s page path (path) first:\nNavigating to another Mini Program: wx.navigateToMiniProgram Setting the landing page of a share card Generating a Mini Program QR code that points to a specific page Below are two methods for getting a page path. The difference lies in whether you can get the path of someone else\u0026rsquo;s Mini Program:\nMethod Entry Point Scope Official Account admin console New article → Mini Program Any Mini Program Mini Program admin console Mini Program Code → Get Mini Program page code Your own Mini Program Method 1: Get the Page Path of Any Mini Program via the Official Account Admin Console # This method leverages the Mini Program insertion feature of the Official Account article editor, allowing you to get the page path of any Mini Program.\nLog in to the Official Account admin console and go to \u0026ldquo;New Creation\u0026rdquo; → \u0026ldquo;Article\u0026rdquo;.\nClick \u0026ldquo;Mini Program\u0026rdquo; in the toolbar.\nClick \u0026ldquo;Search\u0026rdquo;.\nSearch for the Mini Program whose page path you want to get.\nEnter the Mini Program\u0026rsquo;s page selection screen and click \u0026ldquo;Get more page paths\u0026rdquo;.\nThen follow the \u0026ldquo;Copy Page Link\u0026rdquo; steps below to get the path of any page.\nMethod 2: Get the Page Path of Your Own Mini Program via the Mini Program Admin Console # This method only applies to Mini Programs you have admin access to (i.e. your own Mini Programs).\nLog in to the Mini Program admin console, go to \u0026ldquo;Mini Program Code\u0026rdquo; → \u0026ldquo;Get Mini Program page code\u0026rdquo;, and click \u0026ldquo;Get more page paths\u0026rdquo;.\nCopy Page Link # Regardless of which method above you use, once \u0026ldquo;Get more page paths\u0026rdquo; is enabled, the steps are the same:\nEnter an authorized WeChat ID (for Method 1, a WeChat ID that follows the Official Account; for Method 2, a WeChat ID of a Mini Program project member) and enable it. Use your phone to open the Mini Program and tap the \u0026ldquo;More\u0026rdquo; button in the top-right corner. Tap \u0026ldquo;Copy Page Link\u0026rdquo; in the menu to get the page path of the current page. ","date":"3 June 2026","externalUrl":null,"permalink":"/en/posts/wechat/miniappuri/geturi/","section":"Blog","summary":"Introducing two practical methods for obtaining the page path of any mini program in WeChat mini program development","title":"How to Get the Page Path of a WeChat Mini Program","type":"posts"},{"content":"","date":"3 June 2026","externalUrl":null,"permalink":"/en/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":"/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/","section":"Tags","summary":"","title":"微信小程序","type":"tags"},{"content":" ","date":"3 June 2026","externalUrl":null,"permalink":"/en/","section":"欢迎来到 Blowfish ！","summary":"","title":"欢迎来到 Blowfish ！","type":"page"},{"content":"","date":"23 May 2026","externalUrl":null,"permalink":"/en/tags/game-development/","section":"Tags","summary":"","title":"Game Development","type":"tags"},{"content":" Introduction to the Subscribe Message Feature # Official documentation: https://developers.weixin.qq.com/minigame/dev/guide/open-ability/subscribe-message.html\nIn WeChat Mini Games / WeChat Mini Programs, after a user subscribes to messages, actively pushed messages will be delivered to the user via the \u0026ldquo;Service Notifications\u0026rdquo; channel.\nThere are three types of subscribe messages: one-time subscribe messages, social interaction reminders, and game update reminders.\nOne-Time Subscribe Messages # Client Side # Subscribe to Messages # Official documentation: https://developers.weixin.qq.com/minigame/dev/api/open-api/subscribe-message/wx.requestSubscribeMessage.html\n// In mini games, this can only be used normally within the callbacks of the following APIs, // i.e., in response to a user\u0026#39;s tap action: wx.onTouchEnd wx.showModal wx.showActionSheet Note: For one-time subscribe messages, after subscribing once (i.e., calling wx.requestSubscribeMessage() once), the user can only receive one message (i.e., the cumulative number of receivable messages +1).\nTo push multiple messages, developers have the following two options:\nOption 1: Guide players to check \u0026ldquo;Always keep the above choice, don\u0026rsquo;t ask again\u0026rdquo;, and silently trigger the subscription via the relevant button on each login.\nOption 2: Explicitly add a button to increase the subscribe count. When the user taps the button, the subscribe count can be increased (refer to the WeChat Mini Program: Huyou).\nQuery Subscribe Message Status # Official documentation: https://developers.weixin.qq.com/minigame/dev/api/open-api/setting/wx.getSetting.html\nwx.getSetting() // Pass withSubscriptions to also obtain the user\u0026#39;s subscribe message status. // Note: It only returns subscribe messages for which the user has checked // \u0026#34;Always keep the above choice, don\u0026#39;t ask again\u0026#34; in the subscription panel. Server Side # Send Subscribe Messages # Official documentation: https://developers.weixin.qq.com/minigame/dev/api-backend/subscribe-message/api_sendmessage.html\nInterface query parameter (required): access_token, the API call credential.\nRequest body parameters:\nParameter Type Required Description template_id string Yes The ID of the subscribe template to be sent page string No The page to navigate to after tapping the template card; limited to pages within this mini program. Supports parameters (e.g., index?foo=bar). If this field is empty, the template has no navigation touser string Yes The openid of the recipient (user) data object Yes Template content, in a format like { \u0026ldquo;phrase3\u0026rdquo;: { \u0026ldquo;value\u0026rdquo;: \u0026ldquo;Approved\u0026rdquo; }, \u0026ldquo;name1\u0026rdquo;: { \u0026ldquo;value\u0026rdquo;: \u0026ldquo;Subscribe\u0026rdquo; }, \u0026ldquo;date2\u0026rdquo;: { \u0026ldquo;value\u0026rdquo;: \u0026ldquo;2019-12-25 09:42\u0026rdquo; } } miniprogram_state string No Target mini program type: developer for development version; trial for trial version; formal for official version; defaults to official version lang string No The language type for \u0026ldquo;Enter Mini Program to view\u0026rdquo;; supports zh_CN (Simplified Chinese), en_US (English), zh_HK (Traditional Chinese), zh_TW (Traditional Chinese); defaults to zh_CN Error Codes # Error Code Error Description Solution 40001 invalid credential, access_token is invalid The AppSecret was wrong when obtaining the access_token, or the access_token is invalid. Verify the AppSecret, or confirm whether the API is called with the correct account 40003 invalid openid Illegal openid; confirm whether touser is an openid under this mini game 40014 invalid access_token Illegal access_token; verify its validity (e.g., whether it has expired) 40037 invalid template_id Illegal template_id 43101 User has not subscribed to messages Check the subscription popup callback result or confirm whether the subscription succeeded; check whether the one-time subscribe count has been fully consumed 43107 Subscribe message capability banned Check whether the account\u0026rsquo;s subscribe message capability is banned; check whether the template corresponding to the template id is banned 43108 Concurrently sending messages to the same user Check whether multiple messages are being sent to the same user simultaneously 45168 Contains sensitive words Check whether the sent message contains sensitive words 47003 Parameter error Check whether the data structure format is correct; check whether each keyword meets the corresponding rules Recommended Solution # Maintain User Subscribe Status # The server only maintains the subscribe count of each user for different template messages. The server provides two APIs to the client:\nSubscribe success report (subscribe count +1), reported when the client successfully subscribes to messages.\nRefuse message report (reset subscribe count to zero), reported when the client queries the subscribe message status and finds the \u0026ldquo;subscribe message master switch is off\u0026rdquo; or the \u0026ldquo;subscribe status has changed to refuse\u0026rdquo;.\nAfter turning off the subscribe message master switch in the WeChat Mini Game settings, or refusing a certain subscribe message template, the cumulative subscribe count for that template will be reset to zero.\nSend Messages # Validate the template parameter specifications.\nValidate the subscribe count.\nDeduct the subscribe count after a successful send.\nWeChat APIs # openid — Unique User Identifier (independent per mini game / mini program) # How to obtain: After the client calls login() to obtain a code, pass it to the server which calls code2Session() to obtain it.\nOfficial documentation: https://developers.weixin.qq.com/minigame/dev/api/open-api/login/wx.login.html\nAPI Call Credential access_token # Official documentation: https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html\nThere are two APIs to obtain the access_token API call credential; the stable version interface is recommended. Call the API with appid and secret to obtain the access_token. The access_token is valid for 2 hours and needs to be refreshed periodically. Repeatedly calling the stable version interface within the validity period will not update the access_token (no forced refresh). If a forced refresh is needed, you can include the force_refresh parameter; the minimum interval for a forced refresh is 30s.\nEncapsulated SDK # GitHub repository: https://github.com/muzimu/wechat-sdk\nBasic usage:\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() { // Create configuration cfg := \u0026amp;config.Config{ AppID: \u0026#34;your_app_id\u0026#34;, AppSecret: \u0026#34;your_app_secret\u0026#34;, Cache: cache.NewMemory(), // Use the built-in in-memory cache } // Create client cli := client.NewClient(cfg) sub := cli.GetSubscribe() // Send subscribe message 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;Order content\u0026#34;}, \u0026#34;amount2\u0026#34;: {Value: \u0026#34;100.00\u0026#34;}, }, } err := sub.Send(msg) if err != nil { // Handle error } } ","date":"23 May 2026","externalUrl":null,"permalink":"/en/posts/wechat/subscribemessage/","section":"Blog","summary":"Introduction to the development of WeChat Mini Game subscribe message feature and a recommended design solution","title":"WeChat Mini Game Subscribe Messages","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":"3 February 2026","externalUrl":null,"permalink":"/en/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":" How to Change Git Commit Author After Commit # What to do when you find that the name in your local Git config doesn\u0026rsquo;t match your GitHub username after committing\nModify Only the Latest Commit # If you only need to modify the author information of the latest commit (without batch-modifying multiple commits), you can use --reset-author, which reads the current Git config and resets both author and committer at once:\ngit commit --amend --reset-author --no-edit --reset-author: Reset the author (and committer) to the user.name and user.email in the current Git config, and refresh the commit timestamp. --no-edit: Keep the original commit message unchanged. Prerequisite: The user.name/user.email of the current repository must already be the target values. If not yet configured, refer to the Configure Default Author for New Commits section at the end of this article first.\nAfter modification, perform a force push:\ngit push --force Scenario # In daily development, we often encounter this scenario:\nYou configured a temporary username (e.g., OldName) when initializing Git locally, committed and pushed code to a feature branch (e.g., fix_typo) in your fork repository, and even created a PR before realizing that this username doesn\u0026rsquo;t match your GitHub username (e.g., muzimu).\nThe inconsistencies cause issues: Commit author names are displayed incorrectly on GitHub, cannot be properly linked to your personal contribution statistics, or do not comply with team commit guidelines.\nCore requirement: Batch modify the author names of historical commits without affecting PR status or needing to recreate the PR, given that the PR hasn\u0026rsquo;t been merged and multiple commits have been made.\nComplete Tutorial # Prerequisites # Verify current branch: Execute git branch to ensure you are on the feature branch that needs modification (e.g., fix_typo, the branch from which the PR was created).\nVerify remote repository: Execute git remote -v to verify that origin points to your fork repository (not the original), with output similar to:\norigin git@github.com:yourGitHubUsername/repositoryName.git (fetch) origin git@github.com:yourGitHubUsername/repositoryName.git (push) Record key information:\nOld username: Execute git log and check the name after Author: (e.g., OldName). New username: Your target GitHub username (e.g., muzimu). Associated email: The email associated with your GitHub account. Step 1: Batch Modify Author Names of Historical Commits # For scenarios with multiple commits already made, use git filter-branch to rewrite all commit author information in the current branch in batches. Copy and execute the command directly (only need to modify 3 parameters):\n# Batch modify author name and email for all commits in current branch git filter-branch --env-filter \u0026#39; # 3 parameters to modify OLD_NAME=\u0026#34;YourOldGitName\u0026#34; NEW_NAME=\u0026#34;YourGitHubUsername\u0026#34; NEW_EMAIL=\u0026#34;YourGitHubEmail\u0026#34; # No need to modify below 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 After execution, the terminal will display Rewrite xxx (x/x) indicating that it is rewriting commits. A final message Ref 'refs/heads/yourBranchName' was rewritten means the modification is successful. If there is a cache conflict, first execute rm -rf .git/refs/original/ to clear the cache and then re-run the above command. Step 2: Safely Force Push to Remote Branch # After modifying local commit history, the local and remote branch records are inconsistent, and a force push is needed to overwrite the remote branch (PR will sync automatically):\n# Step 1: Refresh locally cached remote branch information (avoid stale info errors) git fetch origin yourFeatureBranchName # e.g., git fetch origin fix_typo # Step 2: Safe force push (recommended to prevent accidental overwrites) git push --force-with-lease origin yourFeatureBranchName # e.g., git push --force-with-lease origin fix_typo # Alternative: If the above command still errors, directly force push (only for branches you developed yourself) # git push -f origin yourFeatureBranchName After a successful push, the terminal will prompt rewriting history or Total 0 (delta 0). Step 3: Verify Modification Results # Local verification: Execute git log to check if all commits\u0026rsquo; Author: fields have been updated to the new username. Remote verification: Open the GitHub PR page, refresh it, and the author names of all commits will update synchronously and can properly link to your GitHub account avatar. Step 4: Configure Default Author for New Commits # To prevent new commits from using the old username again, configure Git information for the current repository or globally:\n# Apply to current repository only (recommended to avoid affecting other repositories) git config user.name \u0026#34;YourGitHubUsername\u0026#34; git config user.email \u0026#34;YourGitHubEmail\u0026#34; # Apply globally (all new commits in local repositories will use this configuration) # git config --global user.name \u0026#34;YourGitHubUsername\u0026#34; # git config --global user.email \u0026#34;YourGitHubEmail\u0026#34; Common Pitfalls # 1. Force push error: \u0026ldquo;stale info\u0026rdquo; # Reason: The locally cached remote branch information is inconsistent with the actual state on GitHub, and the --force-with-lease safety mechanism rejects the push. Solution: First execute git fetch origin yourBranchName to refresh the cache, then re-push. 2. Commits cannot be linked to GitHub account after modification # Reason: The newly configured user.email is not associated with your GitHub account. Solution: Add and verify the email in GitHub\u0026rsquo;s \u0026ldquo;Settings → Emails\u0026rdquo;. ","date":"3 February 2026","externalUrl":null,"permalink":"/en/posts/git/rename/","section":"Blog","summary":"What to do when you find that the name in your local Git config doesn’t match your GitHub username after committing","title":"How to Change Git Commit Author After Commit","type":"posts"},{"content":"","date":"3 February 2026","externalUrl":null,"permalink":"/en/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"24 January 2026","externalUrl":null,"permalink":"/en/tags/actor/","section":"Tags","summary":"","title":"Actor","type":"tags"},{"content":" 1. What is the Actor Model? # The Actor model is a conceptual model for concurrent computing that originated in 1973. In the Actor model, the Actor is the basic unit of computation, and everything is considered an Actor.\nBasic operations of an Actor:\nCreate another Actor Send messages Specify how to handle the next message Actors can maintain their own state and decide how to process the next message based on their current state.\nExample: An Actor maintains the total account balance. When receiving a new transaction message, it updates the balance in its state. For the next message, the Actor\u0026rsquo;s state will have changed.\nActors are very lightweight and can be easily created in thousands or even millions because they require fewer resources than threads. Actors are isolated from each other and do not share memory. Each has its own state, but the only way to change state is by receiving messages. Each Actor has its own mailbox, similar to a message queue. Messages are stored in the mailbox, and Actors process messages in FIFO (First-In-First-Out) order. Actors can only communicate through messages. Messages are simple immutable data structures that can be easily transmitted over a network. An Actor can only process one message at a time. Actors are decoupled and work asynchronously without waiting for responses from other Actors. Actors have multiple addresses. They can only send messages to Actors with known addresses, which include their own address, addresses of child Actors, addresses of sender Actors from received messages, and addresses of other instances of the same Actor type. An Actor can have multiple addresses (instances). Addresses are not equivalent to an Actor\u0026rsquo;s unique identity.\nActors can run locally or remotely (on other machines). Since the Actor model communicates through addresses, local or remote calls are completely transparent to the system. 2. Fault Tolerance Mechanism # In the Actor model, Actors can supervise their child Actors, including liveness detection, restarting, and redirection after message failure, achieving self-healing capabilities.\n3. Pros and Cons Summary # Pros # Easy to scale Strong fault tolerance capabilities Supports distributed deployment Effectively avoids traditional concurrency issues Cons # Risk of deadlocks Message queues may overflow The Actor model is a conceptual model, and its specific performance and characteristics depend on the specific implementation framework. Currently, well-known implementation frameworks include Akka and Elixir.\nArticle content source: https://www.youtube.com/watch?v=ELwEdb_pD0k\n","date":"24 January 2026","externalUrl":null,"permalink":"/en/posts/game/actor/","section":"Blog","summary":"Many game development frameworks adopt the Actor model. This article introduces the concepts and implementation principles of the Actor model","title":"Actor Model | Introduction","type":"posts"},{"content":"","date":"7 January 2026","externalUrl":null,"permalink":"/en/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"In a Git repository, if there\u0026rsquo;s a file named readme.md in previous commits and you want to change the filename to README.md, you may find that Git doesn\u0026rsquo;t recognize the change.\nGit is case-insensitive by default. This article explains how to configure Git to be case-sensitive and fix filenames with the correct case.\nGit Configuration # Git ignores case by default. You can check the current configuration with this command:\ngit config core.ignorecase # Output result: true Change Git to be case-sensitive with the following command:\ngit config core.ignorecase false Fixing Filenames # git mv -f readme.md README.md The -f parameter means force rename.\nCommitting Changes # After making the changes, you can commit them using the following commands:\ngit add README.md git commit -m \u0026#34;docs: fix README.md filename case\u0026#34; To check the status of your changes:\ngit status # Output will show the rename operation # renamed: readme.md -\u0026gt; README.md ","date":"7 January 2026","externalUrl":null,"permalink":"/en/posts/git/ignorecase/","section":"Blog","summary":"Git is case-insensitive by default. This article explains how to configure Git to be case-sensitive and fix filenames with the correct case.","title":"Git Case Sensitivity and Fixing Filenames","type":"posts"},{"content":"","date":"7 January 2026","externalUrl":null,"permalink":"/en/authors/muzimu/","section":"Authors","summary":"","title":"Muzimu","type":"authors"},{"content":" Common Mac Keyboard Shortcuts # 🔧 System Basics # Shortcut Description Symbolic Representation cmd+tab Switch applications ⌘+⇥ cmd+` Switch between windows of same app ⌘+` cmd+w Close current window/tab ⌘+w cmd+m Minimize current window ⌘+m cmd+shift+3 Full screen screenshot ⌘+⇧+3 cmd+shift+4 Partial screenshot ⌘+⇧+4 cmd+q Quit current application ⌘+q cmd+backspace Delete current line/selection ⌘+⌫ cmd+ctrl+f Toggle full screen mode ⌘+⌃+f 💻 Command Line Environment # Shortcut Description Symbolic Representation ctrl+u Delete entire line before cursor ⌃+u ctrl+w Delete word before cursor ⌃+w ctrl+a Move cursor to line start ⌃+a ctrl+e Move cursor to line end ⌃+e ctrl+k Delete from cursor to line end ⌃+k 📝 VS Code # Shortcut Description Symbolic Representation cmd+, Open settings ⌘+, shift+alt+f Format document ⇧+⌥+f ctrl+` Toggle integrated terminal ⌃+` cmd+shift+p Open command palette ⌘+⇧+p cmd+w Close current editor tab ⌘+w cmd+shift+w Close current window ⌘+⇧+w u-tools # Feature Name Shortcut Symbolic Representation Launch main page alt+space ⌥+␣ Stock tracker alt+s ⌥+s Generate variable name alt+n ⌥+n Text translation alt+f ⌥+f Karabiner Custom Script # Modifier: alt (hold alt then press the following keys)\nFunction Key Mapped Function Symbolic Representation j Left arrow key ⌥+j → ← k Down arrow key ⌥+k → ↓ i Up arrow key ⌥+i → ↑ l Right arrow key ⌥+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":"11 December 2025","externalUrl":null,"permalink":"/en/posts/mac/","section":"Blog","summary":"Essential Mac keyboard shortcuts and customizations to improve your workflow efficiency","title":"Boost Mac Productivity with Custom Shortcuts","type":"posts"},{"content":"","date":"11 December 2025","externalUrl":null,"permalink":"/en/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":"","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":"💡 Preface Efficient and elegant log querying is a required skill for backend engineers. This article details the usage of structured log (JSON) query tools and provides practical case analyses combining multiple tools.\nWe assume there are multiple log files in the current directory, all with .log suffix. Let\u0026rsquo;s start introducing log query commands.\n🔍 Log Reading Commands # cat Command # cat is one of the most basic and commonly used commands in Linux/Unix systems, short for \u0026ldquo;concatenate\u0026rdquo;. It is mainly used to read, display, and merge file contents, and can also be used to create simple files. Here are its detailed usages:\nRead a single log file: Directly specify the filename to view the entire contents of the log file. cat test.log Read multiple specified log files: List multiple log filenames in sequence to view their contents at once (concatenated in input order). cat test1.log test2.log Read all .log files in current directory: Use wildcard * to match all files ending with .log. Contents will be displayed in \u0026ldquo;numeric \u0026gt; uppercase \u0026gt; lowercase\u0026rdquo; character sorting order. cat *.log Read log files with specific prefix: Use \u0026ldquo;prefix + wildcard\u0026rdquo; combination to precisely match target files (e.g., match all log files starting with 20250928). cat 20250928*.log Merge multiple files into a new file: Use redirection symbol \u0026gt; to merge contents of multiple files into a specified new file (Note: \u0026gt; will overwrite existing contents of the target file). cat test1.log test2.log \u0026gt; merged.log Append content to an existing file: To preserve existing contents of the target file, use redirection symbol \u0026gt;\u0026gt; to append new content to the end of the target file. cat file3.txt \u0026gt;\u0026gt; merged.txt # Append contents of file3.txt to merged.txt Read from standard input (keyboard) and save to file: When no source file is specified, cat reads from keyboard by default. Press Ctrl+d to end input and save (suitable for quickly creating short text files). cat \u0026gt; newfile.txt # Press Ctrl+d after entering content to save Read gzipped files: Use zcat command to directly read gzipped files. zcat test.log.gz Option Function Description -n Display line numbers for all lines (including empty lines) -b Display line numbers only for non-empty lines (ignore empty lines) -s Compress consecutive empty lines into one (remove extra blank lines) -E Display $ at end of each line (easy to distinguish line endings and spaces) -T Display tab characters as ^I (easy to view hidden tabs) -v Display non-printable characters (except newlines and tabs) For real-time log monitoring, you can use tail -f command\nFor very large log files, you can use less command for pagination\ncat is the most commonly used command in log querying. We can use pipes (|) to pass cat\u0026rsquo;s output to other tools for processing:\nView file and search for keywords (combining with grep):\ncat test.log | grep \u0026#34;ERROR\u0026#34; # Filter lines containing ERROR from test.log View file and sort (combining with sort):\ncat test.log | sort -n # Sort file contents numerically tail Command # View the end contents of test.log (displays last 10 lines by default) tail test.log Real-time tracking of updates to all log files in current directory, suitable for monitoring continuously written logs tail -f *.log Enhanced real-time tracking automatically follows new files when log files are rotated (e.g., archived by date) tail -F *.log less Command # Open file:\nless test.log # View test.log with pagination Can also combine with pipes to receive output from other commands:\ncat *.log | less # View merged contents of all .log files with pagination cat test.log | grep \u0026#34;error\u0026#34; | less # View log lines containing error with pagination Core operation shortcuts:\nPagination: Mouse wheel: Scroll up/down. Space / f: Scroll down one screen. b: Scroll up one screen. Enter / Up/Down arrow keys: Scroll line by line. Navigation: g: Jump to start of file. G: Jump to end of file. 50g: Jump to line 50. Search: /keyword: Search downward from current position (e.g., /ERROR searches for ERROR). ?keyword: Search upward from current position. Press n: Jump to next match. Press N: Jump to previous match. Other: q: Exit view. v: Open file in default editor at current position (convenient for editing). Ctrl+f: Scroll forward one screen (same as space). Ctrl+b: Scroll backward one screen (same as b). To preserve jq syntax highlighting in less, add -C parameter to jq and -R parameter to less Example: cat test.log | jq -C | less -R\n🎯 Log Filtering Commands # jq and Common Functions # Basic Value Extraction # .: Get the entire JSON log object, used for formatted output\nExample: cat test.log | jq '.' (format and print logs)\n-c enables compact mode, displays logs in single lines\n.key: Extract value of specified field (e.g., timestamp, level in logs)\nExample: cat test.log | jq '.level' (extract all log levels)\n.array[index]: Get specified element of array-type field (e.g., tags[0] in logs)\nExample: cat test.log | jq '.tags[1]' (extract second element of tags array)\n.key1.key2: Nested value extraction (e.g., user.name in logs)\nExample: cat test.log | jq '.request.url' (extract request URL)\nConditional Filtering # select(condition): Filter log entries that meet the condition\nExample: cat test.log | jq 'select(.level == \u0026quot;ERROR\u0026quot;)' (filter all error level logs)\nhas # Function: Determine if an object contains the specified field (commonly used in logs to check if an attribute exists)\nUsage: select(has(\u0026quot;fieldname\u0026quot;))\nExample 1: Filter error logs containing stackTrace field\ncat test.log | jq 'select(has(\u0026quot;stackTrace\u0026quot;) and .level == \u0026quot;ERROR\u0026quot;)'\nExample 2: Exclude error logs containing stackTrace field\ncat test.log | jq 'select(has(\u0026quot;stackTrace\u0026quot;) == false)'\ncontains # Function: Determine if an array/string contains specified element/substring\nUsage: select(.field | contains(target))\nExample 1 (string contains): Filter logs where message contains \u0026ldquo;timeout\u0026rdquo;\ncat test.log | jq 'select(.message | contains(\u0026quot;timeout\u0026quot;))'\nExample 2 (string contains): Exclude logs where message contains \u0026ldquo;timeout\u0026rdquo;\ncat test.log | jq 'select(.message | contains(\u0026quot;timeout\u0026quot;) == false)'\nExample 3 (array contains): Filter logs where tags array contains \u0026ldquo;payment\u0026rdquo;\ncat test.log | jq 'select(.tags | contains([\u0026quot;payment\u0026quot;]))'\nindex # Function: Returns starting index of substring in string (returns null if not found), used for precise position judgment\nUsage: select(.field | index(substring) != null)\nExample: Filter logs where \u0026ldquo;error\u0026rdquo; appears at position 5 or later in message\ncat test.log | jq 'select(.message | index(\u0026quot;error\u0026quot;) \u0026gt;= 5)'\ntest (Regular Expression) # Function: Determine if string matches regular expression\nUsage: select(.field | test(regular_expression))\nExample: Filter logs where first character of name is \u0026ldquo;李\u0026rdquo; (Chinese surname Li)\ncat test.log | jq 'select(.name | test(\u0026quot;^李\u0026quot;))'\nsort/uniq/group_by(array) # sort_by(field): Sort logs by specified field\nExample: Sort logs by timestamp in ascending order\ncat test.log | jq '.[] | sort_by(.timestamp)'\nunique: Array deduplication (commonly used to extract unique values)\nExample: Extract all unique userIds\ncat test.log | jq '.[] | .userId' | jq -s 'unique'\ngroup_by(field)[]: Group by field (commonly used before statistics)\nExample: Group and count logs by level\ncat test.log | jq '.[] | group_by(.level)'\nMultiple lines of independent JSON objects (not arrays) # Function: Convert multiple lines of independent JSON objects (not arrays) into an array\nExample: For non-array JSON, use -s and .[]\ncat test.log | jq -s 'sort_by(.timestamp) | .[]'\nlength # Function: Returns length of array or string\nUsage:\ncat test.log | jq '.[] | length' Get number of logs\ncat test.log | jq '.message | length' Get character count of log messages\nRename Fields # Function: Rename fields to other names Usage: # Example: Rename field \u0026#34;code\u0026#34; to \u0026#34;uid\u0026#34; and \u0026#34;cardnumber\u0026#34; to \u0026#34;ID\u0026#34; 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; 📊 Log Statistical Analysis Commands # sort/uniq # Function: Used together to sort and deduplicate arrays, commonly used for log data statistics Usage: cat test.log | jq '.level' | sort | uniq wc # Function: Count number of lines, words, and bytes in logs Usage: cat test.log | wc -l Get number of log lines awk # awk is a powerful text processing tool, particularly suitable for log field extraction, conditional filtering, and statistical calculations. It processes text line by line and can split each line into fields based on delimiters.\nBasic Syntax:\nawk \u0026#39;pattern {action}\u0026#39; file # pattern is filter condition, action is processing action Extract fields by delimiter: Assume logs are space-separated, extract 1st and 3rd columns\ncat test.log | awk \u0026#39;{print $1, $3}\u0026#39; # $1 represents 1st column, $3 represents 3rd column Specify delimiter: Use -F to specify delimiter (e.g., comma, colon, etc.)\ncat test.log | awk -F \u0026#39;,\u0026#39; \u0026#39;{print $2}\u0026#39; # Split by comma, extract 2nd column Conditional filtering: Only process lines that meet the conditions\ncat test.log | awk \u0026#39;$3 \u0026gt; 100 {print $0}\u0026#39; # Filter lines where 3rd column value \u0026gt; 100 cat test.log | awk \u0026#39;/ERROR/ {print $0}\u0026#39; # Filter lines containing ERROR Statistical counting: Use built-in variables NR (line number) and NF (number of fields)\ncat test.log | awk \u0026#39;END {print NR}\u0026#39; # Count total number of lines cat test.log | awk \u0026#39;{sum+=$3} END {print sum}\u0026#39; # Sum values in 3rd column and output total Formatted output: Use printf to customize output format\ncat test.log | awk \u0026#39;{printf \u0026#34;Level: %s, Message: %s\\n\u0026#34;, $1, $2}\u0026#39; Multiple condition combinations: Combine logical operators (\u0026amp;\u0026amp;, ||, !)\ncat test.log | awk \u0026#39;$1==\u0026#34;ERROR\u0026#34; \u0026amp;\u0026amp; $3\u0026gt;50 {print $0}\u0026#39; # 1st column is ERROR and 3rd column \u0026gt; 50 Common Built-in Variables:\nVariable Description $0 Complete content of current line $1, $2, $n 1st, 2nd, nth column of current line NR Current line number being processed NF Total number of fields in current line FS Field separator (default is space) OFS Output field separator 💼 Practical Case Analysis # Query Non-Info Logs # Exclude some irrelevant logs using 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; Query Logs for Specific User # 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; Count Error Logs # cat test.log | jq \u0026#39;select(.level==\u0026#34;error\u0026#34;) | .request_id\u0026#39; | sort | uniq | wc -l ","date":"4 November 2025","externalUrl":null,"permalink":"/en/posts/log/","section":"Blog","summary":"Combining tools like cat/grep/jq/sort/uniq/awk for log query and analysis","title":"Practical Guide to Efficient Log Query and Analysis Tools","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":"我们使用一个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":"","date":"2025-10-28","externalUrl":null,"permalink":"/tags/%E5%A4%A7%E6%A8%A1%E5%9E%8B/","section":"Tags","summary":"","title":"大模型","type":"tags"},{"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":"本文为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/acm/","section":"Tags","summary":"","title":"ACM","type":"tags"},{"content":"","date":"17 October 2025","externalUrl":null,"permalink":"/en/tags/interview/","section":"Tags","summary":"","title":"Interview","type":"tags"},{"content":" Why Choose Python for Algorithm Problems # Undoubtedly, Python has the most concise syntax among mainstream programming languages. Coupled with its rich built-in functions, proficiency in Python can significantly reduce code volume and development time when solving algorithm problems.\nStudents targeting ICPC/CCPC competitions often choose C++ for performance reasons. However, programming problems encountered in ordinary competitions or interviews do not typically constrain language runtime— as long as the algorithm\u0026rsquo;s time complexity meets requirements, it will pass. Therefore, once you become accustomed to using Python for algorithm problems, you can improve efficiency and appreciate Python\u0026rsquo;s elegance.\nCommon Techniques # 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) # default to empty list dict = defaultdict(int) # default to 0 for k, v in dict: print(k, v) Comprehensions # # List comprehension newnames = [name.upper() for name in names if len(name) \u0026gt; 3] multiples = [i for i in range(30) if i % 3 == 0] # Dictionary comprehension newdict = {key: len(key) for key in listdemo} dic = {x: x**2 for x in (2, 4, 6)} # Set comprehension 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;} Custom Sorting # 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]) # Custom sort function def custom_sort_key(item): # Generate sort key based on some property of item return item[\u0026#39;priority\u0026#39;] # Assume we sort by priority # Example data 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}, ] # Use sorted() for sorting sorted_items = sorted(items, key=custom_sort_key) print(sorted_items) # Output sorted list # Use list.sort() for in-place sorting items.sort(key=custom_sort_key) print(items) # Output in-place sorted list Memoization # from functools import cache # Use @cache decorator before function to enable memoization Binary Search Functions # import bisect my_list = [1, 3, 5, 5, 7, 9] # Use bisect_left to find insertion position (leftmost) left_index = bisect.bisect_left(my_list, 5) print(\u0026#34;Insertion position (leftmost):\u0026#34;, left_index) # Use bisect_right to find insertion position (rightmost) right_index = bisect.bisect_right(my_list, 5) print(\u0026#34;Insertion position (rightmost):\u0026#34;, right_index) # Output: # Insertion position (leftmost): 2 # Insertion position (rightmost): 4 # Detailed usage: bisect.bisect_left(a, x, lo=0, hi=len(a), *, key=None) Finds the first index in [lo, hi) of list a where element \u0026gt;= x bisect.bisect_right(a, x, lo=0, hi=len(a), *, key=None) Finds the first index in [lo, hi) of list a where element \u0026gt; x # Custom comparison rule my_list = [1, 3, 5, 7, 9] index = bisect.bisect_left(my_list, 5, key=lambda x: x * x) # Application example: Grade classification def grade(score, breakpoints=[60, 70, 80, 90], grades=\u0026#39;FDCBA\u0026#39;): i = bisect.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;] General Template # This template is for ICPC/CCPC competitions. If you have performance requirements for Python code, consider using the encapsulated IO module in the template.\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":"17 October 2025","externalUrl":null,"permalink":"/en/posts/acm/python/","section":"Blog","summary":"","title":"Python Algorithm Problem Solving Techniques","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 I/O Templates # Many people struggle to solve algorithm problems in job interviews and written tests due to unfamiliarity with ACM-style input and output. Input and output are the fundamentals of solving algorithm problems, and you can \u0026ldquo;memorize\u0026rdquo; the templates based on your preferred programming language.\nBelow is a summary of template implementations for common ACM input and output scenarios, covering mainstream programming languages such as C++, Python, Go, Java, and JavaScript. All sample codes are for the \u0026ldquo;A+B\u0026rdquo; problem, where the task is to read two numbers A and B and output their sum A+B.\n1. Multiple Data Groups: Each Group Starts with n Followed by n Lines of Two Integers # Practice Problem: A+B Problem I\nInput: The first line contains an integer N, indicating there will be N subsequent lines, each containing two integers a and b separated by a space.\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 = +input; // Fix: Original \u0026#34;line\u0026#34; is corrected to \u0026#34;input\u0026#34; for consistency } else { const [a, b] = input.split(\u0026#39; \u0026#39;).map(Number); console.log(a + b); // Fix: Incomplete code in original is completed if (++count \u0026gt;= n) rl.close(); } }) 2. Multiple Lines of Input: Each Line Contains Two Integers # Practice Problem: A+B Problem II\nInput: Contains a series of (a, b) pairs, each separated by a space. Each (a, b) pair occupies one line.\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":"13 October 2025","externalUrl":null,"permalink":"/en/posts/acm/io/","section":"Blog","summary":"","title":"ACM IO Template","type":"posts"},{"content":"","date":"13 October 2025","externalUrl":null,"permalink":"/en/tags/programing/","section":"Tags","summary":"","title":"Programing","type":"tags"},{"content":"When pushing projects to GitHub using the HTTPS method, you need to enter your username and password every time, which is very troublesome. However, by using the SSH method, this is no longer necessary. You only need to configure an SSH key in your GitHub account.\n1. Check if an SSH key already exists on your local machine # Press Win + R to open the Run dialog, type cmd to open the command line.\nEnter the following commands:\ncd .ssh dir If the files id_rsa and id_rsa.pub exist, it means you already have an SSH Key. The image below indicates they exist.\nIf they exist, proceed directly to step three.\n2. Generate an SSH key # If they do not exist, generate one using the following command.\nssh-keygen -t rsa -C \u0026#34;xxx@xxx.com\u0026#34; // Replace \u0026#34;xxx@xxx.com\u0026#34; with your own GitHub email address Press Enter for all prompts.\n3. Get the public key content (id_rsa.pub) # cd .ssh type id_rsa.pub // Windows command line cat id_rsa.pub // Linux command line As shown in the image below, copy this content.\n4. Add the public key to your GitHub account # Click your profile picture in the top right corner, go to Settings, and add SSH Keys. Paste the content you just copied. 5. Verify if the setup was successful # ssh -T git@github.com If successful, it will display something like this:\nOnce successfully set up, you can clone and push code without needing a username and password.\nNote: After this, when cloning repositories, use the SSH URL, not HTTPS!\n","date":"27 September 2025","externalUrl":null,"permalink":"/en/posts/git/gitssh/","section":"Blog","summary":"Config your SSH to connect Github","title":"How to config 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":" Introduction # When using Git for code repository management, it\u0026rsquo;s inevitable that we need to submit commits. Each commit requires a message describing the changes made, helping collaborators quickly review and understand the development progress, thereby improving collaboration efficiency. Currently, the Angular convention is the most widely adopted Git commit message standard.\nThis article references the following documents and provides a beginner-friendly guide:\nAngular GitHub Convention Reference Blog Convention Details # Commit Message Format # \u0026lt;type\u0026gt;(\u0026lt;scope\u0026gt;): \u0026lt;subject\u0026gt; \u0026lt;BLANK LINE\u0026gt; \u0026lt;body\u0026gt; \u0026lt;BLANK LINE\u0026gt; \u0026lt;footer\u0026gt; Among these, type and subject are mandatory, while scope is optional. The first line is the Header (title), the second part is the Body (summary), and the last part is the Footer (notes).\nIn practical development, it\u0026rsquo;s common to primarily follow the header convention, i.e., type(scope): subject. Examples are as follows:\nfeat(Extracurricular Activities): Display extracurricular activity credits fix(Extracurricular Activities): Fix issue with displaying extracurricular activity credits docs: Add environment setup tutorial for newcomers in README test: Unit tests for Academic System API Type # Commonly used Type values include:\nfeat: A new feature fix: A bug fix docs: Documentation changes style: Changes that do not affect code logic (e.g., formatting) refactor: Code refactoring that is neither a new feature nor a bug fix perf: Performance improvements test: Adding or modifying tests chore: Changes to the build process or auxiliary tools Scope # Specifies the module or file scope affected by the commit. For example:\nExtracurricular Activities Academic System Subject # It is recommended to use Chinese. Briefly describe the change using a statement, e.g., \u0026ldquo;Fixed the issue with\u0026hellip;\u0026rdquo;. Do not end the subject with a period or other punctuation. Tools for Standardizing Git Commits # VSCode users can install the git-commit-plugin extension. The usage effect is shown below:\n","date":"24 September 2025","externalUrl":null,"permalink":"/en/posts/git/gitcommit/","section":"Blog","summary":"Introduction to Angular Git Commit Convention","title":"Git Commit Convention","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":" Introduction # This post documents how this site was built: using Hugo as the static site generator, paired with the Blowfish theme, hosted on GitHub Pages, and deployed automatically via GitHub Actions on every push.\nThe tech stack:\nHugo: A Go-based static site generator known for its fast builds. Blowfish: A feature-rich, highly customizable Hugo theme. GitHub Pages: Free static site hosting. GitHub Actions: CI that builds and deploys the site automatically. Prerequisites # Install Go # Hugo is built with Go. If you install Hugo via go install, you need a working Go environment first. Download it from the Go website and verify:\ngo version Install Hugo # With Go in place, install a specific version of Hugo via go install:\ngo install github.com/gohugoio/hugo@v0.151.0 Note: The Blowfish theme requires the extended version of Hugo (for SCSS compilation). If you install via a package manager, make sure it is the extended build.\nVerify the installation:\nhugo version Create the Site # Create a new Hugo site in your working directory:\nhugo new site muzimu cd muzimu git init Install the Blowfish Theme # Blowfish recommends adding the theme as a Git submodule for easy upgrades.\nAdd the Theme # git submodule add -b main https://github.com/nunocoracao/blowfish.git themes/blowfish This creates a .gitmodules file in the project root:\n[submodule \u0026#34;themes/blowfish\u0026#34;] path = themes/blowfish url = git@github.com:nunocoracao/blowfish.git branch = main Update the Theme # To upgrade the theme later:\ngit submodule update --remote --merge Initialize Submodules After Cloning # After cloning the repo on a new machine, sync and fetch the submodule:\ngit submodule sync git submodule update --init --remote Configure the Site # Blowfish keeps its configuration under config/_default/, split into several files by responsibility rather than a single hugo.toml. The key files:\nFile Purpose hugo.toml Core settings (theme, baseURL, language, taxonomies) params.toml Theme parameters (appearance, layout, feature toggles) languages.zh-cn.toml Chinese language and author info languages.en.toml English language config menus.zh-cn.toml Chinese navigation menu markup.toml Markdown rendering and code highlighting Basic Configuration # Set the theme and site URL in 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; Author Info # Configure the author avatar, name, and social links in languages.zh-cn.toml:\n[params.author] name = \u0026#34;muzimu\u0026#34; image = \u0026#34;img/avatar.jpg\u0026#34; headline = \u0026#34;muzimu\u0026#34; links = [ { github = \u0026#34;https://github.com/muzimu\u0026#34; }, { bilibili = \u0026#34;https://space.bilibili.com/448045568\u0026#34; }, ] For more theme parameters, see the official Blowfish docs: https://blowfish.page/docs/configuration/#theme-parameters\nCreate a Post # Use hugo new to create a post from an archetype template. This site uses the Page Bundle layout, where each post lives in its own directory for easier asset management:\nhugo new content content/posts/Hugo/index.md Example Front Matter (YAML):\n--- title: \u0026#34;Post Title\u0026#34; summary: \u0026#34;Post summary\u0026#34; isCJKLanguage: true date: 2025-09-06T17:42:06+08:00 tags: - tag --- Setting isCJKLanguage: true lets Hugo correctly count words and reading time for CJK content.\nLocal Preview # Start the local dev server, available at http://localhost:1313/ by default:\nhugo server If a post is still a draft (draft: true), add -D to render it:\nhugo server -D Deploy to GitHub Pages # Prepare the Repositories # You need two repositories:\nSource repo: Holds the Hugo project source (e.g. muzimu/muzimu). Publish repo: Holds the build output, i.e. the GitHub Pages site (e.g. muzimu/muzimu.github.io). Configure Automated Deployment # Create .github/workflows/hugo.yaml in the source repo. On every push to main, GitHub Actions builds the site and publishes the public/ directory to the target repo:\nname: github pages on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: submodules: true # Fetch the Blowfish theme submodule fetch-depth: 0 # Full history for .GitInfo / .Lastmod - name: Setup Hugo uses: peaceiris/actions-hugo@v2 with: hugo-version: \u0026#39;latest\u0026#39; extended: true # Use the extended build - name: Build run: hugo --minify # Build and minify static assets - 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 Configure the Access Token # Because you publish to a different repository, the default GITHUB_TOKEN won\u0026rsquo;t work. Instead:\nGenerate a token with repo scope on the GitHub Personal Access Tokens page. In the source repo, go to Settings → Secrets and variables → Actions and add a secret named PERSONAL_TOKEN with the token value. Security note: A token is equivalent to your password. Never commit it in plain text inside the workflow file—GitHub will auto-revoke it and it risks leaking. Always reference it through Secrets.\nEnable Pages and Visit # After pushing, wait for the Actions build to finish, confirm the publishing branch under the publish repo\u0026rsquo;s Settings → Pages, then visit your site at https://\u0026lt;username\u0026gt;.github.io/.\nSummary # That\u0026rsquo;s it—a personal blog built on Hugo + Blowfish. Day to day, just create a post, preview it locally, and git push to publish automatically. Clean and efficient.\n","date":"6 September 2025","externalUrl":null,"permalink":"/en/posts/hugo/","section":"Blog","summary":"A step-by-step guide to building a personal blog with the Hugo static site generator and the Blowfish theme, with automated deployment via GitHub Actions.","title":"Building a Blog with Hugo \u0026 Blowfish","type":"posts"},{"content":"","date":"6 September 2025","externalUrl":null,"permalink":"/en/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":" Github # muzimu\u0026rsquo;s Github\nQQ # QQ：2404580981\nMy Wechat # ","externalUrl":null,"permalink":"/en/about/","section":"欢迎来到 Blowfish ！","summary":"","title":"About me","type":"page"},{"content":"","externalUrl":null,"permalink":"/en/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","externalUrl":null,"permalink":"/en/tools/py/","section":"Tools","summary":"","title":"Chinese to Pinyin Initials","type":"tools"},{"content":"","externalUrl":null,"permalink":"/en/tools/excel2json/","section":"Tools","summary":"","title":"Excel to JSON","type":"tools"},{"content":" ","externalUrl":null,"permalink":"/en/friends/","section":"Friends","summary":"","title":"Friends","type":"friends"},{"content":"","externalUrl":null,"permalink":"/en/tools/jsonesc/","section":"Tools","summary":"","title":"JSON String Escape/Unescape","type":"tools"},{"content":"","externalUrl":null,"permalink":"/en/tools/jq-playground/","section":"Tools","summary":"","title":"Online jq Tool","type":"tools"},{"content":"","externalUrl":null,"permalink":"/en/tags/personal-site/","section":"Tags","summary":"","title":"Personal Site","type":"tags"},{"content":"","externalUrl":null,"permalink":"/en/resume/","section":"欢迎来到 Blowfish ！","summary":"","title":"Resume","type":"resume"},{"content":"","externalUrl":null,"permalink":"/en/tools/","section":"Tools","summary":"","title":"Tools","type":"tools"},{"content":"","externalUrl":null,"permalink":"/tags/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/","section":"Tags","summary":"","title":"个人博客","type":"tags"}]