<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>王帅的博客</title><description>王帅的个人技术博客</description><link>https://blog.wangshuai.app/</link><language>zh_CN</language><item><title>chatgpt使用自建mcp插件和openai的tunnel做开发</title><link>https://blog.wangshuai.app/2026-07-25-chatgpt%E4%BD%BF%E7%94%A8%E7%BD%91%E9%A1%B5%E9%A2%9D%E5%BA%A6%E5%BC%80%E5%8F%91%E6%9C%AC%E5%9C%B0%E9%A1%B9%E7%9B%AE/</link><guid isPermaLink="true">https://blog.wangshuai.app/2026-07-25-chatgpt%E4%BD%BF%E7%94%A8%E7%BD%91%E9%A1%B5%E9%A2%9D%E5%BA%A6%E5%BC%80%E5%8F%91%E6%9C%AC%E5%9C%B0%E9%A1%B9%E7%9B%AE/</guid><pubDate>Sun, 26 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近AI使用频繁;因此开通了日区&lt;code&gt;GPT Plus&lt;/code&gt;;本来之前还研究怎么优化每次&lt;code&gt;Codex&lt;/code&gt;的5小时限额;上周 &lt;code&gt;Tibo&lt;/code&gt;就宣布把5小时限额去掉&lt;/p&gt;
&lt;p&gt;这就导致周额度相形见绌了;前天把额度用完后就在研究其它的方法;&lt;strong&gt;传奇程序员&lt;/strong&gt;不能就这样下线啊;&lt;/p&gt;
&lt;p&gt;正巧看到&lt;code&gt;L&lt;/code&gt;站的一篇文章:&lt;a href=&quot;https://linux.do/t/topic/2259880&quot;&gt;通过 Coding Tools MCP 最大化利用 GPT 订阅额度，解决额度焦虑&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本文介绍我实践出的简易临时使用&lt;code&gt;GPT Web&lt;/code&gt;开发本地项目的方法; 路径与上文有些不同&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;解决方案和&lt;code&gt;L&lt;/code&gt;站文章介绍的方法类似;都是使用&lt;code&gt;GPT Web&lt;/code&gt;端的插件功能&lt;/p&gt;
&lt;p&gt;一句话总结:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用开发人员模式,自建一个可以访问本地文件的MCP服务,通过GPT官方隧道(&lt;code&gt;tunnel&lt;/code&gt;) 让AI可以直接读写本地指定的文件达到本地开发的目的&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;根据环境选择最终方案&lt;/h4&gt;
&lt;p&gt;如果不想从头判断自己应该走哪套方案,可以直接在下面按实际环境选择。这里的逻辑是: &lt;strong&gt;是否有域名决定能否使用 Cloudflare Tunnel;是否有服务器决定是否增加 OIDC / Pocket ID 认证层&lt;/strong&gt;。选择完成后会自动组合出对应架构、最终步骤和命令;后面的原始教程内容仍然保留,可以继续查看详细说明。&lt;/p&gt;
&lt;p&gt;&amp;lt;div id=&quot;mcp-route-picker&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;script data-pjax src=&quot;/custom_js/mcp-route-picker.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;h4&gt;新版方案-纯文件访问&lt;/h4&gt;
&lt;p&gt;在官方tunnel的基础之上我又研究出了用&lt;code&gt;cloudflare tunnel&lt;/code&gt;穿透的方法(PS:我本来也做了公开MCP服务的方法;但没CF的网络加持总是&lt;code&gt;time out&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;此处只说明和下面流程不同的地方:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用支持Oauth认证的&lt;code&gt;MCP proxy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里使用的工具是:&lt;strong&gt;&lt;a href=&quot;https://github.com/sigbit/mcp-auth-proxy&quot;&gt;mcp-auth-proxy&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;GPT官方隧道(&lt;code&gt;tunnel&lt;/code&gt;) 不仅实现了http访问;还加了自己的API鉴权;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;modelcontextprotocol/server-filesystem&lt;/code&gt;不支持直接Http访问;因此需要加一个&lt;strong&gt;OAuth 2.1/OIDC gateway&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;**&lt;a href=&quot;https://github.com/sigbit/mcp-auth-proxy&quot;&gt;mcp-auth-proxy&lt;/a&gt;**不仅是mcp代理,还集成了&lt;code&gt;MCP&lt;/code&gt;所需的认证功能;比如&lt;code&gt;GPTMCP&lt;/code&gt;支持的&lt;code&gt;DCR(动态客户端注册)&lt;/code&gt;;没有这个功能的话就需要同时用tunnel转发&lt;code&gt;MCP Server&lt;/code&gt;以及&lt;code&gt;Oauth&lt;/code&gt;认证服务&lt;/p&gt;
&lt;p&gt;&amp;lt;span id=&quot;cf-mcp-auth-proxy&quot;&amp;gt;&amp;lt;/span&amp;gt;
我测试OK的命令是:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  D:\Tools\mcp-auth-proxy.exe `
  -d &quot;D:\Tools\data&quot; `
  --listen 192.168.1.XX:24024 `
  --external-url https://{your-domain} `
  --no-auto-tls  `
  --tls-accept-tos `
  --oidc-configuration-url &quot;https://your-provider.com/.well-known/openid-configuration&quot; `
  --oidc-client-id &quot;your-oidc-client-id&quot; `
  --oidc-client-secret &quot;your-oidc-client-secret&quot; `
  --oidc-allowed-users &quot;user1@example.com,user2@example.com&quot; `
  -- npx.cmd -y @modelcontextprotocol/server-filesystem &quot;D:\Tools&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;无服务器时使用 Password 认证&lt;/h4&gt;
&lt;p&gt;&amp;lt;span id=&quot;cf-password-auth&quot;&amp;gt;&amp;lt;/span&amp;gt;
如果没有可长期运行 &lt;code&gt;Pocket ID&lt;/code&gt; 的服务器,则不需要配置 OIDC;&lt;code&gt;mcp-auth-proxy&lt;/code&gt; 自带 Password 登录认证。&lt;/p&gt;
&lt;p&gt;这里要注意:&lt;strong&gt;ChatGPT 对接的仍然是 OAuth 2.1 / DCR,Password 只是用户在授权页面上的登录方式,不是让 ChatGPT 直接使用密码访问 MCP。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;D:\Tools\mcp-auth-proxy.exe `
-d &quot;D:\Tools\data&quot; `
--listen 192.168.1.XX:24024 `
--external-url https://{your-domain} `
--no-auto-tls `
--password &quot;请替换为强密码&quot; `
-- npx.cmd -y @modelcontextprotocol/server-filesystem &quot;D:\Tools&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果担心明文密码出现在命令行或历史记录中,也可以改用 &lt;code&gt;--password-hash&lt;/code&gt; 配置 bcrypt hash。&lt;/p&gt;
&lt;p&gt;ChatGPT Web 中仍然选择 &lt;code&gt;OAuth&lt;/code&gt; 身份认证,地址填写 &lt;code&gt;https://{your-domain}/mcp&lt;/code&gt;;高级 OAuth 设置保持自动发现即可,不需要填写 Pocket ID/OIDC 的 Client ID 和 Client Secret。&lt;/p&gt;
&lt;p&gt;扫描工具或首次连接时,浏览器会跳转到 &lt;code&gt;mcp-auth-proxy&lt;/code&gt; 的授权页面;输入上面 &lt;code&gt;--password&lt;/code&gt; 设置的密码完成认证后,再回到 ChatGPT 完成连接。&lt;/p&gt;
&lt;p&gt;因此这条路线不要选择“无认证”,也不要把 Password 直接填到 ChatGPT 中。&lt;/p&gt;
&lt;p&gt;这里的“无服务器”只是指没有单独部署 Pocket ID / OIDC 服务;&lt;code&gt;mcp-auth-proxy&lt;/code&gt; 和 &lt;code&gt;cloudflared&lt;/code&gt; 仍然运行在本地 PC,所以本地 PC 关机后这个 MCP 也会离线。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在CF的&lt;a href=&quot;https://dash.cloudflare.com/tunnels&quot;&gt;Tunnels&lt;/a&gt;中配置一个本机路由(到&lt;code&gt;your-domain&lt;/code&gt;)的应用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;span id=&quot;cf-tunnel-config&quot;&amp;gt;&amp;lt;/span&amp;gt;
&lt;img src=&quot;/img/cf_tunnel_application.png&quot; alt=&quot;cf_tunnel_application&quot; /&gt;&lt;/p&gt;
&lt;p&gt;确保直接公网访问会报401&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ChatGpt的插件直接使用&lt;code&gt;OAuth&lt;/code&gt;身份认证&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;地址填&lt;code&gt;your-domain/mcp&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;点击高级&lt;code&gt;OAuth&lt;/code&gt;设置的时候会自动识别出认证授权路由&lt;/p&gt;
&lt;p&gt;&amp;lt;span id=&quot;chatgpt-oauth-config&quot;&amp;gt;&amp;lt;/span&amp;gt;
&lt;img src=&quot;/img/mcp_oauth.png&quot; alt=&quot;mcp oauth&quot; /&gt;&lt;/p&gt;
&lt;p&gt;创建成功后点击连接:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;有服务器 / OIDC&lt;/strong&gt;:会跳转到 &lt;code&gt;Pocket ID&lt;/code&gt;,完成登录后点击页面 &lt;code&gt;Auth&lt;/code&gt; 即可连接成功。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无服务器 / Password&lt;/strong&gt;:会跳转到 &lt;code&gt;mcp-auth-proxy&lt;/code&gt; 的密码认证页面,输入启动命令中 &lt;code&gt;--password&lt;/code&gt; 设置的密码后即可连接成功。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;支持完整开发能力的单MCP服务方案&lt;/h4&gt;
&lt;p&gt;上面的MCP服务&lt;code&gt;modelcontextprotocol/server-filesystem&lt;/code&gt;只能让AI支持访问一个PC的文件夹(可以多目录),一个&lt;code&gt;mcp-auth-proxy.exe&lt;/code&gt;对应一个MCP服务&lt;/p&gt;
&lt;p&gt;除了文件访问,开发时还需要的一大功能是命令执行;我又找到另一个集成专为开发服务的MCP服务:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/wonderwhy-er/DesktopCommanderMCP&quot;&gt;DesktopCommanderMCP&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;它的介绍是给&lt;code&gt;Claude&lt;/code&gt;使用,实际也很配&lt;code&gt;ChatGPT Web&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;因此,本地执行命令就变成了:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  D:\Tools\mcp-auth-proxy.exe `
  -d &quot;D:\Tools\data&quot; `
  --listen 192.168.1.XX:24024 `
  --external-url https://{your-domain} `
  --no-auto-tls  `
  --tls-accept-tos `
  --oidc-configuration-url &quot;https://your-provider.com/.well-known/openid-configuration&quot; `
  --oidc-client-id &quot;your-oidc-client-id&quot; `
  --oidc-client-secret &quot;your-oidc-client-secret&quot; `
  --oidc-allowed-users &quot;user1@example.com,user2@example.com&quot; `
  -- npx.cmd -y @wonderwhy-er/desktop-commander@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PS:同理,后面的GPT官方隧道(tunnel),也可以换这个&lt;code&gt;@wonderwhy-er/desktop-commander@latest&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这个启动命令不能设置允许访问的文件;但可以直接在AI中设置;&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;ChatGPT&lt;/code&gt;网页端配置完成后,直接对AI说 &lt;code&gt;修改配置&lt;/code&gt;即可&lt;/p&gt;
&lt;p&gt;&amp;lt;span id=&quot;desktop-commander-config&quot;&amp;gt;&amp;lt;/span&amp;gt;
&lt;img src=&quot;/img/gpt_mcp_config.png&quot; alt=&quot;change mcp setting&quot; /&gt;&lt;/p&gt;
&lt;p&gt;MCP测试连通性时,推荐&lt;code&gt;POSTMAN&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;下面是之前使用官方tunnel的保留方案:&lt;/p&gt;
&lt;hr /&gt;
&lt;h4&gt;旧版原生 OpenAI API Tunnels方案&lt;/h4&gt;
&lt;p&gt;经测试,免费用户也可以这样做&lt;/p&gt;
&lt;p&gt;不过免费用户不能使用高级模型;意义不大&lt;/p&gt;
&lt;h3&gt;操作步骤&lt;/h3&gt;
&lt;h4&gt;开启开发人员模式&lt;/h4&gt;
&lt;p&gt;打开&lt;a href=&quot;https://chatgpt.com/&quot;&gt;https://chatgpt.com/&lt;/a&gt;,点击左下角头像:&lt;code&gt;设置&lt;/code&gt;→&lt;code&gt;账户安全与登录&lt;/code&gt;→打开&lt;code&gt;开发人员模式&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;span id=&quot;openai-dev-mode&quot;&amp;gt;&amp;lt;/span&amp;gt;
&lt;img src=&quot;/img/chatgpt_dev.png&quot; alt=&quot;chatgpt dev&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;创建GPT隧道(tunnel)&lt;/h4&gt;
&lt;p&gt;打开&lt;code&gt;OpenAI API平台&lt;/code&gt;:设置中的&lt;a href=&quot;https://platform.openai.com/settings/organization/tunnels&quot;&gt;Tunnels - OpenAI API&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;span id=&quot;openai-create-tunnel&quot;&amp;gt;&amp;lt;/span&amp;gt;
&lt;img src=&quot;/img/openai_tunnels.png&quot; alt=&quot;OpenAPI tunnels&quot; /&gt;&lt;/p&gt;
&lt;p&gt;创建一个&lt;code&gt;tunnel&lt;/code&gt;,复制好它的&lt;code&gt;ID&lt;/code&gt; &lt;code&gt;tunnel_6axxxx&lt;/code&gt;备用;&lt;/p&gt;
&lt;p&gt;点击&lt;code&gt;Download tunnel-client&lt;/code&gt;去&lt;code&gt;Github&lt;/code&gt;上下载最新的客户端备用&lt;/p&gt;
&lt;h4&gt;创建隧道专用APIKey&lt;/h4&gt;
&lt;p&gt;还是&lt;code&gt;OpenAI API平台&lt;/code&gt;:设置中的&lt;a href=&quot;https://platform.openai.com/settings/organization/api-keys&quot;&gt;API keys - OpenAI API&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;创建一个专用的&lt;code&gt;APIKEY&lt;/code&gt;权限勾选&lt;code&gt;Tunnels&lt;/code&gt;的&lt;code&gt;Read&lt;/code&gt; &lt;code&gt;Use&lt;/code&gt;;&lt;code&gt;Project&lt;/code&gt;选&lt;code&gt;Defalt project&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;span id=&quot;openai-api-key&quot;&amp;gt;&amp;lt;/span&amp;gt;
&lt;img src=&quot;/img/openai_tunnel_apikey.png&quot; alt=&quot;tunnel api key&quot; /&gt;&lt;/p&gt;
&lt;p&gt;复制生成的&lt;code&gt;sk-proj-IEAXXXX&lt;/code&gt; 这样的&lt;code&gt;APIKey&lt;/code&gt;备用&lt;/p&gt;
&lt;h4&gt;本地连接隧道&lt;/h4&gt;
&lt;p&gt;下载好前面提到的&lt;code&gt;tunnel-client.zip&lt;/code&gt;后解压开;在你期望&lt;code&gt;MCP&lt;/code&gt;访问的目录;按住&lt;code&gt;shift&lt;/code&gt;右键,点击&lt;code&gt;在此处运行powershell&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;由于&lt;code&gt;ChatGPT&lt;/code&gt;不能直连;因此还需要配置&lt;code&gt;socket&lt;/code&gt;代理&lt;/p&gt;
&lt;p&gt;文件路径不知道有什么问题;我发现只能用相对路径,&lt;/p&gt;
&lt;p&gt;填入备用的&lt;code&gt;API Key&lt;/code&gt;和&lt;code&gt;tunnel id&lt;/code&gt;后执行以下命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# OpenAI 控制面走代理
$env:CONTROL_PLANE_HTTP_PROXY = &quot;http://127.0.0.1:7890&quot;

# 原有参数
$env:CONTROL_PLANE_API_KEY = &quot;sk-proj-你的API密钥&quot;
$env:CONTROL_PLANE_TUNNEL_ID = &quot;tunnel_6xxxx&quot;

#直接将当前目录设为mcp可访问
C:\Applications\Tools\tunnel-client.exe run  --mcp.command &apos;cmd.exe /c npx.cmd -y @modelcontextprotocol/server-filesystem &quot;.&quot;&apos;

#或者使用集成的带文件访问及命令执行的desktop-commander,二选一
C:\Applications\Tools\tunnel-client.exe run  --mcp.command &apos;cmd.exe /c npx.cmd -y @wonderwhy-er/desktop-commander@latest&apos;


# http://127.0.0.1:8080/ui#logs
  
# C:\Users\王帅\AppData\Roaming\tunnel-client  

# C:\Applications\Tools\tunnel-client.exe init `
#   --sample sample_mcp_stdio_local `
#   --profile gmail-tunnel `
#   --tunnel-id $([Environment]::GetEnvironmentVariable(&quot;CONTROL_PLANE_TUNNEL_ID&quot;,&quot;User&quot;)) `
#   --mcp.command &apos;cmd.exe /c npx.cmd -y @modelcontextprotocol/server-filesystem &quot;.&quot;&apos;

 # $env:CONTROL_PLANE_HTTP_PROXY=[Environment]::GetEnvironmentVariable(&quot;CONTROL_PLANE_HTTP_PROXY&quot;,&quot;User&quot;); $env:CONTROL_PLANE_API_KEY=[Environment]::GetEnvironmentVariable(&quot;CONTROL_PLANE_API_KEY&quot;,&quot;User&quot;); $env:CONTROL_PLANE_TUNNEL_ID=[Environment]::GetEnvironmentVariable(&quot;CONTROL_PLANE_TUNNEL_ID&quot;,&quot;User&quot;); tunnel-client run --profile gmail-tunnel 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;假如有什么不懂或报错直接问&lt;code&gt;ChatGPT&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;执行成功后打开网页:&lt;a href=&quot;http://127.0.0.1:8080/ui#logs&quot;&gt;http://127.0.0.1:8080&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;正常右上角会显示&lt;code&gt;Health:live&lt;/code&gt;和&lt;code&gt;Ready:ready&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;span id=&quot;openai-local-tunnel&quot;&amp;gt;&amp;lt;/span&amp;gt;
&lt;img src=&quot;/img/openai_tunnel_ready.png&quot; alt=&quot;openai tunnel ready&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;新建MCP插件(Plugin)&lt;/h4&gt;
&lt;p&gt;回到&lt;code&gt;GPT Web&lt;/code&gt;打开&lt;a href=&quot;https://chatgpt.com/plugins&quot;&gt;插件页面&lt;/a&gt;;点击右边的+新增插件&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;:要先执行前面的命令,本地启动MCP服务之后再创建&lt;code&gt;Plugin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;隧道&lt;/code&gt;选择刚刚创建的&lt;/p&gt;
&lt;p&gt;&lt;code&gt;身份验证&lt;/code&gt;我直接用的&lt;code&gt;none&lt;/code&gt;(未授权)&lt;/p&gt;
&lt;p&gt;勾选下方的&lt;code&gt;我了解并希望继续&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;span id=&quot;openai-add-plugin&quot;&amp;gt;&amp;lt;/span&amp;gt;
&lt;img src=&quot;/img/chatgpt_add_plugin.png&quot; alt=&quot;chatgpt add plugin&quot; /&gt;&lt;/p&gt;
&lt;p&gt;创建成功后点击&lt;code&gt;连接&lt;/code&gt;;&lt;/p&gt;
&lt;p&gt;后续就可以在&lt;code&gt;插件&lt;/code&gt;中看到已安装的&lt;code&gt;个人&lt;/code&gt;插件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/gpt_person_plugin.png&quot; alt=&quot;person plugin&quot; /&gt;&lt;/p&gt;
&lt;p&gt;也可以在&lt;code&gt;设置&lt;/code&gt;的&lt;code&gt;插件&lt;/code&gt;中查看&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;ChatGPT Web&lt;/code&gt;端的插件中;如果能看到&lt;code&gt;MCP&lt;/code&gt;支持的操作;则表明一切成功;如果看不到可以点击右上角设置→&lt;code&gt;重新连接&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/chatgpt_plugin_mcp_success.png&quot; alt=&quot;chatgpt_plugin_mcp_success&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;测试&lt;/h4&gt;
&lt;p&gt;在&lt;code&gt;ChatGPT web&lt;/code&gt;上新开一个聊天;选择&lt;code&gt;个人&lt;/code&gt;插件让它调用&lt;code&gt;MCP&lt;/code&gt;返回文件列表测试最终结果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/chatgpt_plugin_test.png&quot; alt=&quot;chatgpt_plugin_test&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;恭喜你,学会了ChatGPT的插件开发!&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;添加插件报错:&lt;code&gt;创建连接器时出错&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Something went wrong. If this issue persists please contact us through our help center at help.openai.com
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/chatpgt_sometingwrong.png&quot; alt=&quot;gptplugin_something_wrong&quot; /&gt;&lt;/p&gt;
&lt;p&gt;遇到这个错误,就是前面说的;&lt;code&gt;MCP&lt;/code&gt;插件无法通过&lt;code&gt;tunnel&lt;/code&gt;连接到服务器导致的;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方法就是先执行tunnel-client.exe run命令创建好MCP服务再添加插件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过&lt;code&gt;F12&lt;/code&gt;检查:&lt;code&gt;https://chatgpt.com/backend-api/aip/connectors/mcp&lt;/code&gt;的&lt;code&gt;response&lt;/code&gt;返回:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;detail&quot;: {
        &quot;type&quot;: &quot;mcp_error&quot;,
        &quot;developer_message&quot;: &quot;MCP SSE probe returned 404 from openai.org&quot;,
        &quot;mcp_url&quot;: &quot;https://tunnel-service.gateway.unified-0.internal.api.openai.org/v1/mcp/tunnel_6aXXXXX&quot;,
        &quot;kind&quot;: &quot;invalid_response&quot;,
        &quot;upstream_status&quot;: 404,
        &quot;response_content_type&quot;: &quot;application/json&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即可知道原因&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每次断开&lt;code&gt;tunnel&lt;/code&gt;客户端后假如遇到访问问题&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在&lt;code&gt;设置&lt;/code&gt;→&lt;code&gt;插件&lt;/code&gt;→选择个人插件→右上角菜单→&lt;code&gt;重新连接&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/chatgpt_plugin_reconnect.png&quot; alt=&quot;chatgpt plugin reconnect&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;要是还遇到什么奇怪的问题,可以把GPT的MCP插件删了重新添加&lt;/li&gt;
&lt;li&gt;测试MCP服务可用性方法:使用新版&lt;code&gt;POSTMAN&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;添加一个&lt;code&gt;MCP&lt;/code&gt;服务;输入mcp服务地址(通常以&lt;code&gt;/mcp&lt;/code&gt;结尾);&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/postman_mcp_test.png&quot; alt=&quot;postman_mcp_test&quot; /&gt;&lt;/p&gt;
&lt;p&gt;首次连接时会启动OAuth认证流程,点击按钮弹窗授权;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/postman_mcp_auth.png&quot; alt=&quot;postman_mcp_auth&quot; /&gt;&lt;/p&gt;
&lt;p&gt;连接成功后能在&lt;code&gt;Message&lt;/code&gt;的&lt;code&gt;Tools&lt;/code&gt;中能看到支持的所有操作;点击&lt;code&gt;Run&lt;/code&gt;即可 测试&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://linux.do/t/topic/2259880&quot;&gt;通过 Coding Tools MCP 最大化利用 GPT 订阅额度，解决额度焦虑&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chatgpt.com/&quot;&gt;&lt;code&gt;ChatGPT&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>王者荣耀世界优化着色器报错解决办法</title><link>https://blog.wangshuai.app/2026-07-11-%E7%8E%8B%E8%80%85%E8%8D%A3%E8%80%80%E4%B8%96%E7%95%8C%E4%BC%98%E5%8C%96%E7%9D%80%E8%89%B2%E5%99%A8%E6%8A%A5%E9%94%99%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95/</link><guid isPermaLink="true">https://blog.wangshuai.app/2026-07-11-%E7%8E%8B%E8%80%85%E8%8D%A3%E8%80%80%E4%B8%96%E7%95%8C%E4%BC%98%E5%8C%96%E7%9D%80%E8%89%B2%E5%99%A8%E6%8A%A5%E9%94%99%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95/</guid><pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近启动《王者荣耀世界》时，在优化着色器的过程中遇到了游戏崩溃。&lt;/p&gt;
&lt;p&gt;报错内容如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Fatal error: [File:Unknown] [Line: 934] 
Rendering thread exception:
Fatal error: [File:Unknown] [Line: 1182] 
ShaderCodeArchive::DecompressShader(): Could not decompress shader (GetShaderCompressionFormat=Oodle). The CPU (13th Gen Intel(R) Core(TM) i7-13700K) may be unstable; for details see http://www.radgametools.com/oodleintel.htm


0x00007ffba9a21c0a KERNELBASE.dll!UnknownFunction []
0x00000001468640f1 NGR-Win64-Shipping.exe!UnknownFunction []
0x0000000146866e9c NGR-Win64-Shipping.exe!UnknownFunction []
0x0000000141a11581 NGR-Win64-Shipping.exe!UnknownFunction []
0x000000014682dff2 NGR-Win64-Shipping.exe!UnknownFunction []
0x0000000144166640 NGR-Win64-Shipping.exe!UnknownFunction []
0x0000000142a4de40 NGR-Win64-Shipping.exe!UnknownFunction []
0x00000001446e23d5 NGR-Win64-Shipping.exe!UnknownFunction []
0x0000000141067b8e NGR-Win64-Shipping.exe!UnknownFunction []
0x0000000141065f95 NGR-Win64-Shipping.exe!UnknownFunction []
0x0000000141379b11 NGR-Win64-Shipping.exe!UnknownFunction []
0x0000000141379cb0 NGR-Win64-Shipping.exe!UnknownFunction []
0x0000000141067d6d NGR-Win64-Shipping.exe!UnknownFunction []
0x000000014106a43f NGR-Win64-Shipping.exe!UnknownFunction []
0x000000014106a1b5 NGR-Win64-Shipping.exe!UnknownFunction []
0x0000000142a4841f NGR-Win64-Shipping.exe!UnknownFunction []
0x00000001417e091d NGR-Win64-Shipping.exe!UnknownFunction []
0x00000001417e02b2 NGR-Win64-Shipping.exe!UnknownFunction []
0x00000001417e01ca NGR-Win64-Shipping.exe!UnknownFunction []
0x000000014145c024 NGR-Win64-Shipping.exe!UnknownFunction []
0x000000014145beda NGR-Win64-Shipping.exe!UnknownFunction []
0x00007ffbab75e957 KERNEL32.DLL!UnknownFunction []
0x00007ffbac7ead6c ntdll.dll!UnknownFunction []
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/img/wangzhe-world-shader-crash.png&quot; alt=&quot;王者荣耀世界优化着色器报错&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这问题从6月二十多号的着色器优化出现;等它两周了还没解决;不知道这些人怎么搞的&lt;/p&gt;
&lt;h3&gt;问题原因&lt;/h3&gt;
&lt;p&gt;这个问题主要出现在使用13、14代&lt;code&gt;Intel CPU&lt;/code&gt;中,因为众所周知原因;这两代CPU 缩肛了，报错弹窗中也会提示CPU可能不稳定。理论上可以通过给CPU降频解决。&lt;/p&gt;
&lt;p&gt;我之前玩&lt;code&gt;SC2&lt;/code&gt;时也遇到过13、14代&lt;code&gt;Intel CPU&lt;/code&gt;导致的游戏崩溃，最后通过降频解决&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;&lt;s&gt;解决方法就是在游戏启动器的设置中清理着色器缓存，然后重新启动游戏，让游戏重新生成着色器缓存。&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;这个解决方案不行,多试几次后依旧会报错;最终还是通过降低电压成功进游戏的:&lt;/p&gt;
&lt;p&gt;我是&lt;code&gt;Micro-Star PRO Z690-A WIFI DDR4&lt;/code&gt;主板,在性能中做如下设置:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;关闭&lt;code&gt;Intel SpeedStep&lt;/code&gt;和&lt;code&gt;Intel Speed Shift&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;设置&lt;code&gt;p-core ratio&lt;/code&gt; 从&lt;code&gt;+1&lt;/code&gt; 到 &lt;code&gt;-2&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;清理前备份&lt;/h4&gt;
&lt;p&gt;着色器缓存文件位于游戏目录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\Program Files\KingLauncher\HOKWorld\WindowsNoEditor\NGR\PSOCache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;清理前可以将整个&lt;code&gt;PSOCache&lt;/code&gt;文件夹复制到其它位置备份。如果清理后出现其它问题，可以再将备份文件恢复到原目录。&lt;/p&gt;
&lt;h4&gt;操作步骤&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;打开《王者荣耀世界》启动器，进入&lt;code&gt;设置&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在左侧选择&lt;code&gt;更新修复&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;找到&lt;code&gt;着色器缓存&lt;/code&gt;，点击&lt;code&gt;一键清理&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/wangzhe-world-clear-shader-cache.png&quot; alt=&quot;清理王者荣耀世界着色器缓存&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;清理完成后重新启动游戏，等待游戏再次优化着色器&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;p&gt;清理后着色器优化还是有可能失败，但成功率会大大提高;我试了三次就成功了。&lt;/p&gt;
</content:encoded></item><item><title>微信小程序导航站要点解析</title><link>https://blog.wangshuai.app/2026-05-30-%E5%A6%82%E4%BD%95%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%AF%BC%E8%88%AA%E7%AB%99/</link><guid isPermaLink="true">https://blog.wangshuai.app/2026-05-30-%E5%A6%82%E4%BD%95%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%AF%BC%E8%88%AA%E7%AB%99/</guid><pubDate>Sat, 30 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;工作有开发微信小程序的内容,而PC微信上的微信浏览器和小程序首页不尽如人意 (打开不方便,首页有广告和其它内容)&lt;/p&gt;
&lt;p&gt;因此结合之前的经验以及最近的发现;写了一个方便的小程序导航页;此为单HTML页面&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/minip_navi.png&quot; alt=&quot;minip_navi&quot; /&gt;&lt;/p&gt;
&lt;p&gt;主要功能有:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;体验版小程序跳转&lt;/li&gt;
&lt;li&gt;小程序码自动生成&lt;/li&gt;
&lt;li&gt;Chrome与手机双支持&lt;/li&gt;
&lt;li&gt;自定义跳转参数和页面&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;打开微信浏览器这最后一公里用到了微信的截图识别二维码功能&lt;/p&gt;
&lt;p&gt;代码请看:https://gist.github.com/wangshuai-007/1dcdc5c09ba7d7e2fb3a0e2442800cbd&lt;/p&gt;
&lt;h3&gt;配置说明&lt;/h3&gt;
&lt;p&gt;配置主要为变量&lt;code&gt;miniProgramGroups&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;示例大概是这样的:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  const miniProgramGroups = {   
        &quot;常用&quot;: {
          &quot;wxcff7381e631cf54e&quot;: { name: &quot;微信开发者助手&quot;, icon: &quot;https://developers.weixin.qq.com/community/ngi/personal/suncode/wxcff7381e631cf54e&quot;,prodUrl:&quot;weixin://dl/business/?appid=wxcff7381e631cf54e&amp;amp;path=pages/index/index&quot; , testUrl: &quot;https://open.weixin.qq.com/sns/getexpappinfo?appid=wxcff7381e631cf54e&quot;},   
        },
        &quot;极简&quot;:{
          &quot;wxcff7381e631cf54e&quot;:{
            name:&quot;极简小程序&quot;,
          }
        }      
      };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;支持的参数有:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;必输&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;显示的名称&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;icon&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;图标,默认用小程序码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;testUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;体验版二维码地址,可以从小程序后台扫码后解析得到&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prodUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;正式二维码地址,一般不需要;只在需要用参数打开页面时有用;具体参数说明请查看微信文档:&lt;a href=&quot;https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/url-scheme.html&quot;&gt;获取 URL Scheme&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;要点解析&lt;/h3&gt;
&lt;p&gt;之前写过一篇文章:&lt;a href=&quot;/2026-01-10-open-wechat-miniprogram-by-url/&quot;&gt;通过url打开微信小程序&lt;/a&gt;介绍了 &lt;code&gt;URL Scheme&lt;/code&gt;和&lt;code&gt;PC快捷方式&lt;/code&gt;打开小程序的方法;这里介绍其它的要点&lt;/p&gt;
&lt;h4&gt;打开微信浏览器&lt;/h4&gt;
&lt;p&gt;日常开发使用的chrome;但体验版微信小程序由于有认证必须要在微信浏览器里面才行;所以使用微信浏览器打开指定的链接是最难的部分;&lt;/p&gt;
&lt;p&gt;解决方法是&lt;code&gt;微信截图&lt;/code&gt;;&lt;/p&gt;
&lt;p&gt;聊天记录中的二维码可以直接在微信里面识别 ;微信截图工具也带了这个功能&lt;/p&gt;
&lt;p&gt;只要截图中有二维码,就可以直接使用微信浏览器打开链接或小程序&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/wechat_snip_qrcode.png&quot; alt=&quot;微信截图识别二维码&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;小程序码&lt;/h4&gt;
&lt;p&gt;官方文档中正常打开一个小程序需要有&lt;code&gt;明文URL Scheme&lt;/code&gt;;这个需要在后台给每个小程序单独开通设置;&lt;/p&gt;
&lt;p&gt;还有一个方法就是&lt;code&gt;小程序码&lt;/code&gt;;不过&lt;code&gt;小程序码&lt;/code&gt;是微信私有的算法;网上暂无公开生成方法;但是我找到一个官方的小程序码地址:&lt;code&gt;https://developers.weixin.qq.com/community/ngi/personal/suncode/wxcff7381e631cf54e&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;有了这个地址后,搭配微信截图识别;就可以打开任意小程序了&lt;/p&gt;
&lt;h4&gt;微信开发者助手&lt;/h4&gt;
&lt;p&gt;导航网站一开始我放了&lt;code&gt;微信开发者助手&lt;/code&gt;这个小程序;这个小程序是微信官方开发的;里面可以看到自己管理或体验的所有小程序;还能看到每个人提交的&lt;code&gt;预览版&lt;/code&gt;版本;&lt;/p&gt;
&lt;p&gt;里面可以看到所有的&lt;code&gt;appid&lt;/code&gt;;如果需要知道其它小程序的&lt;code&gt;appid&lt;/code&gt;;在手机小程序的&lt;code&gt;更多资料&lt;/code&gt;中查看&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/wexin_appid.png&quot; alt=&quot;app_id&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.weixin.qq.com/community/develop/doc/000c68ef3d0fa007a37ab5dc656c0c&quot;&gt;小程序助手-微信开放社区&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>tiktok mod 的下载</title><link>https://blog.wangshuai.app/2026-04-11-tik-tok-mod%E7%9A%84%E4%B8%8B%E8%BD%BD/</link><guid isPermaLink="true">https://blog.wangshuai.app/2026-04-11-tik-tok-mod%E7%9A%84%E4%B8%8B%E8%BD%BD/</guid><pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;本人喜欢学习英语;我比较喜欢的英语学习方法就是刷&lt;code&gt;TikTok&lt;/code&gt;;使用梯子可以直接下载到&lt;code&gt;TikTok&lt;/code&gt;;但是却不能刷(其它功能没问题,但首页里面是空白)&lt;/p&gt;
&lt;p&gt;因为&lt;code&gt;TikTok&lt;/code&gt;有严格的网络限制;解决方案就是使用俄罗斯大神破解的&lt;code&gt;TikTokMod&lt;/code&gt;;官方网站在电报&lt;code&gt;Telegram&lt;/code&gt;中:&lt;a href=&quot;https://t.me/TikTokModCloud&quot;&gt;TikTokMod&lt;/a&gt;;本文介绍下载方法&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;h4&gt;Telegram下载&lt;/h4&gt;
&lt;p&gt;直接的下载方法是直接在电报频道中下载,在频道中点击&lt;code&gt;置顶消息&lt;/code&gt;;然后下载安装即可&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/tiktokmodcloud_download.jpg&quot; alt=&quot;TikTokMod&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;Fylio下载&lt;/h4&gt;
&lt;p&gt;假如没有安装&lt;code&gt;Telegram&lt;/code&gt;或者没找到下载地址,可以从网页中下载;&lt;code&gt;TikTokMod&lt;/code&gt;和&lt;code&gt;Fylio&lt;/code&gt;有合作,你看网站广告可以直接从网页下载&lt;/p&gt;
&lt;p&gt;只是这个网站下载有点费事,本文主要介绍下载方法&lt;/p&gt;
&lt;p&gt;PS:网页下载的话推荐&lt;strong&gt;电脑&lt;/strong&gt;上操作,因为PC可以使用插件拦截页面的广告&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;网页地址从&lt;code&gt;Telegram&lt;/code&gt;中的频道找到最新的版本消息&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;点击&lt;code&gt;Скачать мод ТикТок(Fylio)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;就是第一个链接,&lt;code&gt;44.6.4&lt;/code&gt;的链接就是:&lt;a href=&quot;https://fylio.com/d/e4bff103-9d75-42b9-8b97-40e56efebaad&quot;&gt;https://fylio.com/d/e4bff103-9d75-42b9-8b97-40e56efebaad&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/tg_tk_fy.png&quot; alt=&quot;telegram的fy下载链接&quot; /&gt;&lt;/p&gt;
&lt;p&gt;假如进不去&lt;code&gt;Telegram&lt;/code&gt;,有一个固定的链接:&lt;a href=&quot;https://apkw.ru/en/download/tik-tok-mod/&quot;&gt;APKW.RU TikTokMod&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如果你开了梯子,点进去通常会报错:&lt;code&gt;Forbidden&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;You don&apos;t have permission to access this resource.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/apkw.ru_forbidden.png&quot; alt=&quot;APKW.RU禁止访问&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这个网站访问策略很严格,大部分梯子都会禁止访问;实测我用&lt;code&gt;英国&lt;/code&gt;的节点可以访问;我猜测俄罗斯节点应该能直接访问;假如有的话也可以试试&lt;/p&gt;
&lt;p&gt;没有&lt;code&gt;英国&lt;/code&gt;节点也不用灰心;我测试咱们国家的IP可以访问;所以,此时你把&lt;code&gt;Clash&lt;/code&gt;的代理模式设置为&lt;code&gt;直连&lt;/code&gt;;&lt;/p&gt;
&lt;p&gt;打开后在网页的最后,就有最新版的&lt;code&gt;Fylio&lt;/code&gt;下载地址:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/apkw.ru_tkmod.png&quot; alt=&quot;apkw.ru的&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里,我用我的URL做了一个301跳转,你也可以直接访问这个:&lt;a href=&quot;https://url.wangshuai.app/tkmod&quot;&gt;wangshuai的URL:TKMod&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Fylio&lt;/code&gt;页面下载&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;到这里就是实际的下载页面;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/fylio_tkmod_download.png&quot; alt=&quot;fylio下载方法&quot; /&gt;&lt;/p&gt;
&lt;p&gt;点击&lt;code&gt;Get file&lt;/code&gt;会出现三个任务&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/fylio_download_task.png&quot; alt=&quot;完成任务获得链接&quot; /&gt;&lt;/p&gt;
&lt;p&gt;点击任意一个链接,然后把它关闭;返回回来;会出现一个60s倒计时&lt;/p&gt;
&lt;p&gt;等待60s就会出现&lt;code&gt;Download file&lt;/code&gt;的按钮;点击后就会开始下载&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/fy_download_save.png&quot; alt=&quot;开始下载&quot; /&gt;&lt;/p&gt;
&lt;p&gt;等待下方的进度条完成;点击&lt;code&gt;Save&lt;/code&gt;就下载成功了&lt;/p&gt;
</content:encoded></item><item><title>PVE安装OPENCLAW的简明教程</title><link>https://blog.wangshuai.app/2026-02-23-pve%E5%AE%89%E8%A3%85openclaw%E5%8F%8A%E4%B8%80%E4%BA%9B%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9/</link><guid isPermaLink="true">https://blog.wangshuai.app/2026-02-23-pve%E5%AE%89%E8%A3%85openclaw%E5%8F%8A%E4%B8%80%E4%BA%9B%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9/</guid><pubDate>Mon, 23 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/openclaw/openclaw&quot;&gt;openclaw&lt;/a&gt;流行的安装方法是&lt;code&gt;Mac Mini&lt;/code&gt;,本文介绍我摸索的&lt;code&gt;PVE&lt;/code&gt;(&lt;code&gt;Proxmox VE&lt;/code&gt;)安装&lt;code&gt;OpenClaw&lt;/code&gt;的实践心得&lt;/p&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;h4&gt;安装英文原版&lt;/h4&gt;
&lt;p&gt;虽然&lt;a href=&quot;https://github.com/openclaw/openclaw&quot;&gt;openclaw&lt;/a&gt;现在有不少汉化或者中文的教程,不过由于这个网关是国外弄的我还是建议使用&lt;strong&gt;英文安装&lt;/strong&gt;,不要选中文OS或者中文的版本(我昨天编译安卓的&lt;code&gt;OpenClaw Node apk&lt;/code&gt;就发现没有&lt;code&gt;100%&lt;/code&gt;适配中文)不然就只能&lt;strong&gt;徒增烦恼&lt;/strong&gt;了&lt;/p&gt;
&lt;h4&gt;使用&lt;code&gt;VM&lt;/code&gt;安装&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/openclaw/openclaw&quot;&gt;openclaw&lt;/a&gt;由于破坏性强,因此需要独立安装,我选用的是独立&lt;code&gt;VM&lt;/code&gt;的&lt;code&gt;ubuntu-24.04.1-live-server-amd64.iso&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;安装&lt;code&gt;ubuntu&lt;/code&gt;没什么好说的,使用默认配置即可;&lt;/p&gt;
&lt;p&gt;安装成功后记下它的IP&lt;code&gt;192.168.xx.xx&lt;/code&gt;;同时在安装时我选择导入了我&lt;code&gt;github&lt;/code&gt;账号的&lt;code&gt;ssh key&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;安装前配置代理&lt;/h4&gt;
&lt;p&gt;安装&lt;code&gt;Open Claw&lt;/code&gt;也可以直接用&lt;a href=&quot;https://docs.openclaw.ai/zh-CN/start/getting-started&quot;&gt;官方文档&lt;/a&gt;;使用命令:&lt;code&gt;curl -fsSL https://openclaw.ai/install.sh | bash&lt;/code&gt;一键安装即可;根据提示选择或输入&lt;/p&gt;
&lt;p&gt;就是安装前应该要先配置代理&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export https_proxy=http://192.168.XX.XX:7890;
export http_proxy=http://192.168.XX.XX:7890;
curl google.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后用google测一下连通性,出现&lt;code&gt;301 Moved&lt;/code&gt;的&lt;code&gt;HTML&lt;/code&gt;表面代理有效&lt;/p&gt;
&lt;p&gt;上面是临时方法,假如不想要每次都输入;可以修改环境变量,将&lt;code&gt;http_proxy&lt;/code&gt;附加到&lt;code&gt;/etc/enviroment&lt;/code&gt;末尾&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo vim /etc/environment

#enviroment
http_proxy=http://192.168.XX.XX:7890
https_proxy=http://192.168.XX.XX:7890
HTTPS_PROXY=http://192.168.XX.XX:7890
HTTP_PROXY=http://192.168.XX.XX:7890
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ssh穿透访问&lt;/h4&gt;
&lt;p&gt;安装&lt;code&gt;Open Claw&lt;/code&gt;成功后执行&lt;code&gt;openclaw onboard&lt;/code&gt;命令配置基本的联网方式和模型;默认连接方法是&lt;code&gt;loopback&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;安装成功后执行&lt;code&gt;openclaw&lt;/code&gt;命令查看支持的命令及当前服务信息&lt;/p&gt;
&lt;p&gt;根据提示,在你的电脑上执行&lt;code&gt;ssh&lt;/code&gt;命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -N -i C:\Users\[user]\.ssh\ssh_key -L 18789:127.0.0.1:18789 [user]@192.168.xx.xx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里会把&lt;code&gt;pve&lt;/code&gt;虚拟机(&lt;code&gt;192.168.xx.xx&lt;/code&gt;)的&lt;code&gt;18789&lt;/code&gt;端口映射到本地;这样你就能在浏览器中使用&lt;code&gt;http://localhost:18789/&lt;/code&gt;打开它的网页了(在配置外网访问之前,每次在本机访问&lt;code&gt;Open Claw&lt;/code&gt;之前都需要创建&lt;code&gt;ssh&lt;/code&gt;隧道映射)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ssh_key&lt;/code&gt;是我&lt;code&gt;github&lt;/code&gt;上的&lt;code&gt;ssh&lt;/code&gt;私钥&lt;/p&gt;
&lt;p&gt;默认的访问方式需要在Gui中配置&lt;code&gt;Gateway Token&lt;/code&gt;;这个在初次配置完后在命令行输出中有显示&lt;/p&gt;
&lt;h4&gt;配置&lt;code&gt;Gateway&lt;/code&gt;的&lt;code&gt;Bind&lt;/code&gt;方式(&lt;strong&gt;外网访问&lt;/strong&gt;)&lt;/h4&gt;
&lt;p&gt;外网访问配置在&lt;code&gt;Config&lt;/code&gt;→&lt;code&gt;Gateway&lt;/code&gt;→&lt;code&gt;Bind&lt;/code&gt;中&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/openclaw_gateway_bind.png&quot; alt=&quot;image-20260223014536304&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我把它改为了&lt;code&gt;lan&lt;/code&gt;;&lt;/p&gt;
&lt;p&gt;实际这里假如你的&lt;code&gt;Agents&lt;/code&gt;中&lt;code&gt;model&lt;/code&gt;用的在线模型,可以直接在&lt;code&gt;Chat&lt;/code&gt;中让AI帮你改配置&lt;/p&gt;
&lt;p&gt;这里就体现了它强大的地方;可以自己改自己的设置;&lt;/p&gt;
&lt;p&gt;当然,在改的过程中有好几次都让后台挂了;&lt;/p&gt;
&lt;h5&gt;配置Control Ui&lt;/h5&gt;
&lt;p&gt;除了修改&lt;code&gt;Bind&lt;/code&gt;还需要添加&lt;code&gt;Control Ui&lt;/code&gt;,将&lt;code&gt;https&lt;/code&gt;的域名配置上&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/openclaw_gateway_controlui.png&quot; alt=&quot;配置外网访问&quot; /&gt;&lt;/p&gt;
&lt;h5&gt;设置反向代理允许&lt;code&gt;websocket&lt;/code&gt;连接&lt;/h5&gt;
&lt;p&gt;以及,我用的&lt;code&gt;nginx&lt;/code&gt;代理,要允许&lt;code&gt;websocket&lt;/code&gt;连接&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    location / {
        proxy_pass http://192.168.xxx.xx:18789/;

    	#支持websockcet连接
        proxy_http_version 1.1;
    	proxy_set_header Upgrade $http_upgrade;
		proxy_cache_bypass $http_upgrade;
		proxy_connect_timeout 10s;
    	proxy_set_header Connection $http_connection;
     
        # Tinyauth auth request
        auth_request /tinyauth;
        error_page 401 = @tinyauth_login;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;配置网页外部保护(可选)&lt;/h5&gt;
&lt;p&gt;同时,为了保护&lt;code&gt;Open Claw&lt;/code&gt;后台,我使用了&lt;code&gt;Tinyauth&lt;/code&gt;,详情可参考文章:&lt;a href=&quot;https://blog.dejavu.moe/posts/build-passkeys-identity-system-with-pocketid-x-tinyauth/&quot;&gt;自托管部署 Pocket ID 与 Tinyauth 完全指南&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;后台授权同意&lt;/h5&gt;
&lt;p&gt;配置成功后打开后台会提示:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;disconnected (1008): pairing required&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;因此还需要后台授权同意:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#查看当前授权列表
openclaw devices list
#同意设备
openclaw devices approve XXX
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;后台挂了后重启Gui方法&lt;/h4&gt;
&lt;p&gt;假如&lt;code&gt;localhost:18789&lt;/code&gt;访问不到网页,在后台中执行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openclaw gateway --force
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;命令可以重置网关绑定状态让你可以重新访问&lt;/p&gt;
&lt;h4&gt;重置设置方法&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Open Claw&lt;/code&gt;的配置位于:&lt;code&gt;~/.openclaw/openclaw.json&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在AI自己修改自己的配置过程中,有很多次它都让服务挂了;或者有什么配置不对的地方导致服务出现问题&lt;/p&gt;
&lt;p&gt;此时根据后台提示,可以先尝试命令修复&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openclaw doctor --fix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是这个网关自带的修复;假如修复后还是有问题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd ~/.openclaw
ls
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时你能看到它自动备份了设置,找到最新的一个备份&lt;code&gt;openclaw.json.bak&lt;/code&gt;将当前的设置覆盖后即可恢复&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/openclaw_config_back.png&quot; alt=&quot;image-20260223020117835&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;模型切换&lt;/h4&gt;
&lt;p&gt;网关已配置的模型在&lt;code&gt;Agents&lt;/code&gt;→&lt;code&gt;Overview&lt;/code&gt;中&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/openclaw_agents.png&quot; alt=&quot;已配置的模型&quot; /&gt;&lt;/p&gt;
&lt;p&gt;当有多个模型需要切换时,在&lt;code&gt;Chat&lt;/code&gt;中输入&lt;code&gt;/model&lt;/code&gt;查看模型相关命令&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/openclaw_chat_model.png&quot; alt=&quot;切换模型&quot; /&gt;&lt;/p&gt;
&lt;p&gt;输入&lt;code&gt; /model &amp;lt;provider/model&amp;gt;&lt;/code&gt;切换&lt;/p&gt;
&lt;h4&gt;对接飞书&lt;/h4&gt;
&lt;p&gt;在首次运行的向导中,我添加了飞书的&lt;code&gt;appId&lt;/code&gt;和&lt;code&gt;appSecret&lt;/code&gt;但它无法运行;总会有两个报错&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;15:31:33 [plugins] plugins.allow is empty; discovered non-bundled plugins may auto-load: feishu (/home/shiney/.openclaw/extensions/feishu/index.ts). Set plugins.allow to explicit trusted ids.
15:31:35 [plugins] feishu failed to load from /home/shiney/.openclaw/extensions/feishu/index.ts: Error: Cannot find module &apos;@sinclair/typebox&apos;
Require stack:
- /home/shiney/.openclaw/extensions/feishu/src/bitable.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里有两个问题需要解决&lt;/p&gt;
&lt;h5&gt;安装飞书插件失败&lt;/h5&gt;
&lt;p&gt;安装失败的原因是飞书的&lt;code&gt;package.json&lt;/code&gt;有问题&lt;/p&gt;
&lt;p&gt;通过以下命令重新安装(&lt;code&gt;npm install&lt;/code&gt;)飞书插件即可看到报错&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shiney@openclaw:~$ cd ~/.openclaw/extensions/feishu
shiney@openclaw:~/.openclaw/extensions/feishu$ ls
index.ts  openclaw.plugin.json  package.json  skills  src
shiney@openclaw:~/.openclaw/extensions/feishu$ npm install
npm error code EUNSUPPORTEDPROTOCOL
npm error Unsupported URL Type &quot;workspace:&quot;: workspace:*
npm notice
npm notice New major version of npm available! 10.9.4 -&amp;gt; 11.10.1
npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.10.1
npm notice To update run: npm install -g npm@11.10.1
npm notice
npm error A complete log of this run can be found in: /home/shiney/.npm/_logs/2026-02-24T15_34_28_116Z-debug-0.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改&lt;code&gt;~/.openclaw/extensions/feishu/package.json&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;删除~~&quot;openclaw&quot;: &quot;workspace:*&quot;~~这一行&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/openclaw_feishu_packagejson_error.png&quot; alt=&quot;删除异常配置&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后执行以下命令:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd ~/.openclaw/extensions/feishu
# https://www.cnblogs.com/pautcher/articles/19585433
npm install -g pnpm
pnpm setup

npm install

openclaw plugins install @m1heng-clawd/feishu
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时,执行&lt;code&gt;openclaw&lt;/code&gt;会提示这样的警告&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shiney@openclaw:~$ openclaw
15:42:31 [plugins] plugins.allow is empty; discovered non-bundled plugins may auto-load: feishu (/home/shiney/.openclaw/extensions/feishu/index.ts). Set plugins.allow to explicit trusted ids.
15:42:34 [plugins] feishu: loaded without install/load-path provenance; treat as untracked local code and pin trust via plugins.allow or install records (/home/shiney/.openclaw/extensions/feishu/index.ts)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;plugins.allow is empty&lt;/code&gt;意思是扩展没启动,因为我之前安装失败,&lt;code&gt;feishu&lt;/code&gt;初始化向导中断导致配置没写入;按照&lt;a href=&quot;https://docs.openclaw.ai/zh-CN/channels/feishu&quot;&gt;官方文档&lt;/a&gt;重新配置一下就行&lt;/p&gt;
&lt;h5&gt;因为安装失败导致配置异常&lt;/h5&gt;
&lt;p&gt;参照官方的飞书配置文档:&lt;a href=&quot;https://docs.openclaw.ai/zh-CN/channels/feishu&quot;&gt;消息平台:飞书&lt;/a&gt;依次设置即可&lt;/p&gt;
&lt;p&gt;这里有两个注意点:&lt;/p&gt;
&lt;h6&gt;自建应用审核&lt;/h6&gt;
&lt;p&gt;因为我之前发布过飞书自建应用,重新设置时要重新审核;审核的话在手机飞书APP找到&lt;code&gt;开发者小助手&lt;/code&gt;会提示操作方法;&lt;/p&gt;
&lt;p&gt;网页打开&lt;code&gt;飞书管理后台&lt;/code&gt;→&lt;code&gt;工作台&lt;/code&gt;→&lt;code&gt;应用审核&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/feishu_app_approve.png&quot; alt=&quot;飞书自建应用审核&quot; /&gt;&lt;/p&gt;
&lt;h6&gt;自建应用对话&lt;/h6&gt;
&lt;p&gt;审核成功后,手机飞书中会出现你创建的&lt;code&gt;openclaw&lt;/code&gt;机器人;发送任意消息会触发审核操作,复制命令(&lt;code&gt;openclaw pairing approve feishu XXXX&lt;/code&gt;)到后台执行即可&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/feishu_app_msg.jpg&quot; alt=&quot;自建应用对话&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;对接企业微信&lt;/h4&gt;
&lt;p&gt;PC登录企业微信客户端可以看到官方的消息:&lt;a href=&quot;https://open.work.weixin.qq.com/help2/pc/cat?doc_id=21657&quot;&gt;官方插件支持OpenClaw，3步快速接入&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;参照教程安装即可,这里说几个我遇到的问题&lt;/p&gt;
&lt;h6&gt;工作台的智能机器人中没有API机器人菜单&lt;/h6&gt;
&lt;p&gt;因为企业微信不常用,所以没有最新的功能;点击&lt;code&gt;头像&lt;/code&gt;→&lt;code&gt;设置&lt;/code&gt;→&lt;code&gt;关于&lt;/code&gt;→&lt;code&gt;检查更新&lt;/code&gt;;更新最新企业微信客户端,即可看到&lt;code&gt;API模式创建&lt;/code&gt;的菜单&lt;/p&gt;
&lt;h5&gt;参数不合法&lt;/h5&gt;
&lt;p&gt;参数不合法报错是因为没选择&lt;code&gt;可见范围&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/openclaw_wecom_%E5%8F%82%E6%95%B0%E4%B8%8D%E5%90%88%E6%B3%95.png&quot; alt=&quot;智能机器人参数不合法&quot; /&gt;&lt;/p&gt;
&lt;h5&gt;添加成功后怎么使用&lt;/h5&gt;
&lt;p&gt;添加成功后在&lt;code&gt;通讯录&lt;/code&gt;→&lt;code&gt;智能机器人&lt;/code&gt;中能看到,可以直接发消息或者把它添加到群聊中&lt;/p&gt;
&lt;p&gt;添加到群聊后可以直接&lt;code&gt;@龙虾&lt;/code&gt;让它回复&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/wecom_bot.png&quot; alt=&quot;企业微信智能机器人&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/openclaw/openclaw/issues/4531&quot;&gt;disconnected (1008): pairing required #4531&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cnblogs.com/treasury-manager/p/19601042&quot;&gt;WSL2手工配置Openclaw+minimax接口+飞书插件+踩坑&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cnblogs.com/pautcher/articles/19585433&quot;&gt;从源码快速安装 openclaw&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://open.work.weixin.qq.com/help2/pc/cat?doc_id=21657&quot;&gt;官方插件支持OpenClaw，3步快速接入&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>使用DoNotNotify屏蔽安卓手机的系统通知</title><link>https://blog.wangshuai.app/2026-02-12-%E5%B0%8F%E7%B1%B3%E6%89%8B%E6%9C%BA%E5%B1%8F%E8%94%BD%E5%AE%89%E5%85%A8%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E9%80%9A%E7%9F%A5/</link><guid isPermaLink="true">https://blog.wangshuai.app/2026-02-12-%E5%B0%8F%E7%B1%B3%E6%89%8B%E6%9C%BA%E5%B1%8F%E8%94%BD%E5%AE%89%E5%85%A8%E6%9C%8D%E5%8A%A1%E7%B3%BB%E7%BB%9F%E9%80%9A%E7%9F%A5/</guid><pubDate>Thu, 12 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;手机安装的第三方应用一般都可直接屏蔽通知,但是对系统应用,比如我用的小米手机;它的&lt;code&gt;安全服务&lt;/code&gt;却总是提示&lt;code&gt;发现XXX等2个恶意应用&lt;/code&gt;;而且现在还没有添加白名单的方法,异常恶心&lt;/p&gt;
&lt;p&gt;此方法理论屏蔽任何系统通知,不限于此&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;解决方法就是安装&lt;a href=&quot;https://github.com/anujja/DoNotNotify&quot;&gt;DoNotNotify&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;用这个APP可以自动忽略通知,但缺点是&lt;strong&gt;耗电&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;要持续监听通知需要 &lt;code&gt;电池无限制&lt;/code&gt;+&lt;code&gt;自启动&lt;/code&gt;+&lt;code&gt;后台保活&lt;/code&gt;;所以不耗电是不可能的&lt;/p&gt;
&lt;p&gt;考虑到现在手机电池容量增大,而且有快充;我认为可以接受&lt;/p&gt;
&lt;p&gt;你们也可以自行尝试后决定是否继续使用&lt;/p&gt;
&lt;p&gt;本机为&lt;code&gt;Xiaomi14Pro&lt;/code&gt; 系统&lt;code&gt;hyperos 3.0&lt;/code&gt;,其它品牌的设置方法类似&lt;/p&gt;
&lt;h3&gt;操作步骤&lt;/h3&gt;
&lt;h4&gt;下载安装&lt;/h4&gt;
&lt;p&gt;从项目&lt;a href=&quot;https://github.com/anujja/DoNotNotify/releases&quot;&gt;Release&lt;/a&gt;页面下载最新&lt;code&gt;apk&lt;/code&gt;安装&lt;/p&gt;
&lt;h4&gt;设置&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;安装打开后依据提示开启&lt;code&gt;读取、回复和控制通知&lt;/code&gt;的&lt;code&gt;特殊权限&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;长按&lt;code&gt;DoNotNotify&lt;/code&gt;图标打开&lt;code&gt;App&lt;/code&gt;的&lt;code&gt;应用信息&lt;/code&gt;,开启&lt;code&gt;自启动&lt;/code&gt;选项&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;应用信息&lt;/code&gt;的&lt;code&gt;电量消耗&lt;/code&gt;中选择&lt;code&gt;无限制(不采取任何限制措施)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;打开&lt;code&gt;任务管理视图&lt;/code&gt;长按&lt;code&gt;DoNotNotify&lt;/code&gt;图标点击锁标志&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/donotnotify_lock_app.png&quot; alt=&quot;lock app&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;屏蔽&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;DoNotNotify&lt;/code&gt;是通过规则按文本或者正则匹配屏蔽指定的应用通知;所以每个应用需要有一个&lt;code&gt;Rule&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;设置成功后&lt;code&gt;History&lt;/code&gt;中就能看到历史通知消息;等待下一次&lt;code&gt;安全服务&lt;/code&gt;的通知发生后点击&lt;code&gt;App&lt;/code&gt;的消息内容,在弹窗中选择&lt;code&gt;Create Rule&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;类型选择&lt;code&gt;BLACKLIST&lt;/code&gt;(黑名单)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Title Filter(Optional)&lt;/code&gt;输入&lt;code&gt;.*&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;下面选项选择&lt;code&gt;REGEX&lt;/code&gt;(正则)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Text Filter(Optional)&lt;/code&gt;输入&lt;code&gt;.*&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;下面选项选择&lt;code&gt;REGEX&lt;/code&gt;(正则)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/donotnotify_addrule.png&quot; alt=&quot;以微信为例从消息添加规则&quot; /&gt;&lt;/p&gt;
&lt;p&gt;配置成功后即可在&lt;code&gt;Rules&lt;/code&gt;中看到添加的规则以及命中的数量&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;Blocked&lt;/code&gt;中可查看到屏蔽的历史内容&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/donotnotify_blocked.png&quot; alt=&quot;屏蔽记录&quot; /&gt;&lt;/p&gt;
&lt;p&gt;首次运行可以使用微信测试,用小号给自己发消息;&lt;/p&gt;
&lt;h3&gt;补充说明&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;假如&lt;code&gt;History&lt;/code&gt;中没有任何内容,请检查&lt;code&gt;设置&lt;/code&gt;部分,依次设置好&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;特殊权限&lt;/code&gt;设置位置&lt;/p&gt;
&lt;p&gt;特殊权限设置在&lt;code&gt;设置&lt;/code&gt;→&lt;code&gt;隐私与安全&lt;/code&gt;→&lt;code&gt;隐私&lt;/code&gt;→&lt;code&gt;其它权限&lt;/code&gt;→&lt;code&gt;特殊权限设置&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;也可以在手机设置中搜索&lt;code&gt;特殊&lt;/code&gt;找到&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/mi_hyperos_special.png&quot; alt=&quot;特殊权限搜索&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;正则表达式改良&lt;/p&gt;
&lt;p&gt;正则表达式匹配文本使用&lt;code&gt;.*&lt;/code&gt;会匹配所有文本;就是所有的都会被拦截&lt;/p&gt;
&lt;p&gt;实际&lt;code&gt;安全服务&lt;/code&gt;还是需要显示骚扰拦截内容,比如短信拦截和骚扰电话内容;因此这里我把&lt;code&gt;Title&lt;/code&gt;调整为&lt;code&gt;^(?!.*骚扰).*$&lt;/code&gt;,其它的需求可以让AI帮你写正则处理&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>使用sub-store合并多个机场的订阅</title><link>https://blog.wangshuai.app/2026-01-31-clash-verge%E5%90%88%E5%B9%B6%E8%AE%A2%E9%98%85/</link><guid isPermaLink="true">https://blog.wangshuai.app/2026-01-31-clash-verge%E5%90%88%E5%B9%B6%E8%AE%A2%E9%98%85/</guid><pubDate>Sat, 31 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Google AI Studio&lt;/code&gt;是非常好的AI工具,不过它的网络检测也很严格;之前我用的机场一直没问题 ;&lt;/p&gt;
&lt;p&gt;不过最近可能用户太多;导致被它识别到不支持的地区;经查询,它的地区检测域名是&lt;code&gt;alkalimakersuite-pa.clients6.google.com&lt;/code&gt;;&lt;/p&gt;
&lt;p&gt;在我单独给它设置规则后发现所有的节点都无法访问:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rules:
- DOMAIN-SUFFIX,alkalimakersuite-pa.clients6.google.com,AIStudio
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但我用的别的按量计费的机场发现可以访问&lt;code&gt;Google AI Studio&lt;/code&gt;;每次使用切来切去非常不便;而且我还用了&lt;code&gt;TUN&lt;/code&gt;模式,很多应用都会断网&lt;/p&gt;
&lt;p&gt;于是研究几天找到了适合的节点,规则,合并方法&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;我之前用的是&lt;code&gt;Clash for Windows&lt;/code&gt;,它不具备订阅合并功能;现在我测试使用&lt;code&gt;Mihomo&lt;/code&gt;内核的&lt;code&gt;Clash Verge&lt;/code&gt;有人说可行&lt;/p&gt;
&lt;p&gt;官方的issue:&lt;a href=&quot;https://github.com/clash-verge-rev/clash-verge-rev/issues/1092&quot;&gt;[Feature] 能否合并订阅 #1092&lt;/a&gt;里面说了可以自己写配置实现;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;但我实在不懂&lt;code&gt;Clash&lt;/code&gt;的内核;网上也没有一个完整的教程指导如何合并订阅;&lt;/s&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.metacubex.one/example/conf/&quot;&gt;懒人配置&lt;/a&gt;是可行的&lt;/p&gt;
&lt;p&gt;操作方法如下:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 &lt;code&gt;订阅&lt;/code&gt;中新建一个&lt;code&gt;Local&lt;/code&gt;类型的本地配置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/545425466-6dc029c8-c825-4a88-9314-8015b5b73b48.png&quot; alt=&quot;Image&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;右键新增的配置点击&lt;code&gt;编辑文件&lt;/code&gt;
把懒人配置的所有内容粘贴进去,需要填写自己的订阅地址&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;##我的示例如下:
proxy-providers:
 p1:
   url: &quot;https://xxxx.com?clash=3&amp;amp;extend=1&quot; ##主要填这个
   type: http
   interval: 86400
   path: ./proxy_provider/p1.yml
   health-check: {enable: true,url: &quot;https://www.gstatic.com/generate_204&quot;,interval: 300}
   override:
     additional-prefix: &quot;[p1]&quot;
### 其它配置
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;订阅会自动下载在&lt;code&gt;Clash Verge&lt;/code&gt;目录,默认是&lt;code&gt;%APPDATA%\io.github.clash-verge-rev.clash-verge-rev&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;我加了一个&lt;code&gt;path&lt;/code&gt;配置,这样对应订阅&lt;code&gt;yml&lt;/code&gt;文件就会在&lt;code&gt;%APPDATA%\io.github.clash-verge-rev.clash-verge-rev\proxy_provider&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;保存成功后会拉取一次订阅;假如本地要更新;就要&lt;strong&gt;把本地的文件(p1.yml)删除&lt;/strong&gt;重新拉取&lt;/p&gt;
&lt;p&gt;多个订阅的合并里面用的节点名称正则匹配(&lt;code&gt;filter&lt;/code&gt;);把同一个地区的放到一个&lt;code&gt;proxy-groups&lt;/code&gt;中;&lt;/p&gt;
&lt;p&gt;要修改订阅,简单的方法就是右键订阅配置,使用&lt;code&gt;编辑规则&lt;/code&gt; &lt;code&gt;编辑节点&lt;/code&gt; &lt;code&gt;编辑代理组&lt;/code&gt; 调整&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文主要介绍使用&lt;a href=&quot;https://github.com/sub-store-org/Sub-Store&quot;&gt;&lt;code&gt;Sub-Store&lt;/code&gt;&lt;/a&gt;合并订阅的方法&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Sub-Store&lt;/code&gt;只支持节点合并,不过它的&lt;code&gt;文件管理&lt;/code&gt;→&lt;code&gt;Mihomo配置&lt;/code&gt;支持使用js操作组合订阅;进而生成我需要的配置&lt;/p&gt;
&lt;p&gt;基于&lt;code&gt;Sub-Store&lt;/code&gt;还让我的手机也能访问组合订阅;可以说好处多多&lt;/p&gt;
&lt;p&gt;之前我手机用的&lt;code&gt;Clash for Android&lt;/code&gt;,用起来还不错;只是没有方便修改节点或者配置的地方;有了&lt;code&gt;Sub-Store&lt;/code&gt;后也能在&lt;code&gt;Sub-Store&lt;/code&gt;后台修改了;我现在用的安卓客户端是&lt;a href=&quot;https://github.com/chen08209/FlClash&quot;&gt;&lt;code&gt;FIClash&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;处理脚本我使用的是&lt;a href=&quot;https://github.com/powerfullz/override-rules/blob/main/convert.js&quot;&gt;convert.js&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;操作步骤&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;参照教程安装&lt;code&gt;Sub-Store&lt;/code&gt;:&lt;a href=&quot;https://wp.gxnas.com/15067.html&quot;&gt;订阅转换+聚合+排序的高级管理工具Sub-Store搭建和设置教程&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我的&lt;code&gt;sub-store&lt;/code&gt;部署在内网,同时使用了&lt;code&gt;Tinyauth&lt;/code&gt;保护,方便外网编辑,详见:&lt;a href=&quot;https://blog.dejavu.moe/posts/build-passkeys-identity-system-with-pocketid-x-tinyauth/&quot;&gt;自托管部署 Pocket ID 与 Tinyauth 完全指南&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;添加&lt;code&gt;Mihomo&lt;/code&gt;配置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在添加多个单条订阅并把他们放到一个组合订阅中后,假如你使用&lt;code&gt;Clash&lt;/code&gt;导入订阅;那你本地订阅只有一个&lt;code&gt;Global&lt;/code&gt;的所有订阅组&lt;/p&gt;
&lt;p&gt;之前机场里面的规则,订阅组都不见了;此时需要使用&lt;code&gt;Mihomo&lt;/code&gt;配置功能&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;打开&lt;code&gt;Sub-Store&lt;/code&gt;→&lt;code&gt;文件管理&lt;/code&gt;→&lt;code&gt;添加&lt;/code&gt;→&lt;code&gt;Mihomo配置&lt;/code&gt;→&lt;code&gt;添加一个 脚本操作&lt;/code&gt;→&lt;code&gt;本地内容&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/sub-store_add_mihomo.png&quot; alt=&quot;添加mihomo配置&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/sub-store_add_mihomo_localfile.png&quot; alt=&quot;添加本地内容&quot; /&gt;&lt;/p&gt;
&lt;p&gt;里面的内容可以用&lt;a href=&quot;https://github.com/powerfullz/override-rules/blob/main/convert.js&quot;&gt;convert.js&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这个覆写规则会添加地区的节点分组和一些外部的规则组;&lt;/p&gt;
&lt;p&gt;由于我还有一些&lt;code&gt;Clash for Windows&lt;/code&gt;使用的规则,节点以及节点组;因此我改写了它的脚本;&lt;/p&gt;
&lt;p&gt;添加了实现&lt;code&gt;Clash for Windows&lt;/code&gt;中的&lt;code&gt;Parsers&lt;/code&gt;的&lt;code&gt;prepend-rules&lt;/code&gt; &lt;code&gt;append-proxies&lt;/code&gt; &lt;code&gt;append-proxy-groups&lt;/code&gt;功能&lt;/p&gt;
&lt;p&gt;这里先给出我的&lt;code&gt;本地内容&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;我后面又调整了js,将配置改为外部的&lt;code&gt;custom_clash_config&lt;/code&gt;文件:&lt;/p&gt;
&lt;p&gt;先添加额外的&lt;code&gt;json&lt;/code&gt;配置:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Sub-Store&lt;/code&gt;→&lt;code&gt;文件管理&lt;/code&gt;→&lt;code&gt;添加&lt;/code&gt; →&lt;code&gt;文件&lt;/code&gt;→输入名称&lt;code&gt;custom_clash_config&lt;/code&gt;→粘贴外置json文件内容&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/substore_add_file.png&quot; alt=&quot;substore_add_file&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/substore_addjson.png&quot; alt=&quot;substore_addjson&quot; /&gt;&lt;/p&gt;
&lt;p&gt;文件内容示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;appendProxyGroups&quot;: [
    {
      &quot;name&quot;: &quot;AIStudio&quot;,
      &quot;type&quot;: &quot;select&quot;,
      &quot;icon&quot;: &quot;https://www.gstatic.com/aistudio/ai_studio_favicon_2_128x128.png&quot;,
      &quot;proxies&quot;: [
        &quot;__DEFAULT_SELECTOR__&quot;,
      ]
    }
  ],
  &quot;prependRules&quot;: [
    &quot;DOMAIN-SUFFIX,alkalimakersuite-pa.clients6.google.com,AIStudio&quot;
  ],
  &quot;appendProxies&quot;: [
    {
            &quot;name&quot;: &quot;easyConnect&quot;,
            &quot;server&quot;: &quot;192.168.1.193&quot;,
            &quot;port&quot;: 1080,
            &quot;type&quot;: &quot;socks5&quot;
        }
  ],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;script src=&quot;https://gist.github.com/wangshuai-007/44b3c2089df0f58bb900ebdf69e76ac9.js?theme=dark&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p&gt;我实现了&lt;code&gt;appendProxies&lt;/code&gt;  &lt;code&gt;appendProxyGroups&lt;/code&gt;  &lt;code&gt;prependRules&lt;/code&gt;方法对应 &lt;code&gt;Parsers&lt;/code&gt;中的功能&lt;/p&gt;
&lt;p&gt;这里面用的&lt;code&gt;json&lt;/code&gt;对象;和&lt;code&gt;Clash for Windows&lt;/code&gt;中的&lt;code&gt;yml&lt;/code&gt;格式不同;&lt;/p&gt;
&lt;p&gt;因此你可以用在线&lt;code&gt;yaml&lt;/code&gt;转&lt;code&gt;json&lt;/code&gt;的工具转化:&lt;a href=&quot;https://onlineyamltools.com/convert-yaml-to-json&quot;&gt;&lt;strong&gt;YAML to JSON Converte&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;js代码很容易理解,实际就是用js修改生成的对象;有了AI后想要什么逻辑都可以让它帮你生成&lt;/p&gt;
&lt;p&gt;我添加了&lt;code&gt;AIStudio&lt;/code&gt;代理组及对应规则;同时会把所有的&lt;code&gt;vmess&lt;/code&gt;和&lt;code&gt;hysteria2&lt;/code&gt;类型的节点添加到这个组里面,方便访问&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保存文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在确认好覆写js代码后,可以把代码保存到&lt;code&gt;sub-store&lt;/code&gt;路径的&lt;code&gt;data&lt;/code&gt;目录下;然后把脚本操作改为&lt;code&gt;远程链接&lt;/code&gt;路径填&lt;code&gt;/opt/app/data/xxx.js&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/sub-store_add_mihomo_remotefile.png&quot; alt=&quot;remote file&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;补充说明&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;防止环回设置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为我用了&lt;code&gt;tun&lt;/code&gt;模式 ,且代理了家里的网络;因此使用&lt;code&gt;clash&lt;/code&gt;时不能代理局域网连接防止环回&lt;/p&gt;
&lt;p&gt;需要在&lt;code&gt;设置&lt;/code&gt;→&lt;code&gt;系统设置&lt;/code&gt;→&lt;code&gt;虚拟网卡模式设置&lt;/code&gt;→&lt;code&gt;排除自定义网段&lt;/code&gt;中添加本地网段的排除规则&lt;code&gt;192.168.50.0/24&lt;/code&gt;(请根据你的局域网网段自行填写)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/clash_verge_tun_ignore_net.png&quot; alt=&quot;排除网段&quot; /&gt;&lt;/p&gt;
&lt;p&gt;当然,也可以修改系统代理绕过的网段&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;防止回环统一方法&lt;/p&gt;
&lt;p&gt;第一种方法要每个客户端分别配置,其实可以在&lt;code&gt;proxy group&lt;/code&gt;上做文章
在代理组中添加&lt;code&gt;url-test&lt;/code&gt;类型且配置有&lt;code&gt;DIRECT&lt;/code&gt;方式的代理组&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  type: url-test
  proxies:
  - Home
  - DIRECT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后再在路由器上的指定端口关闭&lt;code&gt;NAT环回&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/openwrt_port_natloop.png&quot; alt=&quot;openwrt的端口回环设置&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这样,在内网时就会默认选择&lt;code&gt;DIRECT&lt;/code&gt;而在外网时就会变成家里的代理&lt;/p&gt;
&lt;p&gt;不用&lt;code&gt;openwrt&lt;/code&gt;的,也可以用nginx的特性根据ip阻断不同的&lt;code&gt;url-test&lt;/code&gt;请求以达到自动切换的目的&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wp.gxnas.com/15067.html&quot;&gt;订阅转换+聚合+排序的高级管理工具Sub-Store搭建和设置教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;https://github.com/powerfullz/override-rules&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>通过url打开微信小程序</title><link>https://blog.wangshuai.app/2026-01-10-open-wechat-miniprogram-by-url/</link><guid isPermaLink="true">https://blog.wangshuai.app/2026-01-10-open-wechat-miniprogram-by-url/</guid><pubDate>Sat, 10 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近项目要用到短信跳转小程序的功能;因此特意研究一下相关功能&lt;/p&gt;
&lt;p&gt;顺便集成跳转小程序的功能到导航网站(&lt;a href=&quot;https://dashy.to/&quot;&gt;dashy&lt;/a&gt;)中&lt;/p&gt;
&lt;p&gt;参考微信官方文档:&lt;a href=&quot;https://developers.weixin.qq.com/minigame/dev/wxcloud/guide/staticstorage/msg-miniprogram.html&quot;&gt;短信跳小程序&lt;/a&gt;可以实现需求但这里有两大坑&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它的H5网页我 实测不怎么好用,至少我没研究出来,因此我自己写了简单的跳转代码&lt;/li&gt;
&lt;li&gt;因2025年3.15导致运营商对短信中的链接要求严格,导致使用 &lt;a href=&quot;https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/cloudbase/cloudbase.sendSms.html&quot;&gt;cloudbase.sendsms&lt;/a&gt; 接口几乎都无法发送成功(我测试只有手机开启了网络短信功能的手机号能收到短信;其它全是&lt;code&gt;(MK:100C)运营商关键词拦截&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;截止日前我没找到发短信的方法,因此本文只讨论链接跳转小程序功能&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;在安装了微信的客户端中,微信会在设备上注册:&lt;code&gt;weixin://&lt;/code&gt;的协议,通过这种格式的链接可以快速打开小程序&lt;/p&gt;
&lt;p&gt;协议规范请见官方文档:&lt;a href=&quot;https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/url-scheme.html&quot;&gt;获取 URL Scheme&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;URL Scheme&lt;/code&gt;有加密和非加密,加密的需要调接口且有时间限制;因此这里讨论的是明文&lt;code&gt;URL Scheme&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;假如你&lt;strong&gt;是小程序的开发者&lt;/strong&gt;,则在允许的情况下在后台设置&lt;code&gt;明文URL Scheme&lt;/code&gt;后可以使用&lt;/p&gt;
&lt;p&gt;&lt;code&gt;weixin://dl/business/?appid=*APPID*&amp;amp;path=*PATH*&amp;amp;query=*QUERY*&amp;amp;env_version=*ENV_VERSION*&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这样的链接访问小程序&lt;/p&gt;
&lt;p&gt;这个链接在手机上正式版体验版都可以跳转;但在PC上&lt;a href=&quot;https://developers.weixin.qq.com/community/develop/doc/000c6c5993cca05a1363609da63c00&quot;&gt;只能跳转正式版&lt;/a&gt;PC跳转体验版的方法可以通过解析小程序体验版的二维码得到,链接是这样的:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://open.weixin.qq.com/sns/getexpappinfo?appid=*APPID*&amp;amp;amp;path=*PATH*#wechat-redirect&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这个链接只能用微信浏览器打开&lt;/p&gt;
&lt;p&gt;小程序需要传参可在&lt;code&gt;path&lt;/code&gt;变量中拼接;比如小程序的启动参数是&lt;code&gt;env=test&lt;/code&gt;时;&lt;code&gt;path&lt;/code&gt;的值就应该是:&lt;code&gt;pages/index/index?env=test&lt;/code&gt;;但这里&lt;code&gt;path&lt;/code&gt;要&lt;code&gt;urlencode&lt;/code&gt;因此就变成&lt;code&gt;pages/index/index%3Fenv=test&lt;/code&gt;读取参数使用API:&lt;a href=&quot;https://developers.weixin.qq.com/miniprogram/dev/api/base/app/life-cycle/wx.getLaunchOptionsSync.html&quot;&gt;&lt;code&gt;getLaunchOptionsSync&lt;/code&gt;&lt;/a&gt;;&lt;/p&gt;
&lt;p&gt;非指定小程序的开发人员可以在PC上跳转到指定小程序的正式版,跳转链接可以使用小程序的&lt;code&gt;添加到电脑桌面&lt;/code&gt;功能&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/wexin_pc_minip_add_shortcut.png&quot; alt=&quot;添加到电脑桌面&quot; /&gt;&lt;/p&gt;
&lt;p&gt;链接大概是这样&lt;code&gt;weixin://launchapplet?app_id=*APPID*&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;其中,&lt;code&gt;app_id&lt;/code&gt;可以在手机小程序的&lt;code&gt;更多资料&lt;/code&gt;中查看&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/wexin_appid.png&quot; alt=&quot;app_id&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里给出我写的&lt;code&gt;H5&lt;/code&gt;跳转小程序的&lt;code&gt;HTML&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;zh-CN&quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
  &amp;lt;title&amp;gt;正在跳转&amp;lt;/title&amp;gt;

  &amp;lt;style&amp;gt;
    html, body {
      width: 100%;
      height: 100%;
      margin: 0;
    }
    body {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      background: #f5f7fa;
      font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;,
                   &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, sans-serif;
      color: #333;
    }
    .tip {
      font-size: 20px;
      margin-bottom: 20px;
    }
    .btn {
      padding: 10px 20px;
      font-size: 16px;
      margin: 10px;
      border: none;
      border-radius: 6px;
      background: #1677ff;
      color: #fff;
      cursor: pointer;
    }
    .btn:hover {
      background: #0958d9;
    }
  &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;

  &amp;lt;div class=&quot;tip&quot;&amp;gt;正在为你打开微信开发助手…&amp;lt;/div&amp;gt;
  &amp;lt;button class=&quot;btn&quot; id=&quot;openBtn&quot;&amp;gt;点击立即跳转&amp;lt;/button&amp;gt;
  &amp;lt;button class=&quot;btn&quot; style=&quot;visibility: hidden;&quot; id=&quot;copyText&quot; onclick=&quot;copyText()&quot;&amp;gt;复制到剪切板&amp;lt;/button&amp;gt;

  &amp;lt;script&amp;gt;
    // 1️⃣ 计算跳转地址（示例：透传当前页面 query）
    const params = new URLSearchParams(window.location.search);


    var queryStr=encodeURIComponent(params.toString())

      var appid=&apos;wxcff7381e631cf54e&apos;;
    var path=&apos;xxx&apos;
    var env_version=&apos;release&apos;
    if(params.has(&apos;appid&apos;)){
        appid=params.get(&apos;appid&apos;)
        params.delete(&quot;appid&quot;)
    }
    if(params.has(&apos;path&apos;)){
        // appid=params.get(&apos;path&apos;)
        params.delete(&quot;path&quot;)
    }
    if(params.has(&apos;env_version&apos;)){
        env_version=params.get(&apos;env_version&apos;)
        params.delete(&quot;env_version&quot;)
    }
    var queryStr=encodeURIComponent(params.toString())

    var ur=`weixin://dl/business/?appid=${appid}&amp;amp;path=${path}&amp;amp;query=${queryStr}&amp;amp;env_version=${env_version}`;

    function getTargetUrl() {

      return ur;
    }
    
    function openTrialShare() {
        // window.location.href = `weixin://dl/business/?appid=wxcff7381e631cf54e&amp;amp;path=xxx&amp;amp;query=${queryStr}&amp;amp;env_version=trial`;
        var ur=`weixin://dl/business/?appid=wxcff7381e631cf54e&amp;amp;path=xxx&amp;amp;query=${queryStr}&amp;amp;env_version=trial`;
        console.log(&apos;href is &apos;,ur)
        window.location.href = ur;
    }
    // 2️⃣ 执行跳转
    function redirect() {
      const url = getTargetUrl();
      window.location.href = url;
    }
    function showCopy() {
      const url = getTargetUrl();
      document.getElementById(&apos;copyText&apos;).style.visibility=&apos;&apos;;
    }
    
    function copyText() {

    navigator.clipboard.writeText(ur)
        .then(() =&amp;gt; {
            alert(&apos;已复制到剪切板&apos;);
        })
        .catch(err =&amp;gt; {
            console.error(err);
            alert(&apos;复制失败&apos;);
        });
    }

    // 3️⃣ 自动跳转（延迟 1 秒，体验更好）
    setTimeout(redirect, 1000);
    setTimeout(showCopy, 3000);

    // 4️⃣ 手动点击兜底
    document.getElementById(&quot;openBtn&quot;).onclick = redirect;
  &amp;lt;/script&amp;gt;

&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;结语&lt;/h3&gt;
&lt;p&gt;&lt;s&gt;我的初心是替代默认的微信浏览器首页,当前看来还有点差距&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;微信浏览器导航站介绍请查看另一篇博文:&lt;a href=&quot;/2026-05-30-%E5%A6%82%E4%BD%95%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%AF%BC%E8%88%AA%E7%AB%99/&quot;&gt;如何创建一个微信小程序导航站&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.weixin.qq.com/minigame/dev/wxcloud/guide/staticstorage/jump-miniprogram.html&quot;&gt;静态网站 H5 跳小程序&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.weixin.qq.com/minigame/dev/wxcloud/guide/staticstorage/msg-miniprogram.html&quot;&gt;短信跳小程序&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/evi0s/WMPFDebugger&quot;&gt;WMPFDebugger&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>注册xshurl协议一键打开xshell的session</title><link>https://blog.wangshuai.app/2026-01-10-register-xsh-url-link/</link><guid isPermaLink="true">https://blog.wangshuai.app/2026-01-10-register-xsh-url-link/</guid><pubDate>Sat, 10 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近突然领会了一个导航网站(&lt;a href=&quot;https://dashy.to/&quot;&gt;dashy&lt;/a&gt;)的妙用&lt;/p&gt;
&lt;p&gt;除了可以放&lt;strong&gt;项目地址&lt;/strong&gt;,&lt;strong&gt;编译&lt;/strong&gt; &lt;strong&gt;发布&lt;/strong&gt;地址打开对应&lt;strong&gt;小程序&lt;/strong&gt;之外还可以直接&lt;code&gt;ssh&lt;/code&gt;连接到测试服务器;更可以改造脚本直达日志文件位置;让每天的班可以上的开开心心&lt;/p&gt;
&lt;p&gt;&lt;code&gt;xsh&lt;/code&gt;是&lt;code&gt;xshell&lt;/code&gt;保存其&lt;code&gt;session&lt;/code&gt;的配置文件格式;当本地配置好连接服务器的方式后可以直接从&lt;code&gt;Session Manager&lt;/code&gt;中双击名称连接到服务器;&lt;/p&gt;
&lt;p&gt;而&lt;code&gt;xshell&lt;/code&gt;命令行工具(&lt;a href=&quot;https://netsarang.atlassian.net/wiki/spaces/EN7/pages/419957436/Xshell+Command+Line+Options&quot;&gt;Xshell Command Line Options&lt;/a&gt;)刚好支持使用&lt;code&gt;xsh&lt;/code&gt;文件打开,且可以使用相对路径;这免于密码输入的连接方法秒杀&lt;code&gt;ssh://&lt;/code&gt;这种协议(详见前文:&lt;a href=&quot;/2026-01-10-register-ssh-url-link/&quot;&gt;注册sshurl协议一键打开terminal&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/xshell_session_manager.png&quot; alt=&quot;&apos;orkz.9&apos;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;注册&lt;code&gt;xsh://&lt;/code&gt;这样的协议需要修改注册表;假如只用注册表,使用&lt;code&gt;cmd&lt;/code&gt;或者&lt;code&gt;powershell&lt;/code&gt;这样的脚本都会有黑屏弹窗的问题&lt;/p&gt;
&lt;p&gt;只有&lt;code&gt;vbs&lt;/code&gt;可以纯后台不弹窗启动应用;因此需要写两个文件:&lt;code&gt;register-xsh-protocol.reg&lt;/code&gt;和&lt;code&gt;xshell-handler.vbs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;拆分后方便开发及调试;先给出我的文件:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;register-xsh-protocol.reg&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\xsh]
@=&quot;URL:Xshell Protocol&quot;
&quot;URL Protocol&quot;=&quot;&quot;

[HKEY_CLASSES_ROOT\xsh\DefaultIcon]
@=&quot;\&quot;C:\\Program Files (x86)\\NetSarang\\Xshell 8\\Xshell.exe\&quot;,0&quot;

[HKEY_CLASSES_ROOT\xsh\shell]

[HKEY_CLASSES_ROOT\xsh\shell\open]

[HKEY_CLASSES_ROOT\xsh\shell\open\command]
@=&quot;wscript \&quot;D:\\Tools\\xshell-handler.vbs\&quot; %1 &quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;xshell-handler.vbs&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos; xshell-handler.vbs
Set objArgs = WScript.Arguments
If objArgs.Count = 0 Then WScript.Quit

&apos; get url xsh://%USERPROFILE%/test.xsh)
rawUrl = objArgs(0)

&apos; 1. replace xsh://
path = Replace(rawUrl, &quot;xsh://&quot;, &quot;&quot;)

&apos; remove last /
If Right(path, 1) = &quot;/&quot; Then
    path = Left(path, Len(path) - 1)
End If

&apos; 2. decode
&apos; path = Unescape(path)
path = Replace(path, &quot;%20&quot;, &quot; &quot;)
path = Replace(path, &quot;%40&quot;, &quot;@&quot;)
path = Replace(path, &quot;%3A&quot;, &quot;:&quot;)
path = Replace(path, &quot;%5C&quot;, &quot;\&quot;)
path = Replace(path, &quot;%22&quot;, &quot;&quot;&quot;&quot;)

Set shell = CreateObject(&quot;WScript.Shell&quot;)

&apos; 3. use enviroment
finalPath = shell.ExpandEnvironmentStrings(path)

&apos; 4. Xshell 的实际程序路径 (请根据你的版本确认路径)
xshellExe = &quot;&quot;&quot;C:\Program Files (x86)\NetSarang\Xshell 8\Xshell.exe&quot;&quot;&quot;

&apos; WScript.Echo finalPath

&apos; shell.Run &quot;&quot;&quot;C:\Program Files (x86)\NetSarang\Xshell 8\Xshell.exe&quot;&quot; \work\hz\1.9.xsh&quot;&quot;&quot;
shell.Run xshellExe &amp;amp; finalPath
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;补充说明&lt;/h3&gt;
&lt;p&gt;相对路径打开&lt;code&gt;zsh&lt;/code&gt; &lt;code&gt;session&lt;/code&gt;用这个URL:&lt;code&gt;xsh://%5Cwork%5Chz%5C1.9&lt;/code&gt;等价于&lt;code&gt;xshell \work\hz\1.9&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;绝对路径使用变量打开&lt;code&gt;zsh&lt;/code&gt; &lt;code&gt;session&lt;/code&gt;用这个URL:&lt;code&gt;xsh://%22%25USERPROFILE%25%5CDocuments%5CNetSarang%20Computer%5C8%5CXshell%5CSessions%5Cwork%5Chz%5C1.9.xsh%22&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;等价于&lt;code&gt;cmd&lt;/code&gt;的&lt;code&gt;xshell &quot;%USERPROFILE%\Documents\NetSarang Computer\8\Xshell\Sessions\work\hz\1.9.xsh&quot;&lt;/code&gt;(这个链接可以用&lt;code&gt;js&lt;/code&gt;代码计算得出:&lt;code&gt;encodeURIComponent(&apos;&quot;%USERPROFILE%\\Documents\\NetSarang Computer\\8\\Xshell\\Sessions\\work\\hz\\1.9.xsh&quot;&apos;)&lt;/code&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;powershell&lt;/code&gt;直接测试&lt;code&gt;xshell&lt;/code&gt;的命令为:&lt;code&gt;xshell \work\hz\1.9&lt;/code&gt;,完整说明请见官方文档:&lt;a href=&quot;https://netsarang.atlassian.net/wiki/spaces/EN7/pages/419957436/Xshell+Command+Line+Options&quot;&gt;Xshell Command Line Options&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;cmd&lt;/code&gt;中直接测试&lt;code&gt;xshell-handler.vbs&lt;/code&gt;的命令为:&lt;code&gt;wscript &quot;D:\Tools\xshell-handler.vbs&quot; xsh://%5Cwork%5Chz%5C1.9&quot; &lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;vbs&lt;/code&gt;中显示变量值用&lt;code&gt;WScript.Echo finalPath&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;url&lt;/code&gt;中有部分关键字不能使用,因此需要转义(比如&lt;code&gt;\&lt;/code&gt;符号就要用&lt;code&gt;%5C&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;url&lt;/code&gt;通常以&lt;code&gt;/&lt;/code&gt;结尾会导致传递给&lt;code&gt;xshell&lt;/code&gt;的路径异常:&lt;code&gt;Unable to resolve host &apos;&apos;C&apos;: Connection failed&lt;/code&gt;因此需要手动移除末尾&lt;code&gt;/&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/xshell_unable_connect.png&quot; alt=&quot;xshell_unable_connect&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://netsarang.atlassian.net/wiki/spaces/EN7/pages/419957436/Xshell+Command+Line+Options&quot;&gt;Xshell Command Line Options&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/asika32764/0422d7e161f5f33294e8b12779c315fd&quot;&gt;Register ssh URL link (protocol) in Windows&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>注册sshurl协议一键打开terminal</title><link>https://blog.wangshuai.app/2026-01-10-register-ssh-url-link/</link><guid isPermaLink="true">https://blog.wangshuai.app/2026-01-10-register-ssh-url-link/</guid><pubDate>Sat, 10 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近突然领会了一个导航网站(&lt;a href=&quot;https://dashy.to/&quot;&gt;dashy&lt;/a&gt;)的妙用&lt;/p&gt;
&lt;p&gt;除了可以放&lt;strong&gt;项目地址&lt;/strong&gt;,&lt;strong&gt;编译&lt;/strong&gt; &lt;strong&gt;发布&lt;/strong&gt;地址打开对应&lt;strong&gt;小程序&lt;/strong&gt;之外还可以直接&lt;code&gt;ssh&lt;/code&gt;连接到测试服务器;更可以改造脚本直达日志文件位置;让每天的班可以上的开开心心&lt;/p&gt;
&lt;p&gt;我暂时没找到可以直接注册&lt;code&gt;url&lt;/code&gt;协议的&lt;code&gt;terminal&lt;/code&gt;,再加上&lt;code&gt;xshell&lt;/code&gt;用起来还不错;因此决定支持&lt;code&gt;ssh://root%40192.168.1.57:22&lt;/code&gt;这样的链接直达服务器的功能&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;注册&lt;code&gt;ssh://&lt;/code&gt;这样的协议需要修改注册表;假如只用注册表,使用&lt;code&gt;cmd&lt;/code&gt;或者&lt;code&gt;powershell&lt;/code&gt;这样的脚本都会有黑屏弹窗的问题&lt;/p&gt;
&lt;p&gt;只有&lt;code&gt;vbs&lt;/code&gt;可以纯后台不弹窗启动应用;因此需要写两个文件:&lt;code&gt;register-ssh-protocol.reg&lt;/code&gt;和&lt;code&gt;ssh-handler.vbs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;拆分后方便开发及调试;先给出我的文件:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;register-ssh-protocol.reg&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\ssh]
@=&quot;URL:SSH Protocol&quot;
&quot;URL Protocol&quot;=&quot;&quot;

[HKEY_CLASSES_ROOT\ssh\DefaultIcon]
@=&quot;cmd.exe,0&quot;

[HKEY_CLASSES_ROOT\ssh\shell]   

[HKEY_CLASSES_ROOT\ssh\shell\open]

[HKEY_CLASSES_ROOT\ssh\shell\open\command]
@=&quot;wscript \&quot;D:\\Tools\\ssh-handler.vbs\&quot; %1 &quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ssh-handler.vbs&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos; xshell-handler.vbs
Set objArgs = WScript.Arguments
If objArgs.Count = 0 Then WScript.Quit


&apos; get url ssh://%USERPROFILE%/test.xsh)
rawUrl = objArgs(0)

&apos; 1. replace xsh://
path = Replace(rawUrl, &quot;ssh://&quot;, &quot;&quot;)

&apos; remove last /
If Right(path, 1) = &quot;/&quot; Then
    path = Left(path, Len(path) - 1)
End If

&apos; 2. decode
&apos; path = Unescape(path)
path = Replace(path, &quot;%20&quot;, &quot; &quot;)
path = Replace(path, &quot;%40&quot;, &quot;@&quot;)
path = Replace(path, &quot;%3A&quot;, &quot;:&quot;)
path = Replace(path, &quot;%5C&quot;, &quot;\&quot;)
path = Replace(path, &quot;%22&quot;, &quot;&quot;&quot;&quot;)

Set shell = CreateObject(&quot;WScript.Shell&quot;)

&apos; 3. use enviroment
finalPath = shell.ExpandEnvironmentStrings(path)

&apos; 4. Xshell 的实际程序路径 (请根据你的版本确认路径)
xshellExe = &quot;&quot;&quot;C:\Program Files (x86)\NetSarang\Xshell 8\Xshell.exe&quot;&quot;&quot;

&apos; WScript.Echo finalPath

&apos; shell.Run &quot;&quot;&quot;C:\Program Files (x86)\NetSarang\Xshell 8\Xshell.exe&quot;&quot; -url root@192.168.50.1&quot;&quot;&quot;
shell.Run xshellExe &amp;amp; &quot;&quot;&quot; -url &quot;&quot;&quot; &amp;amp; finalPath
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;补充说明&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;powershell&lt;/code&gt;直接测试&lt;code&gt;xshell&lt;/code&gt;的命令为:&lt;code&gt;xshell -url root@192.168.50.1&lt;/code&gt;,完整说明请见官方文档:&lt;a href=&quot;https://netsarang.atlassian.net/wiki/spaces/EN7/pages/419957436/Xshell+Command+Line+Options&quot;&gt;Xshell Command Line Options&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;cmd&lt;/code&gt;中直接测试&lt;code&gt;ssh-handler.vbs&lt;/code&gt;的命令为:&lt;code&gt;wscript &quot;D:\Tools\ssh-handler.vbs&quot; ssh://root%40192.168.1.57:22&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;vbs&lt;/code&gt;中显示变量值用&lt;code&gt;WScript.Echo finalPath&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;url&lt;/code&gt;中有部分关键字不能使用,因此需要转义(比如&lt;code&gt;@&lt;/code&gt;符号就要用&lt;code&gt;%40&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;url&lt;/code&gt;通常以&lt;code&gt;/&lt;/code&gt;结尾会导致传递给&lt;code&gt;xshell&lt;/code&gt;的路径异常:&lt;code&gt;Unable to resolve host &apos;&apos;C&apos;: Connection failed&lt;/code&gt;因此需要手动移除&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/xshell_unable_connect.png&quot; alt=&quot;xshell_unable_connect&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://netsarang.atlassian.net/wiki/spaces/EN7/pages/419957436/Xshell+Command+Line+Options&quot;&gt;Xshell Command Line Options&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/asika32764/0422d7e161f5f33294e8b12779c315fd&quot;&gt;Register ssh URL link (protocol) in Windows&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>visual studio 始终以管理员身份运行</title><link>https://blog.wangshuai.app/2025-12-27-visual-studio-run-as-admin/</link><guid isPermaLink="true">https://blog.wangshuai.app/2025-12-27-visual-studio-run-as-admin/</guid><pubDate>Sat, 27 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;因框架限制,公司开发的服务需要占用端口;因此调试时必须管理员权限&lt;/p&gt;
&lt;p&gt;之前每次都是右键&lt;code&gt;visual studio&lt;/code&gt;选择以管理员身份启动;或者修改的&lt;code&gt;visual studio&lt;/code&gt;快捷方式的&lt;code&gt;属性&lt;/code&gt;→&lt;code&gt;高级&lt;/code&gt;→&lt;code&gt;用管理员身份运行&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/app_run_as_admin.png&quot; alt=&quot;short run as admin&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但这种方法只能从快捷方式固定打开,如果从&lt;code&gt;sln&lt;/code&gt;或其它的方法打开项目还是普通权限,这多有不便&lt;/p&gt;
&lt;p&gt;本文介绍两种始终以管理员身份启动&lt;code&gt;visual studio&lt;/code&gt;的方法&lt;/p&gt;
&lt;p&gt;两种方法原理一样,只是添加方式不同&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GUI&lt;/code&gt;添加&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在文件管理器中从&lt;code&gt;疑难解答&lt;/code&gt;中添加&lt;code&gt;visual studio&lt;/code&gt;以管理员启动;&lt;/p&gt;
&lt;p&gt;这是网上流行的解决方法&lt;/p&gt;
&lt;p&gt;选择任意exe右键→&lt;code&gt;兼容性疑难解答&lt;/code&gt;→&lt;code&gt;该程序需要附加权限&lt;/code&gt;→&lt;code&gt;选中visual studio&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这里贴出&lt;code&gt;win11&lt;/code&gt;的英文帮助选项以供参考:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/win_help_noneofabove.png&quot; alt=&quot;先选打开应用选择列表选择最后的翻页&quot; /&gt;&lt;/p&gt;
&lt;p&gt;不过这种方法在你本机安装很多应用的时候就很麻烦,要选很多次&lt;code&gt;None of the above&lt;/code&gt;才能选中&lt;code&gt;visual studio&lt;/code&gt;,因此我从&lt;code&gt;GPT&lt;/code&gt;上问到了快速添加的命令&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;命令行快速添加&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;找到&lt;code&gt;visual studio&lt;/code&gt;的主程序路径,使用以下命令在管理员控制台中执行(注意路径换成你自己的)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;reg add &quot;HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers&quot;  /v &quot;C:\Program Files\Microsoft Visual Studio\18\Professional\Common7\IDE\devenv.exe&quot; /t REG_SZ /d &quot;RUNASADMIN&quot; /f&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这里推荐&lt;a href=&quot;https://github.com/gerardog/gsudo&quot;&gt;&lt;code&gt;gsodu&lt;/code&gt;&lt;/a&gt;(没装的可以执行&lt;code&gt;winget install gerardog.gsudo&lt;/code&gt;安装),它可以直接在控制台弹&lt;code&gt;UAC&lt;/code&gt;授权当前命令&lt;/p&gt;
&lt;p&gt;因此我的命令是这样:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gsudo reg add &quot;HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers&quot;  /v &quot;C:\Program Files\Microsoft Visual Studio\18\Professional\Common7\IDE\Blend.exe&quot; /t REG_SZ /d &quot;RUNASADMIN&quot; /f&lt;/code&gt;&lt;/p&gt;
</content:encoded></item><item><title>xftp failed to retrieve info code 40004</title><link>https://blog.wangshuai.app/2025-12-27-xftp-error-40004/</link><guid isPermaLink="true">https://blog.wangshuai.app/2025-12-27-xftp-error-40004/</guid><pubDate>Sat, 27 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;自从&lt;code&gt;Xshell&lt;/code&gt;更新&lt;code&gt;8.0&lt;/code&gt;后更新过好几次;最近发现&lt;code&gt;Xshell&lt;/code&gt;正常,但无法从&lt;code&gt;Xshell&lt;/code&gt;打开&lt;code&gt;Xftp&lt;/code&gt;会报错:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;xftp failed to retrieve info code 40004&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;本文介绍报错解决方法&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Xftp&lt;/code&gt;报错提示重新安装,但我重装好几次都没解决问题;为此我临时使用了一阵&lt;code&gt;Winscp&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;刚刚又重新安装到非默认目录后还是有同样的错误;但是安装完成提示&lt;code&gt;can not find the nslicense.dll&lt;/code&gt;以及其它报错&lt;/p&gt;
&lt;p&gt;根据这个异常找到一篇文章:&lt;a href=&quot;https://blog.csdn.net/weixin_43578125/article/details/106598957&quot;&gt;Xshell下载安装时报错can not find nslicense.dll&lt;/a&gt;问题为临时文件的中文路径问题&lt;/p&gt;
&lt;p&gt;根据提示,修改&lt;code&gt;TEMP&lt;/code&gt;和&lt;code&gt;TMP&lt;/code&gt;变量的路径为纯英文即可&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/env_temp.png&quot; alt=&quot;image-20251227144612228&quot; /&gt;&lt;/p&gt;
&lt;p&gt;调整后运行正常&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/weixin_43578125/article/details/106598957&quot;&gt;Xshell下载安装时报错can not find nslicense.dll&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>解决powershell表达式或语句中包含意外的标记问题</title><link>https://blog.wangshuai.app/2025-12-20-powershell-syntax-error/</link><guid isPermaLink="true">https://blog.wangshuai.app/2025-12-20-powershell-syntax-error/</guid><pubDate>Sat, 20 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近,在编写&lt;code&gt;powershell&lt;/code&gt;脚本时,在&lt;code&gt;vscode&lt;/code&gt;中已经调试好了;但实际执行却总是失败;而且还没任何日志;通过录像弹窗的方法发现是语法格式问题&lt;/p&gt;
&lt;p&gt;&lt;code&gt;表达式或语句中包含意外的标记&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;语句块或类型定义中缺少右“}”&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这个问题是脚本的编码格式导致的&lt;/p&gt;
&lt;p&gt;解决办法就是修改脚本编码&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;解决问题最关键的一步是&lt;strong&gt;认识问题&lt;/strong&gt;;&lt;/p&gt;
&lt;p&gt;那&lt;code&gt;vscode&lt;/code&gt;中执行正常;而运行脚本报错如何才能知道问题在哪?&lt;/p&gt;
&lt;p&gt;答案是:用&lt;code&gt;powershell&lt;/code&gt;识别代码的方法打开代码&lt;/p&gt;
&lt;p&gt;那就是&lt;code&gt;PowerShell ISE&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;用&lt;code&gt;PowerShell ISE&lt;/code&gt;打开你的代码后就有明显的异常&lt;/p&gt;
&lt;p&gt;比如我这里;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/powershell_ise_unicode_error.png&quot; alt=&quot;powershell_ise_unicode_error&quot; /&gt;&lt;/p&gt;
&lt;p&gt;就是中文的内容把字符串的引号给冲掉了;&lt;/p&gt;
&lt;p&gt;因此,也有一个简单的方法;&lt;strong&gt;将代码中所有的中文换成英文&lt;/strong&gt;;这样也不会有问题&lt;/p&gt;
&lt;h3&gt;操作步骤&lt;/h3&gt;
&lt;h4&gt;查看编码&lt;/h4&gt;
&lt;p&gt;使用&lt;code&gt;PowerShell ISE&lt;/code&gt;打开你的脚本文件;&lt;/p&gt;
&lt;p&gt;在下方的控制台中输入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[console]::OutputEncoding
[console]::InputEncoding
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即可看到本机默认编码格式&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/powershell_ise_default_encode.png&quot; alt=&quot;image-20251220005402908&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;修改脚本编码&lt;/h4&gt;
&lt;p&gt;通常可以使用&lt;code&gt;vscode&lt;/code&gt;修改编码,我引用&lt;code&gt;AI&lt;/code&gt;的操作步骤:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;在 VS Code 中打开乱码的脚本。&lt;/li&gt;
&lt;li&gt;查看窗口&lt;strong&gt;右下角&lt;/strong&gt;的状态栏，通常会显示 UTF-8。&lt;/li&gt;
&lt;li&gt;点击 UTF-8，在弹出的菜单中选择 &lt;strong&gt;“通过编码保存 (Save with Encoding)”&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;在列表里找到并选择 &lt;strong&gt;UTF-8 with BOM&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;保存文件。&lt;/li&gt;
&lt;li&gt;再次用 PowerShell ISE 打开，乱码就消失了。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;实测&lt;code&gt;UTF-8 with BOM&lt;/code&gt;和&lt;code&gt;GB2312&lt;/code&gt;都能解决乱码问题&lt;/p&gt;
&lt;h3&gt;结语&lt;/h3&gt;
&lt;p&gt;代码乱码问题相信每个中国开发者都遇到过;终极解决方法就是换英文操作系统&lt;/p&gt;
&lt;p&gt;我新的电脑就是这样干的,省去很多莫名其妙的问题;但也会导致编码格式 或者老的程序显示问题&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/qq_39124701/article/details/142953431&quot;&gt;UTF8编码的PowerShell脚本中文乱码的解决方法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/zu1k/nali/issues/185&quot;&gt;Powershell 7.4.0 输出乱码&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>TortoiseSVN调用powershell命令及调试详解</title><link>https://blog.wangshuai.app/2025-12-14-TortoiseSVN%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%A7%A6%E5%8F%91Jenkins/</link><guid isPermaLink="true">https://blog.wangshuai.app/2025-12-14-TortoiseSVN%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%A7%A6%E5%8F%91Jenkins/</guid><pubDate>Sun, 14 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;一直很喜欢自动化的东西,自己的项目都配置有&lt;code&gt;CI/CD&lt;/code&gt;;但是公司的项目单独用的&lt;code&gt;TortoiseSVN&lt;/code&gt;和&lt;code&gt;Jenkins&lt;/code&gt;,运维也没有配置服务端的&lt;code&gt;Piplile&lt;/code&gt;;因此自己在&lt;code&gt;TortoiseSVN&lt;/code&gt;使用&lt;code&gt;Hook Scripts&lt;/code&gt;的客户端触发方法,本机执行&lt;code&gt;Jenkins&lt;/code&gt;构建,因为&lt;code&gt;Jenkins&lt;/code&gt;直接支持&lt;code&gt;API&lt;/code&gt;创建构建,所以整体流程还算顺利&lt;/p&gt;
&lt;p&gt;脚本实现的功能:&lt;/p&gt;
&lt;p&gt;在每个SVN项目根目录中添加&lt;code&gt;Post-commit&lt;/code&gt;后,根据脚本中的配置,按需在指定路径代码更新后触发指定服务的构建;支持参数设置&lt;/p&gt;
&lt;p&gt;本文将指导:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;任意脚本命令调试方法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Powershell&lt;/code&gt;调试方法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;API调用Jenkins方法&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TortoiseSVN&lt;/code&gt;触发&lt;code&gt;Jenkins&lt;/code&gt;实用脚本&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;h4&gt;API调用&lt;code&gt;Jenkins&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;我的&lt;code&gt;Jenkins&lt;/code&gt;版本为&lt;code&gt;2.190.1&lt;/code&gt; &lt;code&gt;Jenkins&lt;/code&gt;的触发请查看&lt;code&gt;Jenkins&lt;/code&gt; &lt;code&gt;API&lt;/code&gt;;在网页的最下面有显示;地址为:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[Jenkins地址]/api/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;认证方法为&lt;code&gt;Basic&lt;/code&gt;认证,&lt;code&gt;API Token&lt;/code&gt;请在用户设置中创建&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/jenkins_apitoken.png&quot; alt=&quot;jenkins_apitoken &quot; /&gt;&lt;/p&gt;
&lt;p&gt;新建构建在&lt;code&gt;Create Job&lt;/code&gt;中有说明;&lt;/p&gt;
&lt;p&gt;我的CURL命令是这样:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl --location &apos;http://192.168.1.23/job/AAA.Web/buildWithParameters&apos; \
--header &apos;Authorization: Basic XYZZZZXX=&apos; \
--form &apos;REVISION=&quot;HEAD&quot;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AAA.Web&lt;/code&gt;为项目(&lt;code&gt;Job&lt;/code&gt;)名称;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Authorization&lt;/code&gt;为&lt;code&gt;PostMan&lt;/code&gt;使用&lt;code&gt;Basic Auth&lt;/code&gt;自动生成,用户名为账号,密码为&lt;code&gt;APIKey&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;form&lt;/code&gt;为我的构建参数&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;code&gt;TortoiseSVN&lt;/code&gt;提交代码后自动执行脚本&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://tortoisesvn.net/docs/release/TortoiseSVN_en/tsvn-dug-settings.html#:~:text=Client%20Side%20Hook%20Scripts&quot;&gt;官方文档&lt;/a&gt;中有客户端触发事件的详细说明;我这里只需要代码提交后的事件:&lt;code&gt;Post-commit&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/TortoiseSVN_hookscripts.png&quot; alt=&quot;TortoiseSVN_hookscripts &quot; /&gt;&lt;/p&gt;
&lt;p&gt;其中&lt;code&gt;Work Copy Path&lt;/code&gt;为&lt;code&gt;SVN&lt;/code&gt;本地根路径(每个&lt;code&gt;CheckOut&lt;/code&gt;加一条);执行的命令为&lt;code&gt;powershell  SVN\AAA\autoJenkins.ps1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这个&lt;code&gt;autoJenkins.ps1&lt;/code&gt;文件在后面;保存在&lt;code&gt;SVN&lt;/code&gt;本地路径中;因此意思为使用&lt;code&gt;powershell&lt;/code&gt;调用这个脚本&lt;/p&gt;
&lt;p&gt;这里我先给出我的&lt;code&gt;autoJenkins.ps1&lt;/code&gt;脚本文件:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;#
.SYNOPSIS
    Automated Jenkins Build Trigger for SVN Hooks.

.DESCRIPTION
    This script is designed to be integrated with SVN (Subversion) post-commit hooks. 
    It analyzes the SVN revision, commit messages, and changed paths to automatically 
    trigger specific Jenkins jobs via the Jenkins Remote API.

    Features:
    - Trigger Jenkins jobs based on directory path mapping.
    - Support for Commit Message commands: [ci skip], [build: projectName].
    - Automatic detection of modified files using SVN XML logs.
    - UTF-8 logging for both console and local log files.

.PARAMETER PATH
    The execution path provided by the SVN hook.
.PARAMETER REVISION
    The SVN revision number to analyze.
.PARAMETER RESULTPATH
    The physical path of the committed files in the repository.
.PARAMETER projectName
    (Optional) Force a specific Jenkins job name to build.

.EXAMPLE
    .\autoJenkins.ps1 -REVISION &quot;413&quot; -RESULTPATH &quot;G:\work\SVN\LifeHelper\Program\InterFace\WebService&quot;
#&amp;gt;
# 执行命令
# powershell.exe -command &quot;&amp;amp; {start-process powershell.exe (([system.environment]::GetCommandLineArgs()|ForEach-Object -process { &apos;\&quot;{0}\&quot;&apos; -f $_ })[3..([system.environment]::GetCommandLineArgs().Length-1)])}&quot; &quot;-file&quot; &quot;G:\work\SVN\LifeHelper\autoJenkins.ps1&quot;

# powershell  G:\work\SVN\LifeHelper\autoJenkins.ps1
# PATH DEPTH MESSAGEFILE REVISION ERROR 

param(
    [string]$PATH,      # 命令执行路径
    [string]$DEPTH,  # 对应 arg 1
    [string]$MESSAGEFILE,    # 对应 arg 2
    [string]$REVISION,    # 对应 arg 2
    [string]$SVN_ERROR,    # 对应 arg 2
    [string]$RESULTPATH,   # svn文件提交路径
    [string]$projectName    # 发布的项目名称
)

[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
$OutputEncoding = [System.Text.UTF8Encoding]::new()

Write-Host &quot;$(Get-Date -Format &apos;yyyy-MM-dd HH:mm:ss.fff&apos;) start script&quot;
Add-Content svn-hook.log &quot;$(Get-Date -Format &apos;yyyy-MM-dd HH:mm:ss.fff&apos;) start script&quot;

# Read-Host &quot;Press Enter to exit&quot;

$svnPath = $env:svnPath;
if ([string]::IsNullOrWhiteSpace($svnPath)) {
       $svnPath=&quot;D:\svn\repo&quot;
}


$pathMap = @(
    [PSCustomObject]@{
        Path = &quot;\Program\InterFace\WebService&quot;
        JobName     = &quot;WebService&quot;
    } 
)


# Jenkins 服务器地址
$JENKINS_URL=&quot;http://192.168.1.100:8080&quot;

$username = &quot;007&quot;
# 注意：如果是 Jenkins，这里通常填 API Token，而不是登录密码
$password = &quot;xxx&quot; 

function Log {
    param([string]$Message)
    Write-Host &quot;$(Get-Date -Format &apos;yyyy-MM-dd HH:mm:ss&apos;) $Message&quot;
    Add-Content svn-hook.log &quot;$(Get-Date -Format &apos;yyyy-MM-dd HH:mm:ss&apos;) $Message&quot;
}
Log &quot;PATH: $PATH&quot;
Log &quot;DEPTH: $DEPTH&quot;
Log &quot;MESSAGEFILE: $MESSAGEFILE&quot;
Log &quot;REVISION: $REVISION&quot;
Log &quot;SVN_ERROR: $SVN_ERROR&quot;
Log &quot;RESULTPATH: $RESULTPATH&quot;

$script:listProjectName = @()
# Read-Host &quot;Press Enter to exit&quot;
# 发送 Jenkins 构建请求
function BuildProject {
    param(        
        [string]$projectName,
        [string]$hookPath = &quot;&quot;
    )
    
     # 检查是否存在
    if ($script:listProjectName -contains $ProjectName) {
        Log &quot;ignore project: $ProjectName&quot;
        return
    }

    # 添加到脚本作用域的变量中 (注意 script: 前缀)
    $script:listProjectName += $ProjectName
    # 脚本目录
    # $ScriptDir = $PSScriptRoot
    # $ParentDir = $ScriptDir
    $ParentDir = $hookPath

    $JOB_NAME=$projectName

    # 不设置则取父级名称为项目名称
    if ([string]::IsNullOrWhiteSpace($projectName)) {
        $JOB_NAME=Split-Path $ParentDir -Leaf
    }
    Log &quot; url host is :${JENKINS_URL} &quot;

    $uri = &quot;$($JENKINS_URL.TrimEnd(&apos;/&apos;))/job/$JOB_NAME/buildWithParameters&quot;

    Log &quot;url: $uri&quot;

    $authPair = &quot;$($username):$($password)&quot;
    $encodedCredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($authPair))
    $headers = @{
        Authorization = &quot;Basic $encodedCredentials&quot;
    }

    Invoke-WebRequest -UseBasicParsing `
    -Uri $uri `
    -Method Post `
    -Headers $headers `
    -Body @{
        REVISION=&quot;HEAD&quot;
    }
}
function CheckChildUpdate {
    # ---------------------- 配置区域 ----------------------
    param(
        [string]$Revision = &quot;413&quot;,
        [string]$WatchDir = &quot;/&quot;
    )

    # ---------------------- 1. 获取 SVN 日志 ----------------------
    Log &quot;正在查询 SVN 版本: $Revision ...&quot;
    Log &quot;WatchDir is: $WatchDir&quot;

    # 使用 --xml 参数，这样 PowerShell 可以直接转换成对象，不用去截取字符串
    # 这里的 2&amp;gt;&amp;amp;1 是为了捕获可能的错误
    try {
        # 注意：如果是私有仓库，可能需要追加 --username &quot;xxx&quot; --password &quot;xxx&quot;
        [xml]$svnLog = svn log . -r $Revision -v --xml
    }
    catch {
        Log &quot;SVN 命令执行失败，请检查 SVN 命令行工具是否安装，或 URL/权限是否正确。&quot;
        # Read-Host &quot;Press Enter to exit&quot;
        exit 1
    }
    Log &quot;get svn log done.&quot;

    # ---------------------- 2. 提取变更文件列表 ----------------------
    # 容错处理：确保 XML 结构存在
    if ($null -eq $svnLog.log.logentry) {
        Log &quot;未找到版本信息 (可能版本号不存在)&quot;
        exit
    }

    # 获取所有变更路径的节点
    # .InnerText 会获取形如 &quot;/trunk/ProjectA/code.cs&quot; 的字符串
    $changedPaths = $svnLog.log.logentry.paths.path | ForEach-Object { $_.InnerText }

    Log &quot;该版本变更了 $($changedPaths.Count) 个文件。&quot;

    # ---------------------- 3. 匹配逻辑与执行 ----------------------

    # 定义一个标志位
    $isHit = $false

    # 遍历所有变更文件
    foreach ($pathTemp in $changedPaths) {
    
        # Write-Host &quot;变更文件: $path&quot;
        Log &quot;pathTemp  is: $pathTemp&quot;

        $targetPath=Join-Path (Split-Path $Path  -Parent) $pathTemp
        Log &quot;targetpath: $targetPath&quot;

        if ($targetPath -like &quot;$WatchDir*&quot;) {
            Log &quot; fiind target path: $pathTemp&quot;
            $isHit = $true
            break 
        }
    }
    return $isHit
}
function BuildWithCommand {
    # ---------------------- 配置区域 ----------------------
    param(
        [string]$Revision = &quot;413&quot;,
        [string]$WatchDir = &quot;/&quot;
    )

    # ---------------------- 1. 获取 SVN 日志 ----------------------
    Log &quot;for BuildWithCommand search svn version: $Revision ...&quot;
    Log &quot;WatchDir is: $WatchDir&quot;

    # 使用 --xml 参数，这样 PowerShell 可以直接转换成对象，不用去截取字符串
    # 这里的 2&amp;gt;&amp;amp;1 是为了捕获可能的错误
    try {
        # 注意：如果是私有仓库，可能需要追加 --username &quot;xxx&quot; --password &quot;xxx&quot;
        [xml]$svnLog = svn log . -r $Revision -v --xml
    }
    catch {
        Log &quot;SVN 命令执行失败，请检查 SVN 命令行工具是否安装，或 URL/权限是否正确。&quot;
        # Read-Host &quot;Press Enter to exit&quot;
        exit 1
    }
    Log &quot;get svn log done.&quot;

    # ---------------------- 2. 提取变更文件列表 ----------------------
    # 容错处理：确保 XML 结构存在
    if ($null -eq $svnLog.log.logentry) {
        Log &quot;未找到版本信息 (可能版本号不存在)&quot;
        exit
    }

    $commitMsg = $svnLog.log.logentry.msg.ToString()

    Log &quot;Commit Message: $commitMsg&quot;

    # ---------------------- 3. 匹配逻辑与执行 ----------------------

    # 定义一个标志位
    $isHit = $false

    # ----------------------------------------------------
    # 规则 A: 检查是否跳过 CI
    # 使用正则匹配，(?i) 表示不区分大小写
    # ----------------------------------------------------
    if ($commitMsg -match &quot;(?i)\[(ci skip|skip ci|no build)\]&quot;) {
        Log &quot;检测到跳过指令 [ci skip]，停止执行 CI 流程。&quot;
        exit 0 # 正常退出，不报错
    }

    # ----------------------------------------------------
    # 规则 B: 检查是否强制构建特定项目 (针对你的 LifeHelper)
    # ----------------------------------------------------
    if ($commitMsg -match &quot;(?i)\[build:\s*web\]&quot;) {
        Log &quot;检测到 [build: web] 指令，强制触发 Web 构建...&quot;
        # 调用 Web 的构建函数
        # Invoke-JenkinsJob &quot;LifeHelper.Web&quot;
    }

    $pattern = &quot;build\s*:\s*([\w\.-]+)&quot;

    if ($commitMsg -match $pattern) {
        # $matches[0] 是匹配到的完整字符串 (例如 &quot;build: LifeHelper.Web&quot;)
        # $matches[1] 是括号里捕获的内容 (即我们要的 &quot;LifeHelper.Web&quot;)
        
        $serviceName = $matches[1]
        
        Log &quot;match build server: [$serviceName]&quot;
    
        # 这里可以根据提取到的名称去执行逻辑
        if ($serviceName -eq &quot;LifeHelper.Web&quot;) {
            # 触发 Web 构建
        }
        BuildProject $serviceName
        exit 0 # 正常退出，不报错
    }

    return $isHit
}
# ([system.environment]::GetCommandLineArgs()|ForEach-Object -process { &apos;\&quot;{0}\&quot;&apos; -f $_ })
$myArgs = [System.Environment]::GetCommandLineArgs()

$target = $RESULTPATH

for ($i = 0; $i -lt $myArgs.Length; $i++) {
    Log (&quot;arg[{0}] = `&quot;{1}`&quot;&quot; -f $i, $myArgs[$i])
    # if($i -eq 7){
    #     $target=$myArgs[$i];
    # }
}
Log &quot;target is: $target&quot;
$PATH=$myArgs[1]
Log &quot;new PATH: $PATH&quot;

if (![string]::IsNullOrWhiteSpace($projectName)) {
       BuildProject $projectName
}

BuildWithCommand -Revision $REVISION -WatchDir $target

$fullPathMap = [System.Collections.Generic.List[PSCustomObject]]::new()

foreach ($kv in $pathMap) {
    $key=$kv.Path;
     Log &quot;key is $key &quot;
    $fullPath = Join-Path $svnPath $key
    Log &quot;full path ${fullPath}&quot;
    Log &quot;kv is $kv &quot;
    # Log &quot;kvvalue is $(pathMap[$key])&quot;
    $obj = [PSCustomObject]@{
        Path = $fullPath
        JobName     = $kv.JobName
    }
    $fullPathMap.Add($obj);
    # $fullPathMap.Add($fullPath,$(pathMap[$key]))
}


Log &quot;target is: $targetNorm&quot;

# 规范化路径（非常重要）
# $targetNorm = [System.IO.Path]::GetFullPath($target).TrimEnd(&apos;\&apos;)
$targetNorm = $target
Log &quot;targetNorm is: $targetNorm&quot;
# 匹配并取 Value（核心逻辑）
$matchedValue = $null

foreach ($entry in $fullPathMap) {
    $keyNorm = [System.IO.Path]::GetFullPath($entry.Path).TrimEnd(&apos;\&apos;)
    Log &quot;keyNorm is: $keyNorm&quot;
 
    if ($targetNorm.StartsWith($keyNorm, [System.StringComparison]::OrdinalIgnoreCase)) {
        $matchedValue = $entry.JobName
        Log &quot;Matched path: $keyNorm =&amp;gt; value: $matchedValue&quot;
        BuildProject  $matchedValue $keyNorm
    }
    else {
        Log &quot;Not matched path: $keyNorm try find by parent&quot;
        if($keyNorm.StartsWith($targetNorm, [System.StringComparison]::OrdinalIgnoreCase)){
            $isHit = CheckChildUpdate -Revision $REVISION -WatchDir $keyNorm
            if($isHit){
                $matchedValue = $entry.JobName
                Log &quot;Matched by child path: $keyNorm =&amp;gt; value: $matchedValue&quot;
                BuildProject  $matchedValue $keyNorm
            }
        }
    }
}

if ($null -eq $matchedValue) {
    Log &quot;No path matched: $targetNorm&quot;
    # Read-Host &quot;Press Enter to exit&quot;
    return
}

Write-Host &quot;$(Get-Date -Format &apos;yyyy-MM-dd HH:mm:ss.fff&apos;) script end&quot;
Add-Content svn-hook.log &quot;$(Get-Date -Format &apos;yyyy-MM-dd HH:mm:ss.fff&apos;) commit done&quot;
# Read-Host &quot;Press Enter to exit&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;配置说明&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;配置项&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;svnPath&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SVN&lt;/code&gt;根目录环境变量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pathMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SVN&lt;/code&gt;监听目录及其对应的&lt;code&gt;Jenkins&lt;/code&gt;名称字典;此为&lt;code&gt;SVN&lt;/code&gt;目录中的相对路径;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;JENKINS_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Jenkins&lt;/code&gt;服务地址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;username &lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Jenkins&lt;/code&gt;服务用户&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;password&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Jenkins&lt;/code&gt;服务API Token&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;因为脚本用到了&lt;code&gt;SVN&lt;/code&gt;获取提交记录的方法,因此需要安装&lt;code&gt;SVN CLI&lt;/code&gt;;在安装&lt;code&gt;TortoiseSVN&lt;/code&gt;时需选择&lt;code&gt;command line client tools&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/TortoiseSVN_command_line.png&quot; alt=&quot;TortoiseSVN&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;用法&lt;/h4&gt;
&lt;p&gt;本来是先写这个文章的,结果发现脚本有些问题;经常会报错;于是只能先解决报错;就有了前一篇文章:&lt;a href=&quot;2025-12-20-powershell-syntax-error/&quot;&gt;解决powershell表达式或语句中包含意外的标记问题 &lt;/a&gt;;又经过几次测试使用;改良了脚本;&lt;/p&gt;
&lt;p&gt;当有多个不同目录的文件提交到&lt;code&gt;SVN&lt;/code&gt;时,它的&lt;code&gt;RESULTPATH&lt;/code&gt;会是所有目录的最小父级,因此要能根据&lt;code&gt;SVN&lt;/code&gt;提交的目录来触发对应的&lt;code&gt;Job&lt;/code&gt;还要从&lt;code&gt;REVISION&lt;/code&gt;中遍历所有文件;找到对应的配置匹配的目录触发多个&lt;code&gt;Job&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;此脚本功能:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;根据配置(&lt;code&gt;$pathMap&lt;/code&gt;)在指定&lt;code&gt;svn&lt;/code&gt;目录&lt;code&gt;commit&lt;/code&gt;后触发对应的&lt;code&gt;Jenkins Job&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用命令直接触发&lt;code&gt;Jenkins Job&lt;/code&gt;,命令:&lt;code&gt;.\autoJenkins.ps1 -projectName &quot;JobName&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;从历史版本号触发对应的&lt;code&gt;Jenkins Job&lt;/code&gt;,命令:&lt;code&gt;.\autoJenkins.ps1 -REVISION &quot;500&quot; -RESULTPATH &quot;G:\work\SVN\LifeHelper\Program\InterFace\WebService&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;SVN&lt;/code&gt; &lt;code&gt;Commit&lt;/code&gt;中编写&lt;code&gt;[build: MySpecialJob]&lt;/code&gt; 直接触发构建&lt;code&gt;MySpecialJob&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Powershell调试方法&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;VS Code&lt;/code&gt;中一般调试方法&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;1.安装 &lt;strong&gt;PowerShell 扩展&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2.打开 &lt;code&gt;.ps1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;3.左侧点红点打断点&lt;/p&gt;
&lt;p&gt;4.&lt;code&gt;F5&lt;/code&gt; 启动调试&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;假如需要参数可以在&lt;code&gt;PowerShell Extension&lt;/code&gt;的控制台中直接输入带参数的命令触发&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/vscode_powershell_extension.png&quot; alt=&quot;vscode_powershell_extension&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;TERMINAL&lt;/code&gt;中直接输入脚本带参数命令&lt;code&gt;powershell.exe -File  &quot;G:\work\SVN\LifeHelper\autoJenkins.ps1&quot; -Revision 537&lt;/code&gt;就可以直接中断调试&lt;/p&gt;
&lt;h3&gt;调试&lt;code&gt;Post-commit&lt;/code&gt;命令的方法&lt;/h3&gt;
&lt;p&gt;如果直接使用&lt;code&gt;powershell  SVN\AAA\autoJenkins.ps1&lt;/code&gt;这样的触发命令;因为命令后台执行;假如有意外的错误完全摸不着头脑&lt;/p&gt;
&lt;p&gt;那有什么可以实时看到控制台输出及结果的方法吗?&lt;/p&gt;
&lt;p&gt;我在这位老哥那里找到了:&lt;a href=&quot;https://www.mikeobrien.net/blog/using-tortoisesvn-client-hook-script-to&quot;&gt;Using a TortoiseSVN Client Hook Script to Run a Visible PowerShell Script&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;将&lt;code&gt;powershell  SVN\AAA\autoJenkins.ps1&lt;/code&gt;命令替换为以下内容&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;powershell.exe -command &quot;&amp;amp; {start-process powershell.exe (([system.environment]::GetCommandLineArgs()|ForEach-Object -process { &apos;\&quot;{0}\&quot;&apos; -f $_ })[3..([system.environment]::GetCommandLineArgs().Length-1)])}&quot; &quot;-file&quot; &quot;G:\work\SVN\LifeHelper\autoJenkins.ps1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它可以将启动参数传递给一个新的有前台界面的&lt;code&gt;powershell&lt;/code&gt;进程;这样就可以直观看到命令结果了&lt;/p&gt;
&lt;p&gt;假如命令太快弹窗一下就消失,可以在代码中添加&lt;code&gt;Read-Host &quot;Press Enter to exit&quot;&lt;/code&gt;这样的暂停代码;这样只有在回车确认之后才回继续执行&lt;/p&gt;
&lt;p&gt;如果还有一些异常情况;可以本地开启录屏;在录像中逐帧分析报错&lt;/p&gt;
&lt;h3&gt;结语&lt;/h3&gt;
&lt;p&gt;实测&lt;code&gt;Post-commit&lt;/code&gt;触发器只在&lt;code&gt;svn&lt;/code&gt;的命令执行时才有效;比如文件管理器集成的&lt;code&gt;TortoiseSVN&lt;/code&gt; &lt;code&gt;GUI&lt;/code&gt;操作,&lt;code&gt;SVN Commit&lt;/code&gt;命令以及&lt;code&gt;visual studio&lt;/code&gt;的&lt;code&gt;VisualSVN&lt;/code&gt;插件操作;&lt;/p&gt;
&lt;p&gt;第三方集成的&lt;code&gt;svn&lt;/code&gt;客户端无法触发;比如&lt;code&gt;IDEA&lt;/code&gt;的&lt;code&gt;SVN&lt;/code&gt; &lt;code&gt;VCS&lt;/code&gt;代码工具无法触发&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mikeobrien.net/blog/using-tortoisesvn-client-hook-script-to&quot;&gt;Using a TortoiseSVN Client Hook Script to Run a Visible PowerShell Script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tortoisesvn.net/docs/release/TortoiseSVN_en/tsvn-dug-settings.html&quot;&gt;TortoiseSVN:Client Side Hook Scripts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>lnav 自定义格式 解析nlog 日志 简明教程</title><link>https://blog.wangshuai.app/2025-12-08-lnav-nlog-sample/</link><guid isPermaLink="true">https://blog.wangshuai.app/2025-12-08-lnav-nlog-sample/</guid><pubDate>Mon, 08 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最开始,为了解决日志查看的问题;我找到了专业的日志中心:&lt;a href=&quot;https://datalust.co/&quot;&gt;Seq&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/vXV9IzJ.png&quot; alt=&quot;seq&quot; /&gt;&lt;/p&gt;
&lt;p&gt;它支持结构化日志的过滤筛选,有查询语法;支持类SQL查询数据;有了它很多很麻烦的问题一眼就找到bug所在;真是生产力工具 ,我在我&lt;code&gt;NAS&lt;/code&gt;中部署它接入了接手的所有项目&lt;/p&gt;
&lt;p&gt;但在公司的正式环境中因为网络以及其它的因素却不能使用;这带来了很大的不便;后来我发现了有一个轻量型的命令行工具&lt;a href=&quot;https://docs.lnav.org/en/latest/index.html&quot;&gt;&lt;code&gt;lnav&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这可以一定程度上替代部分&lt;code&gt;SEQ&lt;/code&gt;的功能;本文介绍 &lt;code&gt;lnav&lt;/code&gt;应用在&lt;code&gt;c#&lt;/code&gt; &lt;code&gt;nlog&lt;/code&gt;日志的教程&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;lnav&lt;/code&gt;解析日志文件依赖于正则表达式及相关配置,但是网上却没有&lt;code&gt;lnav&lt;/code&gt;分析&lt;code&gt;nlog&lt;/code&gt;日志的教程;本文权当抛砖引玉&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nlog&lt;/code&gt;的配置在&lt;code&gt;nlog.config&lt;/code&gt;中&lt;/p&gt;
&lt;p&gt;日志格式定义于&lt;code&gt;target.layout&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;我的日志格式是这样:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;${longdate}|${event-properties:item=EventId.Id}|${logger}|${uppercase:${level}}|${message}|${exception:format=toString}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在创建自己的&lt;code&gt;format&lt;/code&gt;时请留意区别&lt;/p&gt;
&lt;p&gt;&lt;code&gt;lnav&lt;/code&gt;我安装的&lt;code&gt;windwos&lt;/code&gt;版它的自定义格式目录在:&lt;code&gt;%APPDATA%\lnav\formats\installed&lt;/code&gt;,首次运行时会自行创建;在&lt;code&gt;%APPDATA%\lnav\formats\default&lt;/code&gt;中有官方的解析其它日志格式的示例,请自行参考&lt;/p&gt;
&lt;p&gt;假如不确定你的目录,请输入&lt;code&gt;lnav.exe -h&lt;/code&gt;查看&lt;/p&gt;
&lt;p&gt;这里我先给出我的&lt;code&gt;nlog.json&lt;/code&gt;配置:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;$schema&quot;: &quot;https://lnav.org/schemas/format-v1.schema.json&quot;,
    &quot;nlog_log&quot; : {
        &quot;title&quot; : &quot;c# nlog Log Format&quot;,
        &quot;description&quot; : &quot;Log format used Nlog with c#&quot;,
        &quot;url&quot; : &quot;http://example.com/log-format.html&quot;,
        &quot;regex&quot; : {
            &quot;basic&quot; : {
                &quot;pattern&quot; : &quot;(?&amp;lt;timestamp&amp;gt;[^|]+)\\|(?&amp;lt;eventid&amp;gt;[^|]*)\\|(?&amp;lt;logger&amp;gt;[^|]+)\\|(?&amp;lt;level&amp;gt;[^|]+)\\|(?&amp;lt;message&amp;gt;[^|]*)\\|(?&amp;lt;exception&amp;gt;.*)&quot;
            }
        },
        &quot;timestamp-format&quot;: [
            &quot;%Y-%m-%d %H:%M:%S&quot;
        ],
        &quot;level-field&quot; : &quot;level&quot;,
        &quot;level&quot; : {
            &quot;error&quot; : &quot;ERROR&quot;,
            &quot;info&quot; : &quot;INFO&quot;,
            &quot;warning&quot; : &quot;WARN&quot;
        },
        &quot;value&quot; : {       
             &quot;logger&quot;: {
                &quot;kind&quot;: &quot;string&quot;,
                &quot;identifier&quot;: true
            }
        },
        &quot;sample&quot; : [
            {
                &quot;line&quot; : &quot;2023-11-02 10:49:34.9658||XXXX.Cache.FactoryCache|ERROR|机构枚举类型加载失败。|System.Net.Http.HttpRequestException: 由于目标计算机积极拒绝，无法连接。 (127.0.0.1:YYYY) ---&amp;gt; &quot;
            }
        ]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;配置解读&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;timestamp-format&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;官方文档:&lt;a href=&quot;https://docs.lnav.org/en/latest/formats.html#format-file-reference&quot;&gt;formats&lt;/a&gt;有对时间格式的详细解释,&lt;code&gt;%Y-%m-%d %H:%M:%S&lt;/code&gt;可以解析年月日时分秒&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;value&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;value&lt;/code&gt;中的&lt;code&gt;&quot;identifier&quot;:ture&lt;/code&gt;表示设置这个匹配字段(&lt;code&gt;logger&lt;/code&gt;)为关键字,这样就会高亮显示了&lt;/p&gt;
&lt;p&gt;假如你的配置有误,执行&lt;code&gt;lnav.exe  xxxx.log&lt;/code&gt;命令时会提示,参照修改即可&lt;/p&gt;
&lt;h3&gt;简明教程&lt;/h3&gt;
&lt;p&gt;执行&lt;code&gt;lnav.exe  xxxx.log yyy.log&lt;/code&gt;打开日志文件后页面如图&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/lnav_nlog_sample.png&quot; alt=&quot;lnav_nlog_sample&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;e&lt;/code&gt; 跳转到下个错误&lt;/li&gt;
&lt;li&gt;按&lt;code&gt;F2&lt;/code&gt;启动鼠标操作;&lt;/li&gt;
&lt;li&gt;左上角可选时间过滤&lt;/li&gt;
&lt;li&gt;双击日志内容会弹出&lt;code&gt;Actions&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Filter-in&lt;/code&gt;表示只显示与选中的内容相同的行&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Filter-out&lt;/code&gt;表示排除选中内容相同的文本的行&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Search&lt;/code&gt;搜索当前内容&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.lnav.org/en/latest/intro.html&quot;&gt;lnav documents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.to/xiaodao/lnav-zi-ding-yi-ri-zhi-ge-shi-zhi-chi-578&quot;&gt;lnav 自定义日志格式支持&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>微信小程序cli工具miniprogram-ci报错处理</title><link>https://blog.wangshuai.app/2025-12-07-miniprogram-ci_browserslist_error/</link><guid isPermaLink="true">https://blog.wangshuai.app/2025-12-07-miniprogram-ci_browserslist_error/</guid><pubDate>Sun, 07 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近在&lt;code&gt;Jenkins&lt;/code&gt;上的小程序发布报错:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;child process stderr: Browserslist: browsers &lt;strong&gt;data&lt;/strong&gt; (caniuse-lite) &lt;strong&gt;is&lt;/strong&gt; 14 months old. Please run:
npx update-browserslist-db@latest
Why you should &lt;strong&gt;do&lt;/strong&gt; it regularly: https://github.com/browserslist/update-db#readme&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这里记录一下解决方案&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;出现问题时的&lt;code&gt;miniprogram-ci&lt;/code&gt;版本是&lt;code&gt;2.0.10&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;直接按照提示执行&lt;code&gt;npx update-browserslist-db@latest&lt;/code&gt;会发现并不能解决问题&lt;/p&gt;
&lt;p&gt;根据提示,是知道因为包过期了导致编译失败;因此更新&lt;code&gt;miniprogram-ci&lt;/code&gt;版本就行了&lt;/p&gt;
&lt;h4&gt;操作步骤&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;首先直接尝试更新到最新版&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;执行命令&lt;code&gt;npm install -g miniprogram-ci@latest&lt;/code&gt;安装好后尝试发布,现在是不会报这个错了;但是因为小程序很大,会报超2M的的问题:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[error] 20003 Error: {&quot;errCode&quot;:-1,&quot;errMsg&quot;:&quot;inner upload fail with errcode: 80200, errmsg: main package source size 2576KB exceed max limit 2048KB&quot;}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;尝试直接使用开发者工具会发现主包只有1.6M,可以直接上传;而且同样的代码;在升级&lt;code&gt;cli&lt;/code&gt;前是不会有这个问题的;因此需要换一个&lt;code&gt;ci&lt;/code&gt;版本&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;找到其它主要&lt;code&gt;miniprogram-ci&lt;/code&gt;版本多次尝试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在npm官网:&lt;a href=&quot;https://www.npmjs.com/package/miniprogram-ci?activeTab=versions&quot;&gt;miniprogram-ci&lt;/a&gt;上可以看到所有版本;依次尝试最新的&lt;code&gt;2.1.29&lt;/code&gt; &lt;code&gt;2.1.28&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;2.1.18&lt;/code&gt;都有同样的问题;再往后看到了&lt;code&gt;1.9.17&lt;/code&gt;;经测试这个版本可以同时解决前面的两个报错;因此解决方法就是执行这个命令:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm install -g miniprogram-ci@1.9.17&lt;/code&gt;&lt;/p&gt;
</content:encoded></item><item><title>暴雪游戏崩溃报错解决办法</title><link>https://blog.wangshuai.app/2025-11-16-sc2%E6%88%98%E7%BD%91%E6%9A%B4%E9%9B%AA%E6%B8%B8%E6%88%8F%E5%87%BA%E7%8E%B0%E4%B8%80%E4%B8%AA%E6%84%8F%E5%A4%96%E9%94%99%E8%AF%AF%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95/</link><guid isPermaLink="true">https://blog.wangshuai.app/2025-11-16-sc2%E6%88%98%E7%BD%91%E6%9A%B4%E9%9B%AA%E6%B8%B8%E6%88%8F%E5%87%BA%E7%8E%B0%E4%B8%80%E4%B8%AA%E6%84%8F%E5%A4%96%E9%94%99%E8%AF%AF%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95/</guid><pubDate>Sun, 16 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;前一段时间玩SC2的时候经常会崩溃报错:程序运行中出现意外错误&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/sc2_crash.jpg&quot; alt=&quot;sc2_crash&quot; /&gt;&lt;/p&gt;
&lt;p&gt;表现行为就是能开游戏,但是玩2~10分钟就会游戏崩溃;如此往复好几次后我终于找到解决方法&lt;/p&gt;
&lt;p&gt;本解决方案是暴雪游戏崩溃的终极解决方法,任意暴雪游戏出现这个弹窗都可据此找到解决方法&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;我遇到的问题是&lt;code&gt;INT_DIVIDE_BY_ZERO&lt;/code&gt;;通过这个关键字你可以找到很多相关的内容;&lt;/p&gt;
&lt;p&gt;根本原因就是13,14代&lt;code&gt;Intel CPU&lt;/code&gt;缺陷导致程序异常,基本的处理方法就是给CPU降频&lt;/p&gt;
&lt;p&gt;这里的报错只是弹窗提示程序出现意外错误,实际的异常在日志文件中记录;&lt;/p&gt;
&lt;p&gt;因此终极解决办法就是:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从本地磁盘中找到异常报错明细&lt;/li&gt;
&lt;li&gt;根据报错明细网上搜索前辈的解决方法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后面我也会给出我这里的处理方法(CPU降频方法)&lt;/p&gt;
&lt;h4&gt;操作步骤&lt;/h4&gt;
&lt;h5&gt;找到崩溃报错明细&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;打开崩溃游戏的日志&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SC2&lt;/code&gt;的崩溃日志在&lt;code&gt;%USERPROFILE%\Documents\StarCraft II\GameLogs&lt;/code&gt;中,其它游戏请在对应目录中查找(假如你不知道你的游戏名字,打开战网游戏设置,安装路径的最后一层就是游戏名字);&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/sc2_gamepath.png&quot; alt=&quot;game path&quot; /&gt;&lt;/p&gt;
&lt;p&gt;找到最近一次&lt;code&gt;Crash&lt;/code&gt;日志(文件夹形如&lt;code&gt;2025-11-17 12.17.32 XXX B11111 Crash&lt;/code&gt;);打开&lt;code&gt;XXXXCrash.txt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/SC2_crash_txt.png&quot; alt=&quot;SC2&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;找到报错明细&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;Crash.txt&lt;/code&gt;中有很多内容,最关键的就是&lt;code&gt;BlizzardError.Summary&lt;/code&gt;,可以看到我这里的报错内容是:&lt;code&gt;INT_DIVIDE_BY_ZERO : DBG-OPTIONS&amp;lt;FunctionsOnly SingleLine&amp;gt;&lt;/code&gt;;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/sc2_crash_blizzarderror.png&quot; alt=&quot;BlizzardError&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在google中搜索&lt;code&gt;sc2 INT_DIVIDE_BY_ZERO&lt;/code&gt;第一个就是解决方法&lt;/p&gt;
&lt;h5&gt;INT_DIVIDE_BY_ZERO处理方法&lt;/h5&gt;
&lt;p&gt;我的主板是&lt;code&gt;Micro-Star PRO Z690-A WIFI DDR4&lt;/code&gt;从网上搜索微星主板CPU降频方法很容易就知道怎么做&lt;/p&gt;
&lt;p&gt;我做的调整是:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;关闭&lt;code&gt;Intel SpeedStep&lt;/code&gt;和&lt;code&gt;Intel Speed Shift&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;设置&lt;code&gt;p-core ratio&lt;/code&gt; 从&lt;code&gt;+1&lt;/code&gt; 到 &lt;code&gt;-2&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这里我设置了几次,每次&lt;code&gt;-1&lt;/code&gt;然后测试游戏,最终发现&lt;code&gt;-2&lt;/code&gt;不会报错;你可以多次测试找到你最佳设置&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;结语&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;sc2&lt;/code&gt;作为一个&lt;code&gt;Dead Game&lt;/code&gt;崩溃肯定不是游戏内的原因&lt;/p&gt;
&lt;p&gt;而这次问题其实之前早已预见;魔兽世界一早就遇到同样的问题;有很多人反馈给了暴雪;因此当时进游戏就会弹窗提示:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;《魔兽世界》检测到你的电脑正在使用英特尔第13或14代处理器，且未安装合适的更新...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最终这个问题还是到了我CPU上&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;只要你的问题合理,报错清晰;那就很容易从关键字中找到解决方案&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为大多数问题,都不会是你第一个遇到;网上找找,就很容易有解决方法;这才是任何问题的&lt;strong&gt;终极解决方法&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/wow/comments/1dspd3t/game_crash_int_divide_by_zero_solution/&quot;&gt;Game Crash INT_DIVIDE_BY_ZERO&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>openwrt升级kwrt指南</title><link>https://blog.wangshuai.app/2025-11-09-openwrt%E5%8D%87%E7%BA%A7kwrt%E6%8C%87%E5%8D%97/</link><guid isPermaLink="true">https://blog.wangshuai.app/2025-11-09-openwrt%E5%8D%87%E7%BA%A7kwrt%E6%8C%87%E5%8D%97/</guid><pubDate>Sun, 09 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;事情起因是最近玩游戏家里网络总是不好,经常遇到ping延迟过高;然后过一段时间路由器就会被强制重新拨号;我猜想这里是因为&lt;code&gt;PCDN&lt;/code&gt;,运营商限制每个家宽的上传;只要超过一定值就会把家里的&lt;code&gt;PPPOE&lt;/code&gt;断开&lt;/p&gt;
&lt;p&gt;我查看&lt;code&gt;接口&lt;/code&gt;→&lt;code&gt;wan&lt;/code&gt;的流量后发现有很大量的上传;为了排查出家里哪个服务在上传;我尝试给路由器加上流量统计(&lt;a href=&quot;https://github.com/timsaya/luci-app-bandix&quot;&gt;luci-app-bandix&lt;/a&gt;);但是因为我软路由很早之前安装的;包管理&lt;code&gt;opkg&lt;/code&gt;的源已经失效(我之前用的&lt;a href=&quot;https://supes.top/?version=22.03&amp;amp;target=x86/64&amp;amp;id=generic&quot;&gt;https://supes.top&lt;/a&gt;的&lt;code&gt;openwrt&lt;/code&gt;版本;版本&lt;code&gt;22.03&lt;/code&gt;现在进网站会发现已经变成&lt;code&gt;openwrt.ai&lt;/code&gt;(&lt;code&gt;kwrt&lt;/code&gt;)了,之前的源也无法访问)无法安装&lt;code&gt;Bandix&lt;/code&gt;于是有了此文&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;我现在安装的&lt;code&gt;openwrt&lt;/code&gt;版本是&lt;code&gt;Kwrt 24.10.4 11.08.2025 by Kiddin&apos; / LuCI openwrt-24.10 branch 26.292.66247~75e41cb&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;所以这里介绍的是&lt;code&gt;openwrt&lt;/code&gt;升级&lt;code&gt;kwrt&lt;/code&gt;操作方法&lt;/p&gt;
&lt;p&gt;我对&lt;code&gt;openwrt&lt;/code&gt;的源码没研究,但从改名来看肯定有不少区别;&lt;/p&gt;
&lt;p&gt;升级之前&lt;strong&gt;强烈建议备份系统&lt;/strong&gt;;我是同时在&lt;code&gt;openwrt&lt;/code&gt;中备份了系统的配置文件以及在&lt;code&gt;PVE&lt;/code&gt;中备份了整个虚拟机&lt;/p&gt;
&lt;p&gt;我一开始尝试的是直接使用&lt;code&gt;系统&lt;/code&gt;→&lt;code&gt;备份与升级&lt;/code&gt;→&lt;code&gt;刷写新的固件&lt;/code&gt;来升级的;升级系统成功了,运行没问题;但遇到两大困难&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;后台页面打不开&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;升级后&lt;code&gt;ssh&lt;/code&gt;没问题,系统看起来正常,没什么报错;但是页面打不开;&lt;code&gt;http://192.168.1.1/luci/&lt;/code&gt;这个地址无法访问&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;路由器拨号失败&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本来后台打不开也没什么,可以自己尝试安装&lt;code&gt;luci&lt;/code&gt;,但没网&lt;code&gt;opkg&lt;/code&gt;无法工作,也没有修复的可能&lt;/p&gt;
&lt;p&gt;跨版本升级,配置不兼容(刷写固件我选的保留配置)我只能花一分钟还原系统然后放弃配置,重新安装重新配置&lt;/p&gt;
&lt;h4&gt;操作步骤&lt;/h4&gt;
&lt;h5&gt;备份系统&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;全量备份&lt;code&gt;openwrt&lt;/code&gt;系统&lt;/p&gt;
&lt;p&gt;直接虚拟机层面备份镜像是最好的;还原10s就完成了极其便捷&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;备份一份配置&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;系统&lt;/code&gt;→&lt;code&gt;备份与升级&lt;/code&gt;→&lt;code&gt;备份&lt;/code&gt;,生成一份备份;因为我是重新安装,但之前的功能还是需要的因此保留配置可以方便后续恢复(系统的配置实际是保存在系统的文件中的.只要有备份的配置后续恢复极其便捷;不用页面一个一个的重新加)&lt;/p&gt;
&lt;h5&gt;升级系统&lt;/h5&gt;
&lt;p&gt;准备一份新的&lt;code&gt;openwrt&lt;/code&gt;固件,在&lt;code&gt;系统&lt;/code&gt;→&lt;code&gt;备份与升级&lt;/code&gt;→&lt;code&gt;刷写新的固件&lt;/code&gt;上传后选择&lt;code&gt;不保留配置&lt;/code&gt;&lt;/p&gt;
&lt;h5&gt;还原配置&lt;/h5&gt;
&lt;p&gt;新的配置一般不会有问题,只是一切需要重头配置;我软路由主要用了三个功能&lt;code&gt;DDNS&lt;/code&gt;,&lt;code&gt;端口转发&lt;/code&gt;和&lt;code&gt;主机名映射&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;DDNS&lt;/code&gt;我用的之前自带的,它的配置位于&lt;code&gt;/etc/config/ddns&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;打开备份配置的对应文件即可知道之前的&lt;code&gt;password&lt;/code&gt;和&lt;code&gt;domain&lt;/code&gt;;&lt;code&gt;Kwrt&lt;/code&gt;中我用的&lt;code&gt;luci-app-ddns-go&lt;/code&gt;,重新配置即可&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;端口转发&lt;/code&gt;配置位于&lt;code&gt;/etc/config/firewall&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;主机名映射&lt;/code&gt;的配置位于&lt;code&gt;/etc/config/dhcp&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些配置假如不知道在哪里可以问&lt;code&gt;GPT&lt;/code&gt;当然,你也可以直接修改任意一个本机的配置,在右上角的&lt;code&gt;未保存的配置&lt;/code&gt;中可以看到修改的文件;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/openwrt_config_location.png&quot; alt=&quot;openwrt_config_location&quot; /&gt;&lt;/p&gt;
&lt;p&gt;据此,要恢复配置只需找到之前备份的对应设置,将之前所有的&lt;code&gt;192.168&lt;/code&gt;这样的配置复制到新的&lt;code&gt;Kwrt&lt;/code&gt;中即可恢复软路由功能&lt;/p&gt;
&lt;h3&gt;常见问题&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Kwrt&lt;/code&gt;后台打不开&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;Kwrt&lt;/code&gt;的首页和之前&lt;code&gt;Openwrt&lt;/code&gt;的不同,需要访问不同的地址(旧地址是:&lt;code&gt;http://192.168.1.1/luci/&lt;/code&gt;新的是&lt;code&gt;http://192.168.1.1/cgi-bin/luci/admin/index&lt;/code&gt;)假如不确定地址,建议使用无痕模式(避免旧的网站缓存)访问网关,一般会自动重定向到正确的地址上;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;主机名映射&lt;/code&gt;失效&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于系统升级,&lt;code&gt;主机名映射&lt;/code&gt;发生了变化,之前用的&lt;code&gt;DNSMasq&lt;/code&gt;服务设置的解析无法立即在升级后的网关DNS服务中生效;我等到第二天恢复的局域网域名就自动恢复了&lt;/p&gt;
</content:encoded></item><item><title>战网通行密钥passkey设置方法</title><link>https://blog.wangshuai.app/2025-10-16-%E6%88%98%E7%BD%91%E9%80%9A%E8%A1%8C%E5%AF%86%E9%92%A5%E8%AE%BE%E7%BD%AE%E6%8C%87%E5%8D%97/</link><guid isPermaLink="true">https://blog.wangshuai.app/2025-10-16-%E6%88%98%E7%BD%91%E9%80%9A%E8%A1%8C%E5%AF%86%E9%92%A5%E8%AE%BE%E7%BD%AE%E6%8C%87%E5%8D%97/</guid><pubDate>Thu, 16 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;今天，战网发布公告：&lt;a href=&quot;https://news.blizzard.com/en-us/article/24240392/passkeys-and-one-time-passcodesfaster-and-safer-ways-to-log-in&quot;&gt;Passkeys and One-Time Passcodes—Faster and safer ways to log in&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;正式宣告战网支持&lt;code&gt;通行密钥&lt;/code&gt; &lt;code&gt;Passkey&lt;/code&gt;;&lt;/p&gt;
&lt;p&gt;那么,什么是&lt;code&gt;通行密钥&lt;/code&gt;呢?&lt;/p&gt;
&lt;p&gt;引用&lt;code&gt;wiki&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;通行密钥&lt;/strong&gt;（英语：Passkey），台湾又称&lt;strong&gt;通行金钥&lt;/strong&gt;、&lt;strong&gt;密码金钥&lt;/strong&gt;，是属于&lt;a href=&quot;https://zh.wikipedia.org/wiki/WebAuthn&quot;&gt;WebAuthn&lt;/a&gt;的一种数字认证凭证，用于网站或应用程序的身份验证[&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E9%80%9A%E8%A1%8C%E5%AF%86%E9%92%A5#cite_note-techrepublic-1&quot;&gt;1]&lt;/a&gt;[&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E9%80%9A%E8%A1%8C%E5%AF%86%E9%92%A5#cite_note-fido-2&quot;&gt;2]&lt;/a&gt;[&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E9%80%9A%E8%A1%8C%E5%AF%86%E9%92%A5#cite_note-google-3&quot;&gt;3]&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;它是2022年苹果优先推出,谷歌随后跟进的无密码登录方式;&lt;/p&gt;
&lt;p&gt;简单来说,就是&lt;strong&gt;用外部可靠设备(手机指纹 FaceID USB密钥)或方式(Windwos hello )替代你输入密码的登录方法&lt;/strong&gt;;由于没有密码因此可以防止密码遗忘,密码泄漏等问题&lt;/p&gt;
&lt;p&gt;具体流程可查看&lt;a href=&quot;https://auth-wiki.logto.io/zh/passkey&quot;&gt;&lt;code&gt;Auth Wiki&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;那么如何使用&lt;code&gt;通行密钥&lt;/code&gt;登录战网呢?&lt;/p&gt;
&lt;p&gt;本文介绍&lt;code&gt;通行密钥&lt;/code&gt;的系统要求,登录方法&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;通行密钥&lt;/code&gt;的使用需要客户端(正在登录的设备)和服务器(你要登录的服务或网站)同时支持;由于战网(服务)已经支持,这里说的要求是客户端要求&lt;/p&gt;
&lt;p&gt;&lt;code&gt;通行密钥&lt;/code&gt;系统要求:&lt;/p&gt;
&lt;p&gt;这里引用少数派&lt;a href=&quot;https://sspai.com/post/77376&quot;&gt;文章&lt;/a&gt;说明:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Apple：iOS 16、iPadOS 16、macOS Ventura 与 tvOS 16 以上均支持，需要打开 iCloud 钥匙串和&lt;a href=&quot;https://support.apple.com/zh-sg/guide/iphone/iphd709a3c46/18.0/ios/18.0&quot;&gt;双重认证&lt;/a&gt;，支持&lt;a href=&quot;https://support.apple.com/zh-cn/guide/iphone/iph0dd1796bb/16.0/ios/16.0&quot;&gt;通过 AirDrop 分享&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Android：Android 9.0 或更高版本，且已开启屏幕锁定功能。如果选择 Google 的密码管理服务，安全密钥支持跨设备同步，且 Android 设备丢失或更换时在新的 Android 设备上登录相同 Google 的账号，并提供所丢失设备的安全 PIN 码、解锁图案或密码即可迁移。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Chrome：Chrome 108 版本以上、Google Play 服务（Google 自动填充框架）为最新版，支持在 Android 与 Android、Android 与特定版本 Chrome 之间同步&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;微软：在 Windows 11 22H2 版本以上支持，需要打开并使用 Windows Hello；而从 Windows 11 24H2 版本&lt;a href=&quot;https://learn.microsoft.com/zh-cn/windows/security/identity-protection/passkeys/?tabs=windows%2Cintune.&quot;&gt;开始&lt;/a&gt;，通行密钥会在登陆了相同微软账户的设备之间无缝同步。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;除了官方的这几种方法,还有一些其它的方法;&lt;/p&gt;
&lt;p&gt;比如第三方密码管理器:&lt;code&gt;1Password&lt;/code&gt;、&lt;code&gt;Bitwarden&lt;/code&gt;;它们也能创建管理&lt;code&gt;通行密钥&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;专为加密而生的安全密钥&lt;code&gt;yubikey&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;所以,登录方法就是使用账号登录战网;在支持的设备上创建密钥;然后就能在创建密钥的设备上直接免密码登录战网了&lt;/p&gt;
&lt;h4&gt;操作步骤&lt;/h4&gt;
&lt;p&gt;创建和使用&lt;code&gt;通行密钥&lt;/code&gt;在微软文档:&lt;a href=&quot;https://learn.microsoft.com/zh-cn/windows/security/identity-protection/passkeys/?tabs=windows%2Cintune#user-experiences&quot;&gt;Passkeys-用户体验&lt;/a&gt;已经有详细配图说明了&lt;/p&gt;
&lt;p&gt;我这里只示范一下战网&lt;code&gt;PC&lt;/code&gt;和手机登录&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用其它方法登录战网并打开&lt;a href=&quot;https://account.battle.net/security&quot;&gt;&lt;code&gt;账号设置&lt;/code&gt;→&lt;code&gt;安全&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;点击&lt;code&gt;创建密钥&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/passkey_winhello.png&quot; alt=&quot;passkey_winhello&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;手机创建也很简单&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/passkey_mi14pro.jpg&quot; alt=&quot;passkey_mi14pro&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;登录时只需输入账号,然后点击&lt;code&gt;使用密鑰&lt;/code&gt;
&lt;img src=&quot;/img/passkey_login.png&quot; alt=&quot;battle_login&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;常见问题&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;假如我PC很老不支持&lt;code&gt;通行密钥&lt;/code&gt;呢?&lt;/p&gt;
&lt;p&gt;大家手机一般换的很勤,新的手机基本都支持;可以用手机的&lt;code&gt;通行密钥&lt;/code&gt;来登录电脑的账号;&lt;/p&gt;
&lt;p&gt;使用方法就是照常在PC上添加&lt;code&gt;通行密钥&lt;/code&gt;在弹窗中选择&lt;code&gt;选择其他通行密钥&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/passkey_another.png&quot; alt=&quot;use_phone&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在弹窗中选择&lt;code&gt;iPhone、iPad或Android设备&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/passkey_another_scan.png&quot; alt=&quot;passkey_another&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后打开手机的&lt;strong&gt;系统相机&lt;/strong&gt;(需要有能调用系统功能的相机,因此微信扫一扫不行)扫描这个二维码，相机拍照的时候页面会出现一个&lt;strong&gt;二维码的图标&lt;/strong&gt;点击后即可(&lt;a href=&quot;https://learn.microsoft.com/zh-cn/windows/security/identity-protection/passkeys/?tabs=linked%2Cintune#passkeys-in-bluetooth-restricted-environments&quot;&gt;这个功能&lt;/a&gt;应该要开启蓝牙才能连接成功)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/passkey_mi14pro_scan.jpg&quot; alt=&quot;passkey_mi14pro_scan&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;结语&lt;/h3&gt;
&lt;p&gt;无密码登录是非常好的登录方式,相信各位在使用指纹解锁手机,&lt;code&gt;FaceId&lt;/code&gt;支付的时候都深有体会,不过在国内,这种技术却有些水土不服;&lt;s&gt;就我所知,目前也就飞书支持.&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;因为国内实名认证要求,大家都用短信验证码登录了;厂商对&lt;code&gt;通行密钥&lt;/code&gt;自然兴致不高.&lt;/p&gt;
&lt;p&gt;所以我推测国服战网也不会有这个功能&lt;/p&gt;
&lt;p&gt;现在国内有&lt;code&gt;飞书&lt;/code&gt; &lt;code&gt;阿里云&lt;/code&gt; &lt;code&gt;腾讯云&lt;/code&gt;支持通行密钥登录&lt;/p&gt;
&lt;p&gt;而国外 &lt;code&gt;1Password&lt;/code&gt;有一个网站:&lt;a href=&quot;https://passkeys.directory/&quot;&gt;Passkeys.directory&lt;/a&gt;罗列了支持通行密钥的网站&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://auth-wiki.logto.io/zh/webauthn&quot;&gt;WebAuthn 是什么？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/zh-cn/windows/security/identity-protection/passkeys/?tabs=windows%2Cintune&quot;&gt;Windows 中对密钥的支持&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sspai.com/post/77376&quot;&gt;一文看懂通行密钥：无密码、更安全的未来，你该如何登录？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>使用ssh连接h3c堡垒机服务器</title><link>https://blog.wangshuai.app/2025-08-01-ssh-h3c-server/</link><guid isPermaLink="true">https://blog.wangshuai.app/2025-08-01-ssh-h3c-server/</guid><pubDate>Fri, 01 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;公司正式环境使用了&lt;code&gt;H3C&lt;/code&gt;的堡垒机,因此在查看正式数据时需使用对应的堡垒机客户端&lt;code&gt;Tgent&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;之前公司还使用过&lt;code&gt;Vone&lt;/code&gt;客户端连接到堡垒机;&lt;/p&gt;
&lt;p&gt;这些堡垒机以我的经验来看都使用的是&lt;code&gt;ssh tunnel&lt;/code&gt;技术&lt;/p&gt;
&lt;p&gt;这一点可以在我使用&lt;code&gt;ssh -J&lt;/code&gt;命令的报错得到印证&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;channel 0: open failed: administratively prohibited: Generic error while opening channel: 0
stdio forwarding failed
kex_exchange_identification: Connection closed by remote host
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;堡垒机(B)登录需要三组IP和用户名密码&lt;/p&gt;
&lt;p&gt;S: 堡垒机外网入口 零信任 &lt;code&gt;Tgent&lt;/code&gt; IP,即为&lt;code&gt;VPN&lt;/code&gt; 服务地址假设 &lt;code&gt;200.0.0.0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;S1:堡垒机登录IP ,即实际管理内网资源的服务网站,IP假设为:&lt;code&gt;10.0.0.0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;B:需要访问的正式服务器内网地址,假设为:&lt;code&gt;192.168.10.0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;客户端C连接目标服务器B的数据流转图大概如下:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;graph LR
 		C-- &quot;Tgent连接&quot; ---&amp;gt;S;
		S-- &quot;Tgent连接&quot; ---&amp;gt;C;
		S-- &quot;内网访问&quot; ---&amp;gt;S1;
	    S1-- &quot;内网访问&quot; --&amp;gt;S;
	    S1--&quot;SSH Tunnel&quot; --&amp;gt;B;
	    B--&quot;SSH Tunnel&quot; ---&amp;gt;S1;  
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;C到S的&lt;code&gt;Tgent&lt;/code&gt;连接的用户密码登录是必输的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/tgent_login.png&quot; alt=&quot;tgent_login&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在进入内网后S到B的连接可直接通过命令简化,本文主要介绍一般登录及简化步骤&lt;/p&gt;
&lt;h4&gt;操作步骤&lt;/h4&gt;
&lt;h5&gt;所需工具&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;VPN&lt;/code&gt;客户端&lt;code&gt;Tgent&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;数据审计工具&lt;code&gt;AccessClient&lt;/code&gt;此工具在帮助页面下载&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SSH&lt;/code&gt;客户端&lt;code&gt;xshell&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;正常GUI登录过程&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;打开&lt;code&gt;Tgent&lt;/code&gt;,输入控制器地址&lt;code&gt;200.0.0.0&lt;/code&gt;,然后输入用户名密码登录&lt;/li&gt;
&lt;li&gt;登录后即可在&lt;code&gt;门户&lt;/code&gt;中查看到账号的堡垒机,点击后进入&lt;code&gt;H3C&lt;/code&gt;运维审计系统&lt;code&gt;10.0.0.0&lt;/code&gt;,同样输入用户名密码登录&lt;/li&gt;
&lt;li&gt;登录后点击访问资产,此处浏览器会提示安装&lt;code&gt;AccessClient&lt;/code&gt;客户端&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/h3c_install_accessclient.png&quot; alt=&quot;install_access_client&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;安装完成后点击资产右侧的访问,选择方式开启&lt;code&gt;ssh&lt;/code&gt;客户端&lt;/p&gt;
&lt;p&gt;根据服务端设置可能需要使用不同的客户端,开启成功后输入对应的密码即可正常使用&lt;/p&gt;
&lt;p&gt;PS:异常情况请查看最后的常见问题&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;Xshell一键登录步骤&lt;/h5&gt;
&lt;p&gt;通过查看&lt;code&gt;AccessClient&lt;/code&gt;打开的&lt;code&gt;SSH&lt;/code&gt;客户端的启动命令&lt;code&gt;&quot;C:\Program Files\VanDyke Software\SecureCRT\securecrt.exe&quot; /T /N root@xxx-aaa /ssh2 10.0.0.0 /P 122 /L account_10.0.0.0 /PASSWORD OTP:OtmBXhl3chsq&lt;/code&gt;,即可了解其作用原理;经分析尝试后得出以下简化登录方法&lt;/p&gt;
&lt;p&gt;从页面点击启动的&lt;code&gt;AccessClient&lt;/code&gt;会启动服务端设定的&lt;code&gt;SSH&lt;/code&gt;客户端,假如本地没有安装则无法连接服务器&lt;/p&gt;
&lt;p&gt;实际使用时,只要支持&lt;code&gt;SSH2&lt;/code&gt;协议即可连接&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;SSH&lt;/code&gt;客户端连接堡垒机服务&lt;code&gt;S1&lt;/code&gt;,目标&lt;code&gt;10.0.0.0&lt;/code&gt;端口根据&lt;code&gt;AccessClient&lt;/code&gt;启动的&lt;code&gt;SSH&lt;/code&gt;客户端命令确定,我这里为122,启动命令可在任务管理器中确定;用户名密码为&lt;code&gt;S1&lt;/code&gt;的用户名密码&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/ssh_secuercrt_cmd.png&quot; alt=&quot;ssh_secuercrt_cmd&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;登录堡垒机的&lt;code&gt;SSH&lt;/code&gt;成功后可查看到当前用户可管理的资产,使用数字切换服务和登录名&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里显示乱码,但不影响使用&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ssh_h3c_resource.png&quot; alt=&quot;ssh_h3c_resource&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入完成再输入目标服务器&lt;code&gt;B&lt;/code&gt;的用户名密码即可登录成功&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是交互式切换登录,实际使用可以用&lt;code&gt;xshell&lt;/code&gt;的&lt;code&gt;Login Scripts&lt;/code&gt;帮助一键登录&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/xshell_login_scripts.png&quot; alt=&quot;login scripts&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Login Scripts&lt;/code&gt;是&lt;code&gt;SSH&lt;/code&gt;连接建立好后立即执行的脚本,我这里输入的是&lt;code&gt;5&lt;/code&gt;,&lt;code&gt;3&lt;/code&gt;和登录密码&lt;/p&gt;
&lt;p&gt;这样即可自动选择资产,选择登录用户以及自动登录;假如部分操作反应慢,可以添加&lt;code&gt;Expect&lt;/code&gt;文本,比如期望&lt;code&gt;Last Login&lt;/code&gt;文本出现后(登录成功后),再输入其它命令,比如登录数据库&lt;/p&gt;
&lt;h3&gt;常见问题&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在访问资产页面点击&lt;code&gt;访问&lt;/code&gt;无任何反应&lt;/p&gt;
&lt;p&gt;需要安装帮助页面的&lt;code&gt;AccessClient&lt;/code&gt;,然后重启浏览器&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;点击&lt;code&gt;访问&lt;/code&gt;后弹出了一个文件选择的框&lt;/p&gt;
&lt;p&gt;此现象表面本地没有对应的客户端,可以根据选择的客户端名字,网上下载对应程序安装后重试&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;弹出的的文件选择错误导致无法启动&lt;code&gt;SSH&lt;/code&gt;客户端&lt;/p&gt;
&lt;p&gt;弹出的文件路径选择后&lt;code&gt;AccessClient&lt;/code&gt;会将路径保存到注册表中,需要修改注册表才能正常运行;注册表路径在日志文件中有记录;&lt;code&gt;AccessClient&lt;/code&gt;日志文件位置为:&lt;code&gt;C:\Users\[用户名]\AppData\Local\Temp\AccessClient.log&lt;/code&gt;;打开后会看到这样的日志:&lt;code&gt; find path, key: Software\Microsoft\Windows\CurrentVersion\App Paths\securecrt.exe&lt;/code&gt;;因此在注册表的&lt;code&gt;Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\securecrt.exe&lt;/code&gt;中添加正确的路径即可&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/2025-05-06-%E6%B7%B1%E4%BF%A1%E6%9C%8D%20%E6%9C%AC%E5%9C%B0%20%E9%80%9A%E8%BF%87%20Easy%20Connect%E5%8F%8A%20ssh%E8%BF%9E%E6%8E%A5%E6%9C%8D%E5%8A%A1%E5%99%A8/&quot;&gt;深信服 本地 通过 Easy Connect及 ssh连接服务器&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>使用sidecar container dump 正在运行的容器</title><link>https://blog.wangshuai.app/2025-05-21-csharp_dump_in_docker/</link><guid isPermaLink="true">https://blog.wangshuai.app/2025-05-21-csharp_dump_in_docker/</guid><pubDate>Wed, 21 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/zh-cn/dotnet/core/diagnostics/debug-memory-leak&quot;&gt;&lt;code&gt;memory dump&lt;/code&gt;&lt;/a&gt;是分析线上程序内存占用非常好的工具&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;windows&lt;/code&gt;上可以直接任务管理器右键生成&lt;code&gt;dump&lt;/code&gt;,假如需要根据条件比如CPU 内存占用到多少生成&lt;code&gt;dump&lt;/code&gt;可以使用&lt;a href=&quot;https://learn.microsoft.com/zh-cn/sysinternals/downloads/procdump&quot;&gt;&lt;code&gt;procdump&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;额外的,还可以使用&lt;a href=&quot;https://www.jetbrains.com/help/dotmemory/Importing_Process_Dumps.html&quot;&gt;&lt;code&gt;dotmemory.exe&lt;/code&gt;&lt;/a&gt;创建&lt;code&gt;dump&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;而&lt;code&gt;linux&lt;/code&gt;呢?&lt;/p&gt;
&lt;p&gt;可以使用&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dump&quot;&gt;&lt;code&gt;dotnet-dump&lt;/code&gt;&lt;/a&gt; 或 &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-gcdump&quot;&gt;&lt;code&gt;dotnet-gcdump&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;经我测试,&lt;code&gt;dotnet-gcdump&lt;/code&gt;安装后可直接执行命令无问题,但在&lt;code&gt;dump&lt;/code&gt;中未发现更多数据,于是尝试使用&lt;code&gt;dotnet-dump&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;但是我在正式服务器上安装一堆东西执行命令后却报错:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet-dump fails with &quot;Writing dump failed (HRESULT: 0x80004005)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;GPT&lt;/code&gt;告诉我这是因为&lt;code&gt;docker run&lt;/code&gt;时未添加特权参数&lt;code&gt;--cap-add=SYS_PTRACE --security-opt seccomp=unconfined&lt;/code&gt;导致的&lt;/p&gt;
&lt;p&gt;但问题是,我只需要为这个线上容器(&lt;code&gt;Container&lt;/code&gt;)创建&lt;code&gt;dump&lt;/code&gt;而不是要重新起一个容器&lt;/p&gt;
&lt;p&gt;本文介绍解决方法&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;微软的官方文档里面有提到这种方法:&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/core/diagnostics/diagnostics-in-containers&quot;&gt;Collect diagnostics in Linux containers&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;即为&lt;code&gt;sidecar container&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;就是另起一个带有特权参数的容器,通过命名空间共享(&lt;a href=&quot;https://docs.docker.com/reference/cli/docker/container/run/#pid&quot;&gt;share a process namespace&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;在特权容器中创建&lt;code&gt;dump&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这里给出我的命令&lt;/p&gt;
&lt;p&gt;以下命令皆由AI生成&lt;/p&gt;
&lt;h4&gt;操作步骤&lt;/h4&gt;
&lt;h5&gt;正常使用&lt;code&gt;dotnet-dump&lt;/code&gt;的命令&lt;/h5&gt;
&lt;p&gt;这里先给出正常情况下,特权参数启用&lt;code&gt;testServer&lt;/code&gt;时的命令以做参考&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;###docker run --name test007 --security-opt seccomp=unconfined --rm --cap-add=SYS_PTRACE testServer
###docker run --name test007 -v tmp-shared-volume:/tmp --security-opt seccomp=unconfined --rm --cap-add=SYS_PTRACE testServer

docker exec -it test007 bash
###add proxy
export https_proxy=http://127.0.0.1:7890
export http_proxy=http://127.0.0.1:7890


###install sdk
apt-get update
apt-get install -y wget apt-transport-https software-properties-common

wget https://packages.microsoft.com/config/debian/$(lsb_release -rs)/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
dpkg -i packages-microsoft-prod.deb

apt-get update
apt-get install -y dotnet-sdk-5.0


####install dump tool
dotnet tool install --global dotnet-dump --version 5.0.251802
dotnet tool install --global dotnet-gcdump --version 5.0.251802


export PATH=&quot;$PATH:$HOME/.dotnet/tools&quot;

###should get error: dotnet-dump fails with &quot;Writing dump failed (HRESULT: 0x80004005)&quot; if not   --security-opt seccomp=unconfined --rm --cap-add=SYS_PTRACE
dotnet-dump collect -p 1 -o /tmp/webservice.dmp --type Full

exit
docker cp test007:/tmp/webservice.dmp .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里因为我的服务用的&lt;code&gt;.NET 5.0&lt;/code&gt;因此选用&lt;code&gt;5.0&lt;/code&gt;的版本,假如不知道对应安装的&lt;code&gt;dotnet-dump&lt;/code&gt;版本可查看&lt;a href=&quot;https://www.nuget.org/packages/dotnet-dump#versions-body-tab&quot;&gt;&lt;code&gt;dotnet-dump versions&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;PS:测试时可以使用&lt;code&gt;docker commit test007 test007-2&lt;/code&gt;命令临时将安装的内容保存,方便调试&lt;/p&gt;
&lt;h5&gt;sidecar container &lt;code&gt;dotnet-dump&lt;/code&gt;生成dump&lt;/h5&gt;
&lt;p&gt;正常&lt;code&gt;sidecar&lt;/code&gt;应该是这样的命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run --rm -it --name t07 --pid=container:test007       --cap-add SYS_PTRACE    -v /tmp/dumps:/dumps_out     test007-2    bash -c &quot;dotnet-dump collect -p 1 -o /dumps_out/myapp.dmp --type Full&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里使用安装了&lt;code&gt;dotnet-dump&lt;/code&gt;的临时&lt;code&gt;image&lt;/code&gt;:&lt;code&gt;test007-2&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;生成的&lt;code&gt;dump&lt;/code&gt;文件为&lt;code&gt;Host&lt;/code&gt;的&lt;code&gt;/tmp/dumps/myapp.dmp&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;但实际执行会发现报错:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Process 1 not running compatible .NET runtime.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;微软的官方文档里有提到这个报错:&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/core/diagnostics/diagnostics-in-containers#use-net-cli-tools-in-a-sidecar-container-or-from-the-host&quot;&gt;Use .NET CLI tools in a sidecar container or from the host&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;它的方案是共享&lt;code&gt;/tmp&lt;/code&gt; 路径,这里我没明白什么意思,但是我找到另一种支持的方法&lt;/p&gt;
&lt;h5&gt;sidecar container &lt;code&gt;createdump&lt;/code&gt;生成dump&lt;/h5&gt;
&lt;p&gt;在&lt;code&gt;Microsoft.NETCore.App&lt;/code&gt;中已经自带了&lt;code&gt;createdump&lt;/code&gt;程序,可以直接生成&lt;code&gt;dump&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这个文件位于容器的&lt;code&gt;/usr/share/dotnet/shared/Microsoft.NETCore.App&lt;/code&gt;目录中,根据你安装的&lt;code&gt;runtime&lt;/code&gt;版本有所不同,因此首先我找到本地自带的&lt;code&gt;createdump&lt;/code&gt;版本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run  --rm -it --name t07   mcr.microsoft.com/dotnet/aspnet:5.0    bash -c &quot;ls /usr/share/dotnet/shared/Microsoft.NETCore.App/&quot;
####output 5.0.5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;直接使用&lt;code&gt;createdump&lt;/code&gt;创建&lt;code&gt;dump&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run --rm -it --name t07 --pid=container:test007       --cap-add SYS_PTRACE    -v /tmp/dumps:/dumps_out         mcr.microsoft.com/dotnet/aspnet:5.0         bash -c &quot;/usr/share/dotnet/shared/Microsoft.NETCore.App/5.0.5/createdump --full     --name /dumps_out/myapp.dmp    1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成的&lt;code&gt;dump&lt;/code&gt;文件为&lt;code&gt;Host&lt;/code&gt;的&lt;code&gt;/tmp/dumps/myapp.dmp&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;1为&lt;code&gt;test007&lt;/code&gt;中&lt;code&gt;dotnet&lt;/code&gt;服务的&lt;code&gt;pid&lt;/code&gt;,可以通过命令查询&lt;/p&gt;
&lt;p&gt;&lt;code&gt;5.0.5&lt;/code&gt;为本机&lt;code&gt;mcr.microsoft.com/dotnet/aspnet:5.0&lt;/code&gt;的&lt;code&gt;createdump&lt;/code&gt;版本&lt;/p&gt;
&lt;p&gt;同时,使用&lt;code&gt;aspnet:5.0&lt;/code&gt;的镜像还避免了正式服务多实例数据异常的风险,可以说是一劳多得,一箭双雕&lt;/p&gt;
&lt;h5&gt;传输&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;dump&lt;/code&gt;文件一般很大,所以需要压缩传输&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tar -czvf dump.tar.gz /tmp/dumps/myapp.dmp&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;假如压缩后还是很大,且正式服务器带宽不大,可以看我之前的文章:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/2023-06-16-%E9%98%BF%E9%87%8C%E4%BA%91ECS%E4%B8%8B%E8%BD%BD%E5%8A%A0%E9%80%9F/&quot;&gt;使用docker加速阿里云ECS下载&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://aistudio.google.com/&quot;&gt;Google AI Studio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/core/diagnostics/diagnostics-in-containers&quot;&gt;Collect diagnostics in Linux containers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>connect server by Easy connect</title><link>https://blog.wangshuai.app/2025-05-06-%E6%B7%B1%E4%BF%A1%E6%9C%8D%20%E6%9C%AC%E5%9C%B0%20%E9%80%9A%E8%BF%87%20Easy%20Connect%E5%8F%8A%20ssh%E8%BF%9E%E6%8E%A5%E6%9C%8D%E5%8A%A1%E5%99%A8/</link><guid isPermaLink="true">https://blog.wangshuai.app/2025-05-06-%E6%B7%B1%E4%BF%A1%E6%9C%8D%20%E6%9C%AC%E5%9C%B0%20%E9%80%9A%E8%BF%87%20Easy%20Connect%E5%8F%8A%20ssh%E8%BF%9E%E6%8E%A5%E6%9C%8D%E5%8A%A1%E5%99%A8/</guid><pubDate>Tue, 06 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;公司正式的服务器使用了深信服,本文介绍深信服服务器的连接方法&lt;/p&gt;
&lt;p&gt;连接正式服务器需要的&lt;code&gt;vpn&lt;/code&gt;软件为&lt;a href=&quot;https://support.sangfor.com.cn/productSoftware/list?product_id=20&amp;amp;category_id=56&quot;&gt;&lt;strong&gt;EasyConnect&lt;/strong&gt;&lt;/a&gt;,这个软件第一坑的地方就是官方下载需要登录深信服社区;我直接拒绝&lt;/p&gt;
&lt;p&gt;后面我找到腾讯的下载地址:&lt;a href=&quot;https://pc.qq.com/detail/8/detail_26728.html&quot;&gt;&lt;strong&gt;EasyConnect下载:腾讯软件下载&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;第二个不便的地方是开启&lt;code&gt;VPN&lt;/code&gt;后本机所有的外网都没了,在找&lt;strong&gt;EasyConnect&lt;/strong&gt;的时候,我意外发现了这个开源项目:&lt;strong&gt;&lt;a href=&quot;https://github.com/docker-easyconnect/docker-easyconnect&quot;&gt;docker-easyconnect&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;本文主要使用此项目&lt;/p&gt;
&lt;p&gt;默认的从客户端(A)连接到服务器(B)的方法是直接连接&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/easyconnect_a-b.png&quot; alt=&quot;easy connect a b&quot; /&gt;&lt;/p&gt;
&lt;p&gt;开启&lt;strong&gt;EasyConnect&lt;/strong&gt;后AB组成了一个局域网,导致A也没有网络了&lt;/p&gt;
&lt;p&gt;而**&lt;a href=&quot;https://github.com/docker-easyconnect/docker-easyconnect&quot;&gt;docker-easyconnect&lt;/a&gt;**的作用是添加一个A1和服务器B通讯,而本机A只用和A1通讯,进而可以自由访问外网&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/easyconnect_aa1b.png&quot; alt=&quot;easy connect a-a1-b&quot; /&gt;&lt;/p&gt;
&lt;p&gt;参照网上的攻略:&lt;a href=&quot;https://zhuanlan.zhihu.com/p/389894063&quot;&gt;用docker封印EasyConnect并连接远程桌面和数据库 - Jortana的文章 - 知乎&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;可以在本机的docker实现这样的功能,攻略中的A1就是本机docker环境,也可以是wsl&lt;/p&gt;
&lt;p&gt;但是我的PC没有docker,于是将A1独立出来,放到另外的测试服务器上&lt;/p&gt;
&lt;p&gt;实际上A1的作用就是堡垒机,本文也是创建一个堡垒机.&lt;/p&gt;
&lt;p&gt;配置完成后实现的功能:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本机可以百度搜索的同时连接深信服服务器&lt;/li&gt;
&lt;li&gt;局域网多个客户端(终端)可以同时连接到服务器&lt;/li&gt;
&lt;li&gt;可以直接使用&lt;code&gt;ssh&lt;/code&gt;连接正式服务器&lt;/li&gt;
&lt;li&gt;可以直接连接到正式服务器上的任意本地服务比如数据库,因此就有直接debug正式服务的能力&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;h4&gt;首先,参照官方方法安装安装&lt;a href=&quot;https://github.com/docker-easyconnect/docker-easyconnect&quot;&gt;docker-easyconnect&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;这里给出我的命令:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#192.168.1.101 为测试服务器Ip 即为A1
docker run --rm --name easyconnect --device /dev/net/tun --cap-add NET_ADMIN -ti -p 192.168.1.101:1080:1080 -p 127.0.0.1:8888:8888 -e EC_VER=7.6.7 -e CLI_OPTS=&apos;-d https://11.22.33.44:123 -u ssss -p xxxx&apos; hagb/docker-easyconnect:cli
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是&lt;code&gt;cli&lt;/code&gt;的连接方法&lt;/p&gt;
&lt;p&gt;需要注意的是,知乎教程中用的是&lt;code&gt;docker desktop&lt;/code&gt;,因此&lt;code&gt;socket&lt;/code&gt;服务IP填的&lt;code&gt;127.0.0.1&lt;/code&gt;,而我这里需要从外部访问测试服务器&lt;code&gt;A1&lt;/code&gt;,因此&lt;code&gt;1080&lt;/code&gt;端口的IP需调整为运行docker服务的地址,即&lt;code&gt;192.168.1.101&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;假如一切正常,能看到&lt;code&gt;user &quot;ssss&quot; login successfully!&lt;/code&gt;登录成功的提示&lt;/p&gt;
&lt;hr /&gt;
&lt;h5&gt;vnc登录&lt;/h5&gt;
&lt;p&gt;假如&lt;code&gt;cli&lt;/code&gt;登录报错,可以使用&lt;code&gt;vnc&lt;/code&gt;远程登录 官方文档有参数详细说明&lt;/p&gt;
&lt;p&gt;我就遇到&lt;code&gt;cli&lt;/code&gt;登录报错的问题:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/local/bin/start.sh: line 152: tigervncpasswd: command not found
WARNING: logging deactivated (can&apos;t log to stdout when daemonized)
/usr/local/bin/start.sh: line 158: tigervncserver: command not found
/usr/local/bin/start.sh: line 170: xclip: command not found
auto login is disabled
login failed!
svpn stop!
Terminated
auto login is disabled
login failed!
svpn stop!
Terminated
auto login is disabled
login failed!
svpn stop!
Terminated
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;经后续测试,这里原因为密码过期,后台强制换密码导致无法登录&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/easy_connect_change_password.png&quot; alt=&quot;easy_connect_change_password&quot; /&gt;&lt;/p&gt;
&lt;p&gt;因此,使用&lt;code&gt;VNC&lt;/code&gt;方法登录换过密码后就能用&lt;code&gt;cli&lt;/code&gt;登录了&lt;/p&gt;
&lt;p&gt;这里给出我的&lt;code&gt;vnc&lt;/code&gt;命令:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run --rm --device /dev/net/tun --cap-add NET_ADMIN -ti -e PASSWORD=password -e DISABLE_PKG_VERSION_XML=1 -e URLWIN=1 -v $HOME/.ecdata:/root -p 192.168.1.101:5901:5901 -p 192.168.1.101:1080:1080 -p 127.0.0.1:8888:8888 hagb/docker-easyconnect:7.6.7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中&lt;code&gt;password&lt;/code&gt;是&lt;code&gt;vnc&lt;/code&gt;登录密码&lt;/p&gt;
&lt;p&gt;arm64 和 mips64el 架构需要加入 &lt;code&gt;-e DISABLE_PKG_VERSION_XML=1&lt;/code&gt; 参数&lt;/p&gt;
&lt;p&gt;根据我这里的参数,使用&lt;code&gt;vnc&lt;/code&gt;连接&lt;code&gt;192.168.1.101:5901&lt;/code&gt;即可&lt;/p&gt;
&lt;h4&gt;配置&lt;code&gt;Clash&lt;/code&gt;服务&lt;/h4&gt;
&lt;p&gt;还是可以参照知乎教程中&lt;code&gt;Clash&lt;/code&gt;的配置方法,将IP改为&lt;code&gt;192.168.1.101&lt;/code&gt;即可&lt;/p&gt;
&lt;p&gt;我这里因为用到了订阅,因此使用&lt;a href=&quot;https://docs.gtk.pw/contents/parser.html&quot;&gt;&lt;code&gt;Parsers&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Parsers:
      prepend-rules:
        - IP-CIDR,192.168.0.11/32,深信服
      append-proxies:
        - {name: easyConnect, server: 192.168.1.101, port: 1080, type: socks5}
      append-proxy-groups:
        - name: 深信服
          type: select
          proxies:
            - easyConnect
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中的&lt;code&gt;prepend-rules&lt;/code&gt;我设置了固定的IP&lt;code&gt;192.168.0.11&lt;/code&gt;,此为服务器IP地址;因为本地网段和服务器的网段会有冲突,因此手写了规则,如果网段不同则不需要&lt;/p&gt;
&lt;p&gt;配置好后&lt;code&gt;Update&lt;/code&gt;订阅即可看到&lt;code&gt;Proxies&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/clash_easyconnect.png&quot; alt=&quot;easy connect clash proxy&quot; /&gt;&lt;/p&gt;
&lt;p&gt;点击&lt;code&gt;Check&lt;/code&gt;会发现&lt;code&gt;Timeout&lt;/code&gt;,实际不影响&lt;/p&gt;
&lt;p&gt;假如&lt;code&gt;Clash&lt;/code&gt;的配置遇到问题可以看前文&lt;a href=&quot;/2023-01-14-%E4%BD%BF%E7%94%A8clash%E4%BB%A3%E7%90%86SSR%E7%9A%84%E5%86%85%E7%BD%91%E8%AF%B7%E6%B1%82/&quot;&gt;使用clash代理SSR的内网请求&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;配置&lt;code&gt;Xshell&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;ssh&lt;/code&gt;的连接及正式服务器的服务连接使用&lt;code&gt;xshell&lt;/code&gt;的功能&lt;/p&gt;
&lt;h5&gt;ssh连接服务器&lt;/h5&gt;
&lt;p&gt;假如&lt;code&gt;xshell&lt;/code&gt;直连正式的服务器没问题可以跳过;我这里因为网段冲突及服务器本身是通过&lt;code&gt;Clash&lt;/code&gt;连接的,因此需要额外配置&lt;/p&gt;
&lt;p&gt;打开配置好的&lt;code&gt;xshell&lt;/code&gt;的&lt;code&gt;Session&lt;/code&gt;配置,右键&lt;code&gt;Properties(属性)&lt;/code&gt;→&lt;code&gt;Proxy(代理)&lt;/code&gt;→&lt;code&gt;Browse(浏览)&lt;/code&gt;新增一个&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/xshell_add_proxy.png&quot; alt=&quot;add proxy&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Type(类型)&lt;/code&gt;选&lt;code&gt;Sockes5&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Host&lt;/code&gt;为&lt;a href=&quot;https://github.com/docker-easyconnect/docker-easyconnect&quot;&gt;docker-easyconnect&lt;/a&gt;服务(A1)的地址&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ssh&lt;/code&gt;连接成功后可以在&lt;code&gt;SSH Channel List(隧道窗格)&lt;/code&gt;中看到连接状态&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/xshell_ssh_channel_list.png&quot; alt=&quot;ssh channel list&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SSH Channel List&lt;/code&gt;可以通过在底部快速命令处右键打开&lt;/p&gt;
&lt;h5&gt;使用ssh tunnel创建 Outgoing转移规则&lt;/h5&gt;
&lt;p&gt;转移规则介绍可查看:&lt;a href=&quot;https://www.xshellcn.com/zhishi/local-zhuanyi.html&quot;&gt;Xshell本地转移规则讲解&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;此设置的目的是将服务器的端口映射到本地,比如将服务器B上面&lt;code&gt;5432&lt;/code&gt;端口的&lt;code&gt;pgsql&lt;/code&gt;服务映射到本地&lt;code&gt;5234&lt;/code&gt;,这样可以直接用&lt;code&gt;DataGrip&lt;/code&gt;等数据库工具设置&lt;code&gt;localhost:5234&lt;/code&gt;直连正式数据库&lt;/p&gt;
&lt;p&gt;将上面的创建的&lt;code&gt;SSH&lt;/code&gt; &lt;code&gt;Session&lt;/code&gt;复制并打开&lt;code&gt;Properties(属性)&lt;/code&gt;→&lt;code&gt;SSH&lt;/code&gt;→&lt;code&gt;Tunneling(隧道)&lt;/code&gt;新增一个&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/xshell_ssh_tunnel_pgsql.png&quot; alt=&quot;xshell ssh tunnel&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Type(类型)&lt;/code&gt;选择&lt;code&gt;Local(Outgoing)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Sourse Host(源主机)&lt;/code&gt;填&lt;code&gt;localhost&lt;/code&gt; 端口 &lt;code&gt;5234&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Destination Host(目标主机)&lt;/code&gt;填正式上需要访问的服务IP和端口&lt;/p&gt;
&lt;p&gt;创建完开启&lt;code&gt;Session&lt;/code&gt;连上服务器后可以看到&lt;code&gt;Forwarding Rules(转移规则)&lt;/code&gt;的状态为&lt;code&gt;Open&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/xshell_ssh_tunnel_forwarding_rules.png&quot; alt=&quot;xshell forwarding rules&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SSH Channel List&lt;/code&gt;可以通过在底部快速命令处右键打开&lt;/p&gt;
&lt;h5&gt;vnc登录&lt;/h5&gt;
&lt;p&gt;要代理&lt;code&gt;VNC&lt;/code&gt;连接,只需参照&lt;code&gt;ssh&lt;/code&gt;规则重新创建一个&lt;code&gt;192.168.1.101:5901&lt;/code&gt;的转发规则即可&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/xshell_vnc_tunnel.png&quot; alt=&quot;xshell vnc forwarding&quot; /&gt;&lt;/p&gt;
&lt;p&gt;此规则会将&lt;code&gt;easyconnect&lt;/code&gt;服务器(&lt;code&gt;192.168.1.101&lt;/code&gt;)上的&lt;code&gt;5901&lt;/code&gt;端口映射到本地&lt;code&gt;5901&lt;/code&gt;端口&lt;/p&gt;
&lt;p&gt;此时使用&lt;code&gt;vnc&lt;/code&gt;客户端连接&lt;code&gt;localhost:5901&lt;/code&gt;密码&lt;code&gt;password&lt;/code&gt;即可&lt;/p&gt;
&lt;p&gt;非&lt;code&gt;127.0.0.1&lt;/code&gt;连接正式环境毕竟还是比较危险,所以我只在每次使用的时候开启&lt;a href=&quot;https://github.com/docker-easyconnect/docker-easyconnect&quot;&gt;docker-easyconnect&lt;/a&gt;服务,使用完毕立即停止&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/docker-easyconnect/docker-easyconnect&quot;&gt;docker-easyconnect&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/389894063&quot;&gt;用docker封印EasyConnect并连接远程桌面和数据库 - Jortana的文章 - 知乎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.aliyun.com/article/1035160&quot;&gt;ssh端口转发(跳板机)详解&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.xshellcn.com/zhishi/local-zhuanyi.html&quot;&gt;Xshell本地转移规则讲解&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://johnliu55.tw/ssh-tunnel.html&quot;&gt;SSH Tunneling (Port Forwarding) 详解&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>homeassistant connect to iotlink with mqtt</title><link>https://blog.wangshuai.app/2025-03-12-homeassistant%E8%BF%9E%E6%8E%A5iotlink/</link><guid isPermaLink="true">https://blog.wangshuai.app/2025-03-12-homeassistant%E8%BF%9E%E6%8E%A5iotlink/</guid><pubDate>Wed, 12 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近趁着国补买了一个屏幕挂灯,这个屏幕挂灯可以在米家中控制;于是寻思可否在&lt;code&gt;HA&lt;/code&gt;中在我电脑开机时自动打开;于是在网上找到了相关工具&lt;a href=&quot;https://iotlink.gitlab.io/index.html&quot;&gt;IOTLink&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这个工具可以将windows的相关事件通过&lt;code&gt;MQTT&lt;/code&gt;发送出去只要,&lt;code&gt;Home Assistant&lt;/code&gt;正确监听即可完成相关自动化&lt;/p&gt;
&lt;p&gt;但官方的&lt;code&gt;yml&lt;/code&gt;配置过于陈旧无法使用,本文主要解决以下两个报错:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;获取“Mosquitto Mqtt Broker”插件的信息失败。&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;不可能将 platform: mqtt 添加到”binary_sensor“配置来配置”mqtt binary_sensor“。查看文档可获取有关如何设置此集成的更多信息。

要解决这个问题：

从 YAML 配置文件的”binary_sensor:“配置中移除 platform: mqtt。
重新启动 Home Assistant 。
应当移除以下示例：

binary_sensor:
 - platform: mqtt
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;首先声明我的环境 ,见前文:&lt;a href=&quot;/2025-01-08-homeassistant%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8AHACS%E7%9A%84%E5%AE%89%E8%A3%85/&quot;&gt;homeassistant初始化及HACS的安装&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我是全新安装的新版&lt;code&gt;2025.3.1&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;获取“Mosquitto Mqtt Broker”插件的信息失败&lt;/h4&gt;
&lt;p&gt;使用&lt;code&gt;MQTT&lt;/code&gt;需要一个&lt;code&gt;Broker&lt;/code&gt;也就是服务器来中转各个&lt;code&gt;client&lt;/code&gt;的消息;&lt;a href=&quot;https://www.home-assistant.io/integrations/mqtt/&quot;&gt;官方&lt;/a&gt;是推荐的从&lt;code&gt;设置&lt;/code&gt;→&lt;code&gt;设备与服务&lt;/code&gt;→&lt;code&gt;添加集成&lt;/code&gt;中搜索添加&lt;/p&gt;
&lt;p&gt;&lt;s&gt;但我不知道哪里有问题使用自带的&lt;code&gt;MQTT&lt;/code&gt;一直报错&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;现在我知道了,我没有安装官方的&lt;code&gt;Mosquitto broker&lt;/code&gt;加载项,而&lt;strong&gt;这个加载项需要docker拉取镜像,必须路由器开启全局代理解决&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我没有解决这个问题,而是在&lt;code&gt;设置&lt;/code&gt;→&lt;code&gt;加载项&lt;/code&gt;→&lt;code&gt;加载项商店&lt;/code&gt;中安装了另一个支持的&lt;code&gt;MQTT&lt;/code&gt; &lt;code&gt;Broker&lt;/code&gt; &lt;a href=&quot;https://www.emqx.com/zh&quot;&gt;EMQX&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;安装后配置登录完成后同样打开&lt;code&gt;MQTT&lt;/code&gt;集成,使用自定义配置添加&lt;code&gt;EMQX&lt;/code&gt;中的&lt;code&gt;Broker&lt;/code&gt;配置&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ha_mqtt.png&quot; alt=&quot;mqtt_config&quot; /&gt;&lt;/p&gt;
&lt;p&gt;连接完成后即可在&lt;code&gt;EMQX&lt;/code&gt;中看到客户端在线,但此时打开&lt;code&gt;MQTT&lt;/code&gt;会发现没有任何实体,此时需要我们通过&lt;code&gt;/homeassistant/configuration.yaml&lt;/code&gt;配置添加我们的&lt;code&gt;IOTLink&lt;/code&gt;传感器(&lt;code&gt;sensor&lt;/code&gt;)&lt;/p&gt;
&lt;h4&gt;不可能将 platform: mqtt 添加到”binary_sensor“配置来配置”mqtt binary_sensor“&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;IOTLink&lt;/code&gt;&lt;a href=&quot;https://gitlab.com/iotlink/iotlink/-/wikis/Addons/Windows-Monitor&quot;&gt;官方&lt;/a&gt;表示PC在线状态的&lt;code&gt;yml&lt;/code&gt;如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;binary_sensor:
  - platform: mqtt
    name: &quot;My Computer - Connected&quot;
    state_topic: &quot;iotlink/workgroup/my-computer/lwt&quot;
    value_template: &quot;{{ value }}&quot;
    payload_on: &quot;ON&quot;
    payload_off: &quot;OFF&quot;
    device_class: &quot;connectivity&quot;
    qos: 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将此传感器配置添加到&lt;code&gt;HA&lt;/code&gt;中即可看到上文的错误,通过查看&lt;a href=&quot;https://www.home-assistant.io/integrations/sensor.mqtt/&quot;&gt;MQTT Sensor&lt;/a&gt;文档我们知道此处正确的格式为:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mqtt:
  sensor:
    - name: &quot;My Computer - Idle Time&quot;
      state_topic: &quot;iotlink/workgroup/my-computer/windows-monitor/stats/system/idle-time&quot;
      unit_of_measurement: &apos;seconds&apos;
      icon: mdi:timer
      value_template: &quot;{{ value }}&quot;
      availability_topic: &quot;iotlink/workgroup/my-computer/lwt&quot;
      payload_available: &quot;ON&quot;
      payload_not_available: &quot;OFF&quot;

  binary_sensor:
    - name: &quot;My Computer - Connected&quot;
      state_topic: &quot;iotlink/workgroup/my-computer/lwt&quot;
      value_template: &quot;{{ value }}&quot;
      payload_on: &quot;ON&quot;
      payload_off: &quot;OFF&quot;
      device_class: &quot;connectivity&quot;
      qos: 1
      unique_id: &quot;homepc_007_on&quot;
      device:
        name: &quot;my-computer&quot;
        identifiers:
          - &quot;homepc_007&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调整后应用配置即可在&lt;code&gt;HA&lt;/code&gt;中看到传感器&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ha_iotlink_state.png&quot; alt=&quot;iotlink_state&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里顺便说明一下&lt;code&gt;IOTLink&lt;/code&gt;的用法:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先从官网上安装最新版&lt;/li&gt;
&lt;li&gt;安装完成后可在windows中查看到快捷菜单&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/iotlink_fold.png&quot; alt=&quot;iotlink_short&quot; /&gt;&lt;/p&gt;
&lt;p&gt;先启动服务再打开配置文件&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;修改配置为&lt;code&gt;Broker&lt;/code&gt;的配置,一般配置&lt;code&gt;hostname&lt;/code&gt; &lt;code&gt;port&lt;/code&gt; &lt;code&gt;username&lt;/code&gt; 和&lt;code&gt;password&lt;/code&gt;即可&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/iotlink_config.png&quot; alt=&quot;iot_link&quot; /&gt;&lt;/p&gt;
&lt;p&gt;配置完成后会自动重载,将上面修改好的&lt;code&gt;yml&lt;/code&gt;配置到&lt;code&gt;HA&lt;/code&gt;后即可正常使用了&lt;/p&gt;
&lt;p&gt;假如你不知道你&lt;code&gt;MQTT&lt;/code&gt;的&lt;code&gt;Topic&lt;/code&gt;打开&lt;code&gt;IOTLink&lt;/code&gt;的日志记录&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/iotlink_servicelog.png&quot; alt=&quot;topic_location&quot; /&gt;&lt;/p&gt;
&lt;p&gt;假如总是连接不上可以使用&lt;code&gt;POSTMan&lt;/code&gt;或者其它的&lt;code&gt;MQTT Explorer&lt;/code&gt;检查&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://community.home-assistant.io/t/way-to-detect-if-pc-is-powered-on/243301&quot;&gt;Way to detect if PC is powered on&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;https://iotlink.gitlab.io/index.html&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>homeassistant init in pve and install hacs</title><link>https://blog.wangshuai.app/2025-01-08-homeassistant%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8Ahacs%E7%9A%84%E5%AE%89%E8%A3%85/</link><guid isPermaLink="true">https://blog.wangshuai.app/2025-01-08-homeassistant%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8Ahacs%E7%9A%84%E5%AE%89%E8%A3%85/</guid><pubDate>Wed, 08 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;随着小米开源了它的&lt;code&gt;Home Assistant&lt;/code&gt;仓库:&lt;a href=&quot;https://github.com/XiaoMi/ha_xiaomi_home&quot;&gt;ha_xiaomi_home&lt;/a&gt; 最近有了捣鼓HA的想法&lt;/p&gt;
&lt;p&gt;我的安装方式为PVE 虚拟机安装，基础的安装方式参照&lt;a href=&quot;https://post.smzdm.com/p/a830wxml/&quot;&gt;【手把手教你】Proxmox VE（PVE）安装HomeAssistant&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这里补充一个教程遗漏的地方,虚拟机创建完成后启动前;要先在&lt;code&gt;选项&lt;/code&gt;→&lt;code&gt;引导顺序&lt;/code&gt;中调整,把&lt;strong&gt;新加的ISO磁盘设置为首个启动&lt;/strong&gt;,安装完成后再将&lt;code&gt;ISO&lt;/code&gt;磁盘移除&lt;/p&gt;
&lt;p&gt;本文为&lt;code&gt;HA&lt;/code&gt;的新手教程&lt;/p&gt;
&lt;p&gt;下面列出我遇到的几个问题及对应处理办法&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;h4&gt;安装耗时过长问题&lt;/h4&gt;
&lt;p&gt;PVE的安装方法是,安装一个linux系统,然后再在里面使用docker安装所需镜像,这一点可以在安装&lt;code&gt;Advanced SSH &amp;amp; Web Terminal&lt;/code&gt;加载项并关闭&lt;code&gt;保护模式&lt;/code&gt;后得到印证&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ha_docker.png&quot; alt=&quot;ha_docker&quot; /&gt;&lt;/p&gt;
&lt;p&gt;它的初始化就是拉取&lt;code&gt;ghcr.io&lt;/code&gt;中的镜像,国内想要加速比较好的办法就是内网网关开启全局代理,比如我就是打开&lt;code&gt;OpenWrt&lt;/code&gt;的&lt;code&gt;OpenClah&lt;/code&gt;然后重启虚拟机,重启后两分钟就初始化好了.&lt;/p&gt;
&lt;h4&gt;安装后什么菜单都没有&lt;/h4&gt;
&lt;p&gt;默认官方初始化后的菜单仅有&lt;code&gt;概览&lt;/code&gt; &lt;code&gt;地图&lt;/code&gt; &lt;code&gt;能源&lt;/code&gt; &lt;code&gt;日志&lt;/code&gt; &lt;code&gt;历史&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ha_ex_menu.png&quot; alt=&quot;ha_ex_menu&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最重要的&lt;code&gt;File editor&lt;/code&gt; &lt;code&gt;SSH&lt;/code&gt; &lt;code&gt;HACS&lt;/code&gt;一个都没有这里给出操作步骤.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一步应该开启&lt;a href=&quot;https://www.home-assistant.io/blog/2019/07/17/release-96/#advanced-mode&quot;&gt;&lt;code&gt;高级模式&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;点击头像,访问个人中心(http://homeassistant.local:8123/profile/general)  ,在常规的用户设置中开启&lt;code&gt;高级模式&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ha_adv_mode.png&quot; alt=&quot;ha_adv&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;安装官方基础加载项&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;打开 &lt;code&gt;设置&lt;/code&gt; → &lt;code&gt;加载项&lt;/code&gt; → &lt;a href=&quot;http://homeassistant.local:8123/hassio/store&quot;&gt;&lt;code&gt;加载项商店&lt;/code&gt;&lt;/a&gt;安装&lt;code&gt;Advanced SSH &amp;amp; Web Terminal&lt;/code&gt; &lt;code&gt;File editor&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ha_plugin_store.png&quot; alt=&quot;ha_plugin_store&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里假如商店里面一个加载项也没有,应该是网络问题,开启全局代理后就能看到了;&lt;/p&gt;
&lt;p&gt;安装基础加载项后可以开启&lt;code&gt;SSH&lt;/code&gt;进而有更多操作空间,不过本教程不需要用&lt;/p&gt;
&lt;h4&gt;安装HACS&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;HACS&lt;/code&gt;就是一个从&lt;code&gt;Github&lt;/code&gt;安装插件的工具&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ha_hacs.png&quot; alt=&quot;ha_hacs&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HACS&lt;/code&gt;有官方安装教程:&lt;a href=&quot;https://www.hacs.xyz/docs/use/download/download/#to-download-hacs&quot;&gt;To download HACS&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;简单来说,就是安装&lt;a href=&quot;https://my.home-assistant.io/redirect/supervisor_addon/?addon=cb646a50_get&amp;amp;repository_url=https%3A%2F%2Fgithub.com%2Fhacs%2Faddons&quot;&gt;&lt;code&gt;Get HACS&lt;/code&gt;&lt;/a&gt;的插件,用这个插件来安装&lt;code&gt;HACS&lt;/code&gt;,安装插件后点击&lt;code&gt;启动&lt;/code&gt;,在&lt;a href=&quot;http://homeassistant.local:8123/hassio/addon/cb646a50_get/info&quot;&gt;&lt;code&gt;日志&lt;/code&gt;&lt;/a&gt;中可以看到详情;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ha_get_hacs.png&quot; alt=&quot;ha_get_hacs&quot; /&gt;&lt;/p&gt;
&lt;p&gt;安装完成后按快捷键&lt;code&gt;C&lt;/code&gt;打开&lt;a href=&quot;https://www.home-assistant.io/docs/tools/quick-bar/&quot;&gt;快速工具栏&lt;/a&gt;重启服务器&lt;/p&gt;
&lt;p&gt;然后在&lt;code&gt;设置&lt;/code&gt; →&lt;code&gt;设备与服务&lt;/code&gt;→&lt;a href=&quot;https://my.home-assistant.io/redirect/integrations/&quot;&gt;集成&lt;/a&gt;→&lt;code&gt;添加集成&lt;/code&gt;里面 搜索添加&lt;code&gt;HACS&lt;/code&gt;即可&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ha_hacs_intergration.png&quot; alt=&quot;ha_hacs_intergration&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;配置HACS代理(&lt;code&gt;proxy&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;HACS&lt;/code&gt;需要&lt;code&gt;Github&lt;/code&gt;的网络,一直全局开着&lt;code&gt;OpenClash&lt;/code&gt;也不是事,由于我内网安装了&lt;code&gt;Clash&lt;/code&gt;代理,因此调整&lt;code&gt;HACS&lt;/code&gt;的&lt;code&gt;proxy&lt;/code&gt;代理设置.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;HACS&lt;/code&gt;代理使用&lt;a href=&quot;https://github.com/ryanh7/hacs-proxy&quot;&gt;HACS Proxy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;打开&lt;code&gt;HACS&lt;/code&gt;&lt;a href=&quot;http://homeassistant.local:8123/hacs/dashboard&quot;&gt;菜单&lt;/a&gt;→&lt;code&gt;点击右上角&lt;/code&gt;→&lt;code&gt;Custom repositories&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Repository&lt;/code&gt;填写&lt;code&gt;Github&lt;/code&gt;地址:https://github.com/ryanh7/hacs-proxy&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Type&lt;/code&gt;选择&lt;code&gt;Integration&lt;/code&gt;(集成)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ha_hacs_proxy.png&quot; alt=&quot;ha_hacs_proxy&quot; /&gt;&lt;/p&gt;
&lt;p&gt;安装完成后参照&lt;code&gt;HACS&lt;/code&gt;的集成方法,在&lt;/p&gt;
&lt;p&gt;然后在&lt;code&gt;设置&lt;/code&gt; →&lt;code&gt;设备与服务&lt;/code&gt;→&lt;a href=&quot;https://my.home-assistant.io/redirect/integrations/&quot;&gt;集成&lt;/a&gt;→&lt;code&gt;添加集成&lt;/code&gt;里面 搜索添加&lt;code&gt;HACS Proxy&lt;/code&gt;即可&lt;/p&gt;
&lt;p&gt;代理地址填写内网&lt;code&gt;Socket&lt;/code&gt;代理地址,如&lt;code&gt;http://127.0.0.1:7890&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;PS:这里不能使用内网域名,只能使用IP;原因可见&lt;a href=&quot;https://github.com/ryanh7/hacs-proxy/issues/4#issuecomment-2570319637&quot;&gt;Issue:hacs 最新版无效&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;安装完成后即可将全局&lt;code&gt;OpenClash&lt;/code&gt;代理关闭;为了内网安全访问,我还安装了&lt;code&gt;Tailscale&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/%E5%BD%BC%E5%BE%97%E6%BD%98%E7%9A%84-swift-ios-app-%E9%96%8B%E7%99%BC%E6%95%99%E5%AE%A4/%E5%AD%B8%E7%BF%92%E6%97%A5%E8%AA%8C-%E5%8D%81%E5%85%AB-home-assistant%E5%AE%89%E8%A3%9Dhacs-9c9ff0b2ea27&quot;&gt;学习日志十八：Home Assistant安装HACS商店&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;https://www.hacs.xyz/docs/use/&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>homeassistant自定义微信消息通知</title><link>https://blog.wangshuai.app/2025-01-02-homeassistant%E8%87%AA%E5%AE%9A%E4%B9%89%E5%BE%AE%E4%BF%A1%E6%B6%88%E6%81%AF%E9%80%9A%E7%9F%A5/</link><guid isPermaLink="true">https://blog.wangshuai.app/2025-01-02-homeassistant%E8%87%AA%E5%AE%9A%E4%B9%89%E5%BE%AE%E4%BF%A1%E6%B6%88%E6%81%AF%E9%80%9A%E7%9F%A5/</guid><pubDate>Thu, 02 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;随着小米开源了它的&lt;code&gt;Home Assistant&lt;/code&gt;仓库:&lt;a href=&quot;https://github.com/XiaoMi/ha_xiaomi_home&quot;&gt;ha_xiaomi_home&lt;/a&gt; 最近有了捣鼓HA的想法&lt;/p&gt;
&lt;p&gt;安装完成后(安装方法可见:&lt;a href=&quot;/2025-01-08-homeassistant%E5%88%9D%E5%A7%8B%E5%8C%96%E5%8F%8AHACS%E7%9A%84%E5%AE%89%E8%A3%85/&quot;&gt;homeassistant初始化及HACS的安装&lt;/a&gt;)首先想到的是给自己的手机发送一个通知.&lt;/p&gt;
&lt;p&gt;搜索后发现这个插件:&lt;a href=&quot;https://github.com/nesror/ha-wework_notify&quot;&gt;ha-wework_notify&lt;/a&gt;,但是企业微信现在发送通知需要可信固定IP,于是想到折衷的企业微信机器人的办法:&lt;a href=&quot;https://developer.work.weixin.qq.com/document/path/91770&quot;&gt;企业微信群机器人配置说明&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;而HA原生支持&lt;a href=&quot;https://www.home-assistant.io/integrations/notify.rest/&quot;&gt;rest&lt;/a&gt;请求,但是官方没有给出足够的&lt;code&gt;sample&lt;/code&gt;,这里给出我捣鼓两天总结的&lt;code&gt;POST&lt;/code&gt;带参数的请求方法&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;直接给出&lt;code&gt;POST&lt;/code&gt;请求&lt;code&gt;configuration.yaml&lt;/code&gt;配置:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;notify:
  - platform: rest
    name: wepush
    resource: &quot;https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxx&quot;
    method: POST_JSON
    data:
      msgtype: &quot;text&quot;
      text:
        content: &quot;{{message}}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改完成后重启HA服务(首次添加时需完整重启服务器而非&lt;code&gt;yaml&lt;/code&gt;配置),在&lt;a href=&quot;http://homeassistant.local:8123/developer-tools/action&quot;&gt;&lt;code&gt;开发者工具→动作&lt;/code&gt;&lt;/a&gt;中选中&lt;code&gt;通知: Send a notification with wepush&lt;/code&gt;输入&lt;code&gt;message&lt;/code&gt;即可测试&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ha_test_notify.png&quot; alt=&quot;test notify&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里给出我的 图示说明&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ha_rest_desc.png&quot; alt=&quot;rest description&quot; /&gt;&lt;/p&gt;
&lt;p&gt;意思是HA会自动将&lt;code&gt;data&lt;/code&gt;数据传递给下一个&lt;code&gt;action&lt;/code&gt;,且这个对象会在&lt;code&gt;method: POST_JSON&lt;/code&gt;时自动转为json ,通过模板&lt;code&gt;{{varname}}&lt;/code&gt;可以取到上一个&lt;code&gt;action&lt;/code&gt;传过来的参数&lt;/p&gt;
</content:encoded></item><item><title>fzf多级文件选择查看日志</title><link>https://blog.wangshuai.app/2024-12-24-fzf%E5%A4%9A%E7%BA%A7%E6%96%87%E4%BB%B6%E9%80%89%E6%8B%A9%E6%9F%A5%E7%9C%8B%E6%97%A5%E5%BF%97/</link><guid isPermaLink="true">https://blog.wangshuai.app/2024-12-24-fzf%E5%A4%9A%E7%BA%A7%E6%96%87%E4%BB%B6%E9%80%89%E6%8B%A9%E6%9F%A5%E7%9C%8B%E6%97%A5%E5%BF%97/</guid><pubDate>Tue, 24 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;公司的服务部署在linux上,路径大概是这样&lt;/p&gt;
&lt;p&gt;logs
└── XXX
├── Error
│   ├── 2024-12-21
│   ├── 2024-12-22
│   └── 2024-12-23
├── Info
│   ├── 2024-12-21
│   ├── 2024-12-22
│   └── 2024-12-23
└── Debug
├── 2024-12-21
├── 2024-12-22
└── 2024-12-23&lt;/p&gt;
&lt;p&gt;每次线上出问题查看日志都要打一大堆的&lt;code&gt;cd&lt;/code&gt;和&lt;code&gt;ls&lt;/code&gt;命令,很是麻烦;&lt;/p&gt;
&lt;p&gt;于是询问&lt;code&gt;ChatGPT&lt;/code&gt;,结果它告诉我有&lt;a href=&quot;https://github.com/junegunn/fzf/&quot;&gt;fzf&lt;/a&gt;这种神器&lt;/p&gt;
&lt;p&gt;本文说明合理运用&lt;code&gt;fzf&lt;/code&gt;多次选择XXX Error路径并最终查看日志文件的命令&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;完整版
直接给出我的一行命令&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;find /logs -type d -mindepth 1 -maxdepth 1 | fzf --prompt=&quot;Select a project: &quot; |  xargs -I{} find {} -type d -mindepth 1 -maxdepth 1 | fzf --sync --prompt=&quot;Select log level: &quot; | xargs -I{} find {} -printf &quot;%T@ %p\n&quot; | sort -n -r | cut -d&apos; &apos; -f 2- |  fzf --sync --prompt=&quot;Select a log file: &quot; --no-clear --preview &apos;cat {}&apos;  |  xargs -I{} less {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里引用ChatGPT的解析:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;1.选择项目目录：&lt;/p&gt;
&lt;p&gt;find /logs -type d -mindepth 1 -maxdepth 1 | fzf --prompt=&quot;Select a project: &quot;&lt;/p&gt;
&lt;p&gt;查找 /logs 目录下的一级子目录。&lt;/p&gt;
&lt;p&gt;使用 fzf 提供交互式选择。&lt;/p&gt;
&lt;p&gt;2.选择日志级别子目录：&lt;/p&gt;
&lt;p&gt;xargs -I{} find {} -type d -mindepth 1 -maxdepth 1 | fzf --sync --prompt=&quot;Select log level: &quot;&lt;/p&gt;
&lt;p&gt;根据选定的项目目录，查找其一级子目录。
再次使用 fzf 提供交互式选择。&lt;/p&gt;
&lt;p&gt;3.按修改时间排序日志文件：&lt;/p&gt;
&lt;p&gt;xargs -I{} find {} -printf &quot;%T@ %p\n&quot; | sort -n -r | cut -d&apos; &apos; -f 2-
查找选定日志级别目录中的所有文件。
使用 -printf &quot;%T@ %p\n&quot; 输出修改时间和路径。
按时间戳倒序排序，并提取文件路径。
4.选择日志文件并预览内容：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;关键点:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--sync&lt;/code&gt;参数支持多次选择,具体请看&lt;a href=&quot;https://github.com/junegunn/fzf/issues/2294&quot;&gt;参考资料&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;xargs&lt;/code&gt;支持参数传递&lt;/p&gt;
&lt;p&gt;把这一行命令直接存入&lt;code&gt;Xshell&lt;/code&gt;的快速命令栏,即可迅速查看到服务的对应日志&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;简易版&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;假如你的日志文件不是太多,也可使用简易版本&lt;/p&gt;
&lt;p&gt;&lt;code&gt;find /logs -type f | sort | fzf --preview &apos;cat {}&apos; | xargs -I {} less {}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;一次搜索预览并 使用&lt;code&gt;less&lt;/code&gt;检索日志文件&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;其它说明-安装方法&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/junegunn/fzf/&quot;&gt;fzf&lt;/a&gt;的安装可以参考官方文档&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我本机直接 &lt;code&gt;choco&lt;/code&gt;安装&lt;/li&gt;
&lt;li&gt;家里的测试服务器不支持&lt;code&gt;yum&lt;/code&gt;直接安装,因此我选择的是github 源码安装;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;公司的环境没有&lt;code&gt;git&lt;/code&gt;不能直接安装;最后我选择的是二进制文件安装&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从&lt;a href=&quot;https://github.com/junegunn/fzf/releases&quot;&gt;github release&lt;/a&gt;页面下载好对应二进制文件后上传文件到服务器并执行以下命令即可&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tar -xvzf fzf-0.57.0-linux_amd64.tar.gz
mv fzf /usr/local/bin
chmod +x /usr/local/bin/fzf
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ChatGPT&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>mysql binlog 数据恢复 记一次事故数据恢复</title><link>https://blog.wangshuai.app/2024-10-13-mysql-binlog%E6%95%B0%E6%8D%AE%E6%81%A2%E5%A4%8D/</link><guid isPermaLink="true">https://blog.wangshuai.app/2024-10-13-mysql-binlog%E6%95%B0%E6%8D%AE%E6%81%A2%E5%A4%8D/</guid><pubDate>Sun, 13 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近项目即将上线,收到任务先行在正式环境上备份还原库;于是有了以下命令:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mysqldump -u root -p xxxx &amp;gt; xxxx20241012.sql&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;使用标准&lt;code&gt;mysqldump&lt;/code&gt;备份 sql文件;然后再执行:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;以下为错误示范&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;&lt;code&gt;mysql -u root -p xxx &amp;lt; xxxx20241012.sql&lt;/code&gt;&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;以上命令请认真仔细看好,每个字都需确认清楚;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;毫无疑问,这句命令犯了一个巨大错误(&lt;strong&gt;备份还原不能对一个表操作&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;正确命令为:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mysql -u root -p xxx2 &amp;lt; xxxx20241012.sql&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;由于错误的命令,我使用dump备份覆盖了线上数据库;而且由于还原耗时非常长;导致数据库丢失了2H的数据&lt;/p&gt;
&lt;p&gt;本文介绍我的binlog数据还原过程;&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;首先声明我的mysql版本是8025 ,服务所用框架为 &lt;code&gt;spring boot &lt;/code&gt; 数据存储使用&lt;code&gt;mybatis&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;很多教程都写的是用&lt;em&gt;position&lt;/em&gt;来还原数据,这当然是标准操作,毕竟有&lt;code&gt;mysqlbinlog&lt;/code&gt;命令原生支持&lt;/p&gt;
&lt;p&gt;假如操作简单这么做当然省事效果好;但我其实是还原覆盖了数据库两次,服务器上生成了多个binlog文件;而且正式环境数据太多,&lt;strong&gt;我无法找到正确起止pos&lt;/strong&gt;,我更担心这个操作会再次对数据库的数据产生不利影响;进而产生更糟糕的结果;&lt;/p&gt;
&lt;p&gt;因此我决定退而求其次,放弃全表还原,仅从binlog中 找到重要数据 的SQL语句,仅还原重要数据.&lt;/p&gt;
&lt;p&gt;虽然,数据库被之前的备份还原导致中间的数据丢失;但在还原的过程中;服务正常运行,数据还是曾经写入到了数据库中;&lt;/p&gt;
&lt;p&gt;因此&lt;strong&gt;binlog会有正常流程产生的数据&lt;/strong&gt;;因此只需找到此时的INSERT 和UPDATE语句即可修复数据问题.&lt;/p&gt;
&lt;p&gt;数据处理过程中涉及非常多的步骤,由于我非专业dba,linux命令也不熟;多亏的chatgpt,很多问题 它能提供能运行的代码;为我节省了大量时间;强烈建议多用用.&lt;/p&gt;
&lt;h4&gt;操作步骤&lt;/h4&gt;
&lt;h5&gt;找到异常时数据库所有SQL记录&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;首先,找到mysql 服务器数据存放路径;查看异常binlog文件,由于我多次还原库因此我这里会有多个binlog&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;ls /xxx/mysql -all&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;xxxx 14:00 binlog.000048&lt;/p&gt;
&lt;p&gt;xxxx 15:00 binlog.000049&lt;/p&gt;
&lt;p&gt;xxxx 16:00 binlog.000050&lt;/p&gt;
&lt;p&gt;xxxx 17:00 binlog.000051&lt;/p&gt;
&lt;p&gt;xxxx 18:00 binlog.000052&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;还原数据之前,可以先将binlog文件 下载到本地,然后用本地的&lt;code&gt;mysqlbinlog&lt;/code&gt;命令解密及搜索处理;因为我这里数据量巨大而且完整解密出SQL语句之后的文件同样非常大;如此大的文件,操作建议本地在SSD上进行.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;可以直接用less 查看 binlog;但是里面的SQL语句 base64加密了,因此使用以下命令解密&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;mysqlbinlog --stop-datetime=&quot;2024-10-14 16:00:00&quot; binlog.000050 --base64-output=decode-rows --verbose &amp;gt;coded_after50.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为我这里数据多为方便追踪,每个binlog按照序号分别解密&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...coded_after51.sql
...coded_after52.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用&lt;code&gt;less&lt;/code&gt;查看数据内容:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;less code_after50.sql&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#241012 16:29:35 server id 1  end_log_pos 485557996 CRC32 0x1bc4ffbf 	Table_map: `xxx`.`yyyy` mapped to number 212
# at 485557996
#241012 16:29:35 server id 1  end_log_pos 485558475 CRC32 0x7448527f 	Update_rows: table id 212 flags: STMT_END_F
### UPDATE `xxx`.`yyyy`
### WHERE
###   @1=zzz
###   @2=&apos;2024-10-12 16:29:25&apos;
###   @3=&apos;xxxx&apos;
###   @4=NULL
###   @5=NULL
###   @6=0
###   @7=NULL
###   @8=&apos;33&apos;
###   @9=NULL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上为我解密的sql文件示例;每个binlog文件都有大量的SQL语句,每个文件都大于2G;在windows中 即使使用&lt;code&gt;logview&lt;/code&gt;也极难找到我想要的数据;&lt;/p&gt;
&lt;p&gt;但linux 的&lt;code&gt;grep&lt;/code&gt;却极其好用高效;因此我在&lt;code&gt;powershell&lt;/code&gt;中输入&lt;code&gt;bash&lt;/code&gt; 切换到&lt;code&gt;wsl&lt;/code&gt;环境中来操作.&lt;/p&gt;
&lt;h5&gt;筛选出目标表的操作SQL语句&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;确认大致SQL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;假如你直接执行&lt;code&gt;grep &apos;yyyy&apos; coded_after50.sql&lt;/code&gt;会发现大量不需要的数据,且没有上下文无法确定;此时你向ChatGPT求助;它会告诉你正确的命令;一番修改后就得到这样的语句&lt;/p&gt;
&lt;p&gt;&lt;code&gt;grep -A 50 yyyy decoded_after50.sql | grep -A 50 -B 25 &quot;@2=&apos;2024-10-12&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;此处解释一下:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-A 50&lt;/code&gt;表面返回表后面50行数据,方便查看是否是需要的SQL语句&lt;/p&gt;
&lt;p&gt;&lt;code&gt;| grep&lt;/code&gt;两次搜索 是从相关的表中找到出现问题时执行的SQL语句&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-B 25&lt;/code&gt;返回二次搜索关键字前25行,方便后续处理&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@2=&apos;2024-10-12&apos;&lt;/code&gt;是我表中的创建时间,添加此条件后只返回当天的数据,如果想要过滤详细时间&lt;/p&gt;
&lt;p&gt;​	可以使用&lt;code&gt;-E  &quot;@2=&apos;2024-10-12 15:[4-5]&quot;&lt;/code&gt;使用正则查找15点40之后的数据&lt;/p&gt;
&lt;p&gt;​	使用&lt;code&gt;-E  &quot;@2=&apos;2024-10-12 1[6-8]&quot;&lt;/code&gt;查找16-18点的数据&lt;/p&gt;
&lt;p&gt;多次调整过滤条件后添加 &lt;code&gt;&amp;gt; 50.sql&lt;/code&gt;将结果写入到文件中 方便后续处理&lt;/p&gt;
&lt;p&gt;所以现在的命令是这样的&lt;/p&gt;
&lt;p&gt;&lt;code&gt;grep -A 50 yyyy decoded_after50.sql | grep -A 50 -B 25 -E  &quot;@2=&apos;2024-10-12 15:[4-5]&quot; &amp;gt;50.SQL&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;假如有多个文件想要合并到一起.可以使用&lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;操作符,如:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;grep -A 50 yyyy decoded_after51.sql | grep -A 50 -B 25 -E  &quot;@2=&apos;2024-10-12 15:[4-5]&quot; &amp;gt;&amp;gt;50.SQL&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;表示附加binlog51中的查找结果到50.SQL中&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;返回明确SQL语句&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根据你的业务场景,你可以从&lt;code&gt;50.SQL&lt;/code&gt;中找到大致的SQL语句,但是由于服务使用了&lt;code&gt;mybatis&lt;/code&gt;导致有很多@符号,且文件中包括很多非SQL字符串,要转成所需SQL还需进一步处理&lt;/p&gt;
&lt;p&gt;我这里给出我的简单处理的代码,使用C# 创建控制台即可使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using Dapper;
using MySqlConnector;
using System.Data;
using System.Data.SqlClient;
using static System.Net.Mime.MediaTypeNames;
using System.Text.RegularExpressions;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 连接字符串
            string connectionString =
                &quot;server=xxxx&quot;;

            // 使用Dapper执行SQL语句
            using (IDbConnection connection = new MySqlConnection(connectionString))
            {               
                var users = connection.Query&amp;lt;User&amp;gt;(&quot;show columns from xxx;&quot;).Select(s =&amp;gt; s.Field).ToList();

                string filePath; // Replace with your actual file path

                // 目标目录
                string directoryPath = @&quot;G:\data\sql\&quot;;

                // 最大文件大小限制 (500 KB)
                long maxSizeInBytes = 500 * 1024;

                // 获取目录中所有文件
                string[] files = Directory.GetFiles(directoryPath);

                // 筛选大小小于500 KB 的文件
                var smallFiles = files.Where(file =&amp;gt;
                {
                    var info = new FileInfo(file);
                    return info.Length &amp;lt; maxSizeInBytes &amp;amp;&amp;amp; info.Extension == &quot;.sql&quot;;
                });
                foreach (var item in smallFiles)
                {
                    filePath = item;
                    // 输出文件路径
                    var fileINfo = new FileInfo(filePath);
                    var isInsert = false;
                    string outputFilePath = fileINfo.Name;

                    // 使用流读取文件
                    using (StreamReader reader = new StreamReader(filePath))
                    {
                        // 使用流写入新文件
                        using (StreamWriter writer = new StreamWriter(outputFilePath))
                        {
                            string line;
                            var listColumnName = new List&amp;lt;string&amp;gt;();

                            while ((line = reader.ReadLine()) != null)
                            {
                                // 查找 &quot;###&quot; 并替换后面的内容
                                int index = line.IndexOf(&quot;###&quot;);

                                var tableIndex = false;
                                var needColumns = false;
                                if (index &amp;gt;= 0)
                                {

                                    Match match = Regex.Match(line, @&quot;`(\w+)`.`(\w+)`&quot;);

                                    if (match.Success)
                                    {
                                        listColumnName = connection.Query&amp;lt;User&amp;gt;($&quot;show columns from {match.Groups[2].Value};&quot;).Select(s =&amp;gt; s.Field).ToList();
                                        if (line.Contains(&quot;INSERT INTO&quot;))
                                        {
                                            needColumns = true;
                                            isInsert=true;
                                        }
                                        else
                                        {
                                            isInsert = false;
                                        }
                                        tableIndex = true;
                                    }
                                    // 提取需要替换的部分
                                    string replacement = line.Substring(index + 3).Trim();
                                    string result;

                                    if (isInsert)
                                    {
                                        string pattern = @&quot;@\d+=&quot;; // 正则表达式匹配 @数字=
                                        result = Regex.Replace(replacement, pattern, &quot;&quot;);
                                    }
                                    else
                                    {

                                        // 正则表达式
                                        string pattern = @&quot;@(\d+)&quot;;

                                        // 替换函数
                                        MatchEvaluator evaluator = match =&amp;gt;
                                        {
                                            // 获取匹配的数字
                                            int index2 = int.Parse(match.Groups[1].Value) - 1;

                                            // 检查数组索引是否有效
                                            if (index2 &amp;gt;= 0 &amp;amp;&amp;amp; index2 &amp;lt; listColumnName.Count)
                                            {
                                                // 返回数组中对应元素的值
                                                return listColumnName[index2];
                                            }
                                            else
                                            {
                                                // 索引无效，返回原始字符串
                                                return match.Value;
                                            }
                                        };

                                        // 使用正则表达式替换
                                        result = Regex.Replace(replacement, pattern, evaluator);
                                    }


                                    // 替换内容
                                    line = line.Replace(line.Substring(index),
                                        result + (needColumns ? $&quot;({string.Join(&quot;,&quot;, listColumnName)})&quot; : &quot;,&quot;));
                                }
                                else if (tableIndex)
                                {
                                    tableIndex = false;
                                    listColumnName = new List&amp;lt;string&amp;gt;();
                                    isInsert= false;
                                }

                                // 写入新文件
                                writer.WriteLine(line);
                            }
                        }
                    } 
                }
            }
        }

        public class User
        {
            public string Field { get; set; }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;处理后在bin目录即可看到同名文件只需少量复制粘贴即可有完整SQL&lt;/p&gt;
&lt;p&gt;这里,假如你想要标识哪些SQL没写入;可以使用&lt;a href=&quot;https://tools.stefankueng.com/grepWin.html&quot;&gt;&lt;code&gt;grepwin&lt;/code&gt;&lt;/a&gt;搜索表名;然后根据行号逐个复制,防止遗漏&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/grep_win_mysql.png&quot; alt=&quot;image-20241014012955156&quot; /&gt;&lt;/p&gt;
&lt;p&gt;当然,你也可以写你自己的文本处理方法,只要调用&lt;code&gt;show columns from xxx;&lt;/code&gt;找到表对应的字段 即可替换出正确的SQL&lt;/p&gt;
&lt;h5&gt;数据还原&lt;/h5&gt;
&lt;p&gt;按照以上的步骤找出所有表相关的SQL语句之后,我从正式库同步一份目标表结构的空库到本地;&lt;/p&gt;
&lt;p&gt;然后依时间执行SQL语句,即可得到最终结果的表;&lt;/p&gt;
&lt;p&gt;中间因为大文本搜索,可能会有重复数据,本地执行SQL后数据库会帮我们检查数据的正确性,&lt;/p&gt;
&lt;p&gt;根据逻辑,检查数据缺失或者关联的表数据&lt;/p&gt;
&lt;p&gt;全部流程完成之后;&lt;/p&gt;
&lt;p&gt;在本地的新测试库即可得到完整的修复数据拷贝,此时将所有数据导出成SQL语句&lt;/p&gt;
&lt;p&gt;再补充上事务代码:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;START TRANSACTION;
...INSERT
COMMIT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;多次检查之后,就能上线修复了.&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;https://kousikraj.me/2014/07/19/how-to-recover-data-table-level-using-mysql-bin-log-file-mysqlbinlog/&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/Jokers_lin/article/details/126449539&quot;&gt;Mysql 通过 binlog日志 恢复数据（数据搞丢看过来）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cnblogs.com/rickiyang/p/13841811.html&quot;&gt;带你了解 MySQL Binlog 不为人知的秘密&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ChatGPT&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>windows login microsoft account something went wrong</title><link>https://blog.wangshuai.app/2024-06-11-windows%E7%99%BB%E5%BD%95%E5%BE%AE%E8%BD%AF%E8%B4%A6%E5%8F%B7%E5%8F%91%E7%94%9F%E4%BA%86%E9%94%99%E8%AF%AF/</link><guid isPermaLink="true">https://blog.wangshuai.app/2024-06-11-windows%E7%99%BB%E5%BD%95%E5%BE%AE%E8%BD%AF%E8%B4%A6%E5%8F%B7%E5%8F%91%E7%94%9F%E4%BA%86%E9%94%99%E8%AF%AF/</guid><pubDate>Tue, 11 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近新买了nuc  ,在新电脑上登录微软账号时遇到了&lt;code&gt;发生了错误&lt;/code&gt;(&lt;code&gt;something went wrong&lt;/code&gt;)的异常,特此记录解决方法.&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;本机已运行clash ,具备 google条件,同时 首次登录 微软账号一切正常;只有尝试 退出微软账号 使用本地账号登录,然后第二次使用微软账号登录时才会遇到这个&lt;code&gt;something went wrong&lt;/code&gt;的异常.&lt;/p&gt;
&lt;p&gt;本来我参考别人的教程,重新新建普通用户登录到了微软账号,我以为我已经解决了这个问题;直到我尝试打开微软商店打不开&lt;/p&gt;
&lt;p&gt;我才意识到,是uwp的代理问题;重新设置后果然一切正常.&lt;/p&gt;
&lt;h4&gt;操作步骤&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;依次打开&lt;code&gt;clash&lt;/code&gt;→&lt;code&gt;General&lt;/code&gt;→&lt;code&gt;UWP Loopback:Launch Helper&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;点击 &lt;code&gt;Exempt All&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;点击&lt;code&gt;Save Changes&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/clash_launchHelper.png&quot; alt=&quot;image-20240611004359507&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以,问题是微软登录窗口用到了uwp组件;这个组件出现了网络问题 ;导致出现&lt;code&gt;发生了错误&lt;/code&gt;的问题&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/m0_56604126/article/details/121460383&quot;&gt;解决Windows11/10本地账户改用Microsoft账户登录显示“发生了错误”的问题&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>windows11打开可选功能</title><link>https://blog.wangshuai.app/2024-06-10-windows11%E6%89%93%E5%BC%80%E5%8F%AF%E9%80%89%E5%8A%9F%E8%83%BD/</link><guid isPermaLink="true">https://blog.wangshuai.app/2024-06-10-windows11%E6%89%93%E5%BC%80%E5%8F%AF%E9%80%89%E5%8A%9F%E8%83%BD/</guid><pubDate>Mon, 10 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;可选功能是windows自带的一组 平常隐藏的功能;可以在设置中 打开来增强windows能力;比如我最近用到的投屏(投放)功能就可以在可选功能(&lt;code&gt;optionalfeatures&lt;/code&gt;)中打开&lt;/p&gt;
&lt;p&gt;windows 10 中可选功能的位置在 设置 →应用中&lt;/p&gt;
&lt;p&gt;在windows 11 的之前版本中 可选功能也在应用中;但在最新版本的windows11 中可选功能已经移动了位置;本文主要解决此问题.&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;微软官方文档中已经说明了可选功能的打开方法:&lt;a href=&quot;https://learn.microsoft.com/zh-cn/windows/client-management/client-tools/add-remove-hide-features?pivots=windows-11&quot;&gt;添加、删除或隐藏 Windows 功能&lt;/a&gt;,可惜搜索引擎排名不高,且被国内文章误导,进而无法找到最新的可选功能位置.&lt;/p&gt;
&lt;p&gt;这里我引用一下:&lt;/p&gt;
&lt;p&gt;新版的可选功能位于:&lt;strong&gt;设置&lt;/strong&gt;→&lt;strong&gt;系统&lt;/strong&gt;→&lt;strong&gt;可选功能&lt;/strong&gt; 中&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/win11_optionfeature.png&quot; alt=&quot;win11_whereisoptionfeature&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最简单的打开方法,还是使用命令&lt;/p&gt;
&lt;h4&gt;操作步骤&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;右键windows 图标 打开 运行窗口 ,或者直接按&lt;code&gt;Win&lt;/code&gt;+&lt;code&gt;R&lt;/code&gt;键&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在运行窗口中输入&lt;code&gt;ms-settings:optionalfeatures&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/win11_mssetting_optionalfeatures.png&quot; alt=&quot;image-20240611002046930&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/client-management/client-tools/add-remove-hide-features?pivots=windows-11&quot;&gt;Add, remove, or hide Windows features&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>windows使用navi指南</title><link>https://blog.wangshuai.app/2024-02-07-windows%E4%BD%BF%E7%94%A8navi/</link><guid isPermaLink="true">https://blog.wangshuai.app/2024-02-07-windows%E4%BD%BF%E7%94%A8navi/</guid><pubDate>Wed, 07 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;命令行(&lt;code&gt;command-line&lt;/code&gt;)是一种瑞士军刀,无论你想做什么;几乎都能用命令来实现你的要求.&lt;/p&gt;
&lt;p&gt;在日常开发时根据需要;我常常会使用多种组合命令,常用的命令当然很容易记住和使用;而一些不那么常用及需要多个步骤才能实现的要求;用命令就不会那么方便;通常我都会记笔记,然后使用前查一下笔记再使用.&lt;/p&gt;
&lt;p&gt;对于中阶的命令,通常可以使用命令行的历史记录功能来速查;快捷键通常是&lt;code&gt;Ctrl&lt;/code&gt;+&lt;code&gt;R&lt;/code&gt;;命令行历史通常只适用于单行命令;对于多行命令它还是太乏力了;&lt;/p&gt;
&lt;p&gt;我一直想找一个命令行收藏夹这样的工具;想用的时候就像浏览器打开一个链接那样点击即用,庆幸的是我找到了.&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;解决方法就是&lt;a href=&quot;https://github.com/denisidoro/navi&quot;&gt;navi&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;navi&lt;/code&gt;是一种交互式备忘命令行工具(&lt;code&gt;An interactive cheatsheet tool for the command-line.&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;在官方的文档中介绍的都是linux的安装和使用方法;本文介绍windows中的使用教程.&lt;/p&gt;
&lt;p&gt;首先声明我的环境&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本机是windows11;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;s&gt;用的wsl版本(可以使用&lt;code&gt;wsl.exe -l -v &lt;/code&gt;查看)是1(wsl2同样可用)&lt;/s&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;安装工具是&lt;code&gt;choco&lt;/code&gt;,其版本是&lt;code&gt;0.11.3&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;操作步骤&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;choco install navi  &lt;/code&gt;命令安装navi&lt;/li&gt;
&lt;li&gt;安装完成后,运行&lt;code&gt;navi&lt;/code&gt;命令会提示:&lt;code&gt;navi was unable to call fzf&lt;/code&gt;,因此继续执行命令&lt;code&gt;choco install fzf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;运行&lt;code&gt;navi repo add https://github.com/denisidoro/cheats&lt;/code&gt;命令安装命令模板&lt;/li&gt;
&lt;li&gt;输入&lt;code&gt;navi&lt;/code&gt;命令查看可用命令&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/navi_command_not_found.png&quot; alt=&quot;image-20240207162533826&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/bin/bash&lt;/code&gt;是本地命令预览;此时会发现这个异常:&lt;code&gt;/bin/bash:C:ProgramDatachocolateylibnavitoolsnavi.exe:command not found&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这里的命令是正常的,因为&lt;code&gt;navi&lt;/code&gt;是linux程序它只能被&lt;code&gt;choco&lt;/code&gt;安装到了&lt;code&gt;wsl&lt;/code&gt;中;而你直接执行&lt;code&gt;navi&lt;/code&gt;是从&lt;code&gt;powershell&lt;/code&gt;中启用的;你选择任意命令执行会发现&lt;code&gt;The command &apos;XXX&apos; could not be found in this WSL 1 distro.&lt;/code&gt;本文主要解决的就是这个问题&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本机的命令在windows中,而&lt;code&gt;navi&lt;/code&gt;存在于wsl中;要正常使用&lt;code&gt;navi&lt;/code&gt;需要通过&lt;code&gt;powershell.exe&lt;/code&gt;这个程序这个程序可以直接在&lt;code&gt;wsl&lt;/code&gt;的&lt;code&gt;bash&lt;/code&gt;环境中使用&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;首先创建自己的&lt;code&gt;.cheat&lt;/code&gt;文件,这个文件在你本机的&lt;code&gt;%AppData%\navi\cheats\自己的文件夹&lt;/code&gt;中,具体规则参照官方文档&lt;a href=&quot;https://github.com/denisidoro/navi?tab=readme-ov-file#cheatsheet-syntax&quot;&gt;Cheatsheet syntax&lt;/a&gt;这里我给我的配置&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;% cd



# cd 
powershell.exe   -noexit &apos;cd G:\work;ls;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;noexit&lt;/code&gt;参数;即可在&lt;code&gt;navi&lt;/code&gt;中使用同一个ps&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>windows 修改 grub引导顺序</title><link>https://blog.wangshuai.app/2023-09-14-windows%E4%BF%AE%E6%94%B9grub%E5%BC%95%E5%AF%BC%E9%A1%BA%E5%BA%8F/</link><guid isPermaLink="true">https://blog.wangshuai.app/2023-09-14-windows%E4%BF%AE%E6%94%B9grub%E5%BC%95%E5%AF%BC%E9%A1%BA%E5%BA%8F/</guid><pubDate>Thu, 14 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;在学习PVE系统的时候,基于现成的二奶机;&lt;/p&gt;
&lt;p&gt;我在原有的windows系统中在同一个盘里面安装了新的Debian系统;每次需要切换系统的时候;&lt;/p&gt;
&lt;p&gt;如果是Windows就使用&lt;a href=&quot;https://neosmart.net/EasyBCD/&quot;&gt;EasyBCD&lt;/a&gt;切换引导;如果是Debian,就修改&lt;code&gt;/etc/default/grub&lt;/code&gt;;&lt;/p&gt;
&lt;p&gt;最近新购入了主板;把原来的硬盘接入后顺利的识别到 了硬盘中的系统;但默认引导的是Windows系统;而且因为之前已经修改过Debian中的grub设置,无法再次编辑&lt;code&gt;/etc/default/grub&lt;/code&gt;实现Linux启动;那如何修复引导顺序问题呢?&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;在Bios的 &lt;code&gt;bios boot override&lt;/code&gt;中可以看到windows和linux系统;只是顺序不对;因此只要修改系统分区的UEFI顺序即可.&lt;/p&gt;
&lt;h4&gt;操作步骤&lt;/h4&gt;
&lt;p&gt;首先准备好PE系统,假如已有&lt;a href=&quot;https://www.wepe.com.cn/&quot;&gt;微PE&lt;/a&gt;则可跳过此步骤.我这里因为使用了&lt;a href=&quot;https://www.ventoy.net/cn/&quot;&gt;vendoy&lt;/a&gt;作为U盘系统;因此需要中转一下;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;打开&lt;code&gt;WePE.exe&lt;/code&gt;点击生成可启动&lt;code&gt;ISO&lt;/code&gt;导出&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/wepe_to_iso.png&quot; alt=&quot;wepe_iso&quot; /&gt;&lt;/p&gt;
&lt;p&gt;导出成功后放到&lt;code&gt;vendoy&lt;/code&gt;的iso目录下;启动&lt;code&gt;vendoy&lt;/code&gt;的系统并打开&lt;code&gt;wepe.iso&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;打开扇区小工具&lt;code&gt;BOOTICE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/pe_bootice.png&quot; alt=&quot;open_botice&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在UEFI选项中调整启动顺序,将&lt;code&gt;Debian&lt;/code&gt;调到第一个并保存;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PS:我保存之后测试还是不对,再进WePE里面再改一次才变好.&lt;/p&gt;
</content:encoded></item><item><title>使用docker加速阿里云ECS下载</title><link>https://blog.wangshuai.app/2023-06-16-%E9%98%BF%E9%87%8C%E4%BA%91ECS%E4%B8%8B%E8%BD%BD%E5%8A%A0%E9%80%9F/</link><guid isPermaLink="true">https://blog.wangshuai.app/2023-06-16-%E9%98%BF%E9%87%8C%E4%BA%91ECS%E4%B8%8B%E8%BD%BD%E5%8A%A0%E9%80%9F/</guid><pubDate>Fri, 16 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;公司正式服务器在阿里云上,为节省成本;公司购买的是1M小水管.运行服务跑跑网站的时候倒也够用;一旦遇到从服务器下载大文件的需求时就捉急了.&lt;/p&gt;
&lt;p&gt;最近服务器上内存占用异常,为分析内存占用原因;需要将dump文件下载下载本地分析.一个dump有6G大,就算压缩后也有500M;&lt;/p&gt;
&lt;p&gt;粗算一下也要1H,实际服务器有其它网络请求的情况下我花3小时才下载下来.&lt;/p&gt;
&lt;p&gt;那有什么下载加速的方法吗?&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;网络传输受限公网带宽,除了加钱没什么直接方案;不过阿里云内网里面没有网络限制,只要我们找到一种可以内网上传+外网下载不限速的服务就可以了,要是还免费就更佳.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文件传输最优方案应该是阿里云盘,毕竟是阿里的云盘;可惜我不知道阿里云盘的内网地址,此方案PASS.&lt;/li&gt;
&lt;li&gt;还有一种可靠方案,就是对象存储OSS(&lt;a href=&quot;https://help.aliyun.com/document_detail/39584.html&quot;&gt;ECS实例通过OSS内网地址访问OSS资源&lt;/a&gt;);临时上传下载要不了多少钱,不过我没有开通此服务,因此此方案优先级较低&lt;/li&gt;
&lt;li&gt;阿里云还提供有免费的镜像仓库服务,同时它提供内网地址;只要你在内网有linux服务器;通过服务器&lt;code&gt;docker build push&lt;/code&gt;本地&lt;code&gt;docker pull&lt;/code&gt;就可以间接将文件传回本地&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;操作步骤&lt;/h4&gt;
&lt;p&gt;首先在阿里云中开通&lt;a href=&quot;https://cr.console.aliyun.com/cn-hangzhou/instance/dashboard&quot;&gt;容器镜像服务&lt;/a&gt;,创建命名空间和镜像仓库;在&lt;a href=&quot;https://cr.console.aliyun.com/cn-hangzhou/instance/credentials&quot;&gt;访问凭证&lt;/a&gt;中设置固定密码用于账号登录;在基本信息中可查看操作指南;据此可以编写上传下载的命令.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;阿里云内网linux服务器用于文件上传.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里我写出我用的命令:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#先cd到空目录并将文件拷贝到这里
#info.log是要上传的文件
echo -e &apos;FROM hello-world\n COPY info.log /info.log \n CMD [&quot;hello&quot;] &apos; &amp;gt; Dockerfile

docker build -t upload .

#根据你的操作指南选择合适的内网镜像仓库地址
docker login --username=xxxx@yy.com registry-vpc.cn-hangzhou.aliyuncs.com

#zzzz是命名空间,download是我创建的用于文件传输的仓库名称
docker tag upload registry-vpc.cn-hangzhou.aliyuncs.com/zzzz/download
docker push registry-vpc.cn-hangzhou.aliyuncs.com/zzzz/download
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;本地使用&lt;code&gt;docker cp&lt;/code&gt;命令将文件拷到本地,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里贴出我用的powershell命令(windows)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#docker login xxxxx

#linux
id=$(docker create registry.cn-hangzhou.aliyuncs.com/zzzz/download)

docker cp $id:dump.tar.gz - &amp;gt; dump.tar.gz
docker rm -v $id

#windows
$container= &apos;registry.cn-hangzhou.aliyuncs.com/zzzz/download&apos;
docker pull $container
$id=$(docker create $container)
$fileName= &apos;info.log&apos;

$cp_arguments = @(&quot;cp&quot;,                                               `
                  &quot;$($id):$($fileName)&quot;,  `
                  &quot;D:\Work\docker\${fileName}&quot;)
docker $cp_arguments
docker rm -v $id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用docker传输确实略显麻烦,不过临时用用肯定比等三个小时强;毕竟服务器不是每天都需要dump.&lt;/p&gt;
&lt;p&gt;同理,腾讯云或者其它的只要提供免费docker服务的一般都可以用此方法.&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/62053469/docker-cp-parameter-expansion-in-powershell&quot;&gt;Docker cp parameter expansion in powershell&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>.NetCore登录验证码的实现（.Net core login captcha）</title><link>https://blog.wangshuai.app/2023-06-13-netcore%E7%99%BB%E5%BD%95%E9%AA%8C%E8%AF%81%E7%A0%81%E7%9A%84%E5%AE%9E%E7%8E%B0/</link><guid isPermaLink="true">https://blog.wangshuai.app/2023-06-13-netcore%E7%99%BB%E5%BD%95%E9%AA%8C%E8%AF%81%E7%A0%81%E7%9A%84%E5%AE%9E%E7%8E%B0/</guid><pubDate>Tue, 13 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;之前项目紧的时候，非功能要求都是能省则省，现在项目完成了；验收时有了安全性要求，自然需要登录验证码。&lt;/p&gt;
&lt;p&gt;国外的登录安全验证（&lt;code&gt;captcha&lt;/code&gt;）一般用的&lt;code&gt;reCAPTCHA &lt;/code&gt;，也就是经常见到的打勾；&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/login_captcha.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;有的时候还会触发别的题目，这种验证方式固然好；但不符合国情。&lt;/p&gt;
&lt;p&gt;国内用的最多的还是图形验证码。&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;登录验证码是防机器人登录的一种方式，这是一种系统安全手段；因此你的验证码值是不能告诉前端的，那怎么才能判断用户输入的验证码对不对呢？&lt;/p&gt;
&lt;p&gt;我的解决方法是&lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state&quot;&gt;&lt;code&gt;Session&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;启用&lt;code&gt;Session&lt;/code&gt;的代码如下:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;public void ConfigureServices(IServiceCollection services)
{
    //...

    services.AddDistributedMemoryCache();
    
    services.AddSession(options =&amp;gt;
    {
        options.IdleTimeout = TimeSpan.FromSeconds(60);
        options.Cookie.HttpOnly = true;
        options.Cookie.IsEssential = true;
    });
    services.Configure&amp;lt;CookiePolicyOptions&amp;gt;(options =&amp;gt;
    {
        // This lambda determines whether user consent for non-essential cookies is needed for a given request.
        options.CheckConsentNeeded = context =&amp;gt; false;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });
}
public void Configure(IApplicationBuilder app)
{
    //...
    app.UseSession();
    app.UseCookiePolicy();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;图形验证码的生成我使用了一个开源的库:&lt;a href=&quot;https://github.com/aliasadidev/SixLaborsCaptcha&quot;&gt;SixLaborsCaptcha&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;验证码的使用可查看官网示例，我的代码如下：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;using SixLaborsCaptcha.Mvc.Core;
...
		public void ConfigureServices(IServiceCollection services)
		{
			services.AddSixLabCaptcha(x =&amp;gt;
			{
				x.DrawLines = 4;
			});
		}
...
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// 获得验证码图片
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&quot;sixLaborsCaptcha&quot;&amp;gt;&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&quot;now&quot;&amp;gt;时间戳,前端使用Date.now()生成&amp;lt;/param&amp;gt;
/// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
[HttpGet]
[Route(&quot;[action]&quot;)]
public async Task&amp;lt;FileResult&amp;gt; GetCaptchaImage([FromServices] ISixLaborsCaptchaModule sixLaborsCaptcha, long? now)
{
    now ??= DateTime.Now.Ticks;

    string key = Extensions.GetUniqueKey(6);
    HttpContext.Session.SetString(&quot;CaptchaTime&quot;, now.ToString());
    HttpContext.Session.SetString(&quot;CaptchaKey&quot;, key);
    await HttpContext.Session.CommitAsync();

    _log.LogInformation(&quot;current session is {session}&quot;, HttpContext.Session.Id);
    _log.LogInformation(&quot;key is {key}&quot;, key);
    var imgText = sixLaborsCaptcha.Generate(key);
    return File(imgText, &quot;Image/Png&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;验证码校验：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;[HttpPost(&quot;[Action]&quot;)]
public async Task&amp;lt;UserInfo&amp;gt; Login(LoginInfo loginInfo)
{
    _log.LogInformation(&quot;current session is {session}&quot;, HttpContext.Session.Id);

    await HttpContext.Session.LoadAsync();
    _log.LogInformation(&quot;after load current session is {session}&quot;, HttpContext.Session.Id);

    if (string.IsNullOrEmpty(loginInfo.Captcha))
    {
        return BadRequest(&quot;请输入验证码!&quot;);
    }

    var timeStr = HttpContext.Session.GetString(&quot;CaptchaTime&quot;);
    if (string.IsNullOrEmpty(timeStr) || !long.TryParse(timeStr, out long result))
    {
        return BadRequest(&quot;登陆无效,请刷新后重试&quot;);
    }

    var time = new DateTime(result);
    if (time.AddMilliseconds(1) &amp;gt; DateTime.Now)
    {
        return BadRequest(&quot;验证码无效,请刷新后重试&quot;);
    }

    if (loginInfo.Captcha.ToLower() != HttpContext.Session.GetString(&quot;CaptchaKey&quot;)?.ToLower())
    {
        return BadRequest(&quot;验证码错误&quot;);
    }

    var info = Login(loginInfo.Account, loginInfo.Password);

    return info;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;验证码校验不区分大小写，同时我设置了一分钟超时时间&lt;/p&gt;
&lt;h4&gt;常见问题&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HttpContext.Session.Id&lt;/code&gt;数据不断变化(&lt;strong&gt;登陆无效,请刷新后重试&lt;/strong&gt;)的解决方法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;session&lt;/code&gt;依赖客户端的&lt;code&gt;cookie&lt;/code&gt;，在请求图片的时候服务端会在返回头中添加&lt;code&gt;Set-Cookie: .AspNetCore.Session=xxxx&lt;/code&gt;，在登录时依据此&lt;code&gt;cookie&lt;/code&gt;取得&lt;code&gt;session&lt;/code&gt;然后才能判断验证码的正误；因此应首先在&lt;code&gt;Chrome&lt;/code&gt;的&lt;code&gt;Network&lt;/code&gt;中查看登录请求是否携带&lt;code&gt;.AspNetCore.Session&lt;/code&gt;的&lt;code&gt;cookie&lt;/code&gt;；若无，可检查以下几个方向：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;跨域问题&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;生成图片的API和登录的API最好属于一个域，若存在跨域问题；一般情况下不会发送&lt;code&gt;cookie&lt;/code&gt;，需在前端发起请求时添加&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials&quot;&gt;&lt;code&gt;withCredentials&lt;/code&gt;&lt;/a&gt;头，具体可查阅使用的http库文档。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;分布式缓存配置问题&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;session&lt;/code&gt;默认使用基于内存的缓存(&lt;code&gt;AddDistributedMemoryCache&lt;/code&gt;)，若项目本身使用了基于&lt;code&gt;redis&lt;/code&gt;的缓存(&lt;code&gt;AddStackExchangeRedisCache&lt;/code&gt;)，则默认使用了&lt;code&gt;redis&lt;/code&gt;缓存，在&lt;code&gt;redis&lt;/code&gt;缓存配置有误时，&lt;code&gt;session&lt;/code&gt;无法正常工作，因此不会向前端添加&lt;code&gt;Set-Cookie: .AspNetCore.Session=xxxx&lt;/code&gt;，同时系统无任何异常，此时可尝试去掉验证码登录逻辑确认配置正常&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;自动登录方法&lt;/p&gt;
&lt;p&gt;在启用验证码登录后每次测试都需要手输验证码，这让自动化测试变得麻烦；因此我添加了一种额外的基于时间的OTP验证码。使用&lt;a href=&quot;https://github.com/kspearrin/Otp.NET&quot;&gt;Otp.NET&lt;/a&gt;实现，在验证验证码之前验证OTP code即可。&lt;/p&gt;
&lt;p&gt;配合&lt;code&gt;Vaultwarden &lt;/code&gt;可以实现无感登录。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/aliasadidev/SixLaborsCaptcha&quot;&gt;SixLaborsCaptcha&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kspearrin/Otp.NET&quot;&gt;https://github.com/kspearrin/Otp.NET&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>SC2星际争霸国服更新国际服指南</title><link>https://blog.wangshuai.app/2023-01-26-sc2%E6%98%9F%E9%99%85%E4%BA%89%E9%9C%B8%E5%9B%BD%E6%9C%8D%E6%9B%B4%E6%96%B0%E5%9B%BD%E9%99%85%E6%9C%8D%E6%8C%87%E5%8D%97/</link><guid isPermaLink="true">https://blog.wangshuai.app/2023-01-26-sc2%E6%98%9F%E9%99%85%E4%BA%89%E9%9C%B8%E5%9B%BD%E6%9C%8D%E6%9B%B4%E6%96%B0%E5%9B%BD%E9%99%85%E6%9C%8D%E6%8C%87%E5%8D%97/</guid><pubDate>Thu, 26 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;时间来到2025年10月21日 ;&lt;/p&gt;
&lt;p&gt;国服已经重新回来并开启了技术测试,现在已经可以欢乐的突变了&lt;/p&gt;
&lt;p&gt;而从亚服回到国服则非常简单;不会有之前国服去亚服的各种奇怪问题;只需更新最新战网客户端;在登录页面切换为&lt;code&gt;中国&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/battlenet_cn.png&quot; alt=&quot;battlenet to cn&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后更新游戏即可&lt;/p&gt;
&lt;p&gt;记得在礼物中领取&lt;code&gt;30天强化剂&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/sc2_gift.png&quot; alt=&quot;合作任务强化剂&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;以下为旧消息&lt;/p&gt;
&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;本文可解决以下问题:
1.游戏客户端版本和所选区域不符
2.战网国际服的安装
3.战网完全卸载重新安装&lt;/p&gt;
&lt;p&gt;这些问题我的解决方案就是重新安装战网国际服&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;一般解决方法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;假如你有科学上网环境,可以直接卸载&lt;strong&gt;暴雪战网&lt;/strong&gt;
然后下载亚服战网选择英文安装(中文肯定会失败)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;已选择中文安装解决方案&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;假如已经选了中文
则需要在注册表中删除战网语言选择项:
在开始中输入&lt;strong&gt;regedit&lt;/strong&gt;打开注册表;删除&lt;strong&gt;计算机\HKEY_CURRENT_USER\SOFTWARE\Blizzard Entertainment\Launcher&lt;/strong&gt;选项&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/blizzard_launcher.png&quot; alt=&quot;launcher&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无VPN下载战网方法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;假如没有梯子,可以使用迅游加速器下载战网&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;手动卸载旧版战网方法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;完全卸载战网方法:
打开控制面板的&lt;strong&gt;卸载程序&lt;/strong&gt;菜单卸载&lt;strong&gt;暴雪战网&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在文件资源管理器中打开**%appdata%&lt;strong&gt;和&lt;/strong&gt;%programdata%&lt;strong&gt;删除&lt;/strong&gt;Battle.net&lt;strong&gt;和&lt;/strong&gt;Blizzard Entertainment**文件夹&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SC2下载加速方法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下载亚服星际二推荐使用网易UU加速器,加速亚服战网后游戏下载也会变快(10M/s)&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=UoVXzXqiETw&quot;&gt;How To Completely Uninstall Battle.net From Windows 7/8/10&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>浙里办真机调试方法,解决跳转地址不在白名单中问题</title><link>https://blog.wangshuai.app/2023-01-22-%E6%B5%99%E9%87%8C%E5%8A%9E%E7%9C%9F%E6%9C%BA%E8%B0%83%E8%AF%95%E6%96%B9%E6%B3%95/</link><guid isPermaLink="true">https://blog.wangshuai.app/2023-01-22-%E6%B5%99%E9%87%8C%E5%8A%9E%E7%9C%9F%E6%9C%BA%E8%B0%83%E8%AF%95%E6%96%B9%E6%B3%95/</guid><pubDate>Sun, 22 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近做的浙里办H5应用里面遇到一个文件上传的问题,这是一个偶发性问题;因此需要大量上传测试&lt;/p&gt;
&lt;p&gt;而浙里办的开发工具&lt;code&gt;ZWEurope&lt;/code&gt;在绑定安卓设备时选不了文件,要想测试bug就需要发布后真机测试&lt;/p&gt;
&lt;p&gt;要是能有一个本地真机调试的方法,不就完美了吗?&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;从&lt;code&gt;ZWEurope&lt;/code&gt;里面就能知道浙里办为我们提供了基本的认证方法 ,除此之外和普通的H5应用并无区别&lt;/p&gt;
&lt;p&gt;通过扫描发布二维码,我们可以直接在浙里办APP上查看不同的发布内容;而直接打开二维码链接可以看到它就是普通的H5网站;因此我大胆尝试,用手机APP直接扫描本地&lt;code&gt;npm run serve&lt;/code&gt;网址二维码(在chrome中可以直接点击网址后面的分享箭头选择&lt;code&gt;创建二维码&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;APP提示&lt;code&gt;网页无法打开&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这也正常,因为手机并不能访问PC的localhost的网站&lt;/p&gt;
&lt;p&gt;重新扫描192.158.1.xx:8080(局域网访问网址)的网址,会发现浙里办无响应;这里使用&lt;code&gt;ZWEurope&lt;/code&gt;重试可以知道是因为&lt;code&gt;localhost:8080&lt;/code&gt;无法访问导致的;修改&lt;code&gt;redirectUrl=http://localhost:8080&lt;/code&gt;为&lt;code&gt;redirectUrl=http://192.168.1.xx:8080&lt;/code&gt;重试&lt;/p&gt;
&lt;p&gt;结果APP提示&lt;code&gt;跳转地址不在白名单中!&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这就比较难办了,因为这个白名单是不归我们管的;后台并没有配置方法&lt;/p&gt;
&lt;p&gt;除非可以有代理手机localhost的方法&lt;/p&gt;
&lt;p&gt;通过前文&lt;a href=&quot;../2023-01-20-%E8%BF%9C%E7%A8%8B%E6%97%A0%E7%BA%BF%E8%B0%83%E8%AF%95%E5%AE%89%E5%8D%93%E6%89%8B%E6%9C%BA(mi9)%E7%9A%84chrome%E6%B5%8F%E8%A7%88%E5%99%A8&quot;&gt;远程无线调试安卓手机(mi9)的chrome浏览器&lt;/a&gt;可知&lt;code&gt;google&lt;/code&gt;为我们提供了强大的手机--PC联调机制;&lt;/p&gt;
&lt;p&gt;其实它还有一个功能,就是端口转发(&lt;code&gt;Port forwarding&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/chrome_inspect_devices_forwarding.png&quot; alt=&quot;port forwarding&quot; /&gt;&lt;/p&gt;
&lt;p&gt;开启此功能后即可在手机上通过&lt;code&gt;loclahost:8080&lt;/code&gt;访问PC的&lt;code&gt;localhost:8080&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;所以真机调试浙里办的方法就是:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;npm run&lt;/code&gt;之后 确保可以通过&lt;code&gt;ZWEurope&lt;/code&gt;本地访问待调试应用&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;手机开启&lt;code&gt;USB调试&lt;/code&gt;(&lt;code&gt;无线调试&lt;/code&gt;)并与PC连接&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;配置手机到PC的端口转发(二选一)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;使用&lt;code&gt;adb&lt;/code&gt;命令直接转发(推荐)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;adb reverse tcp:8080 tcp:8080&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用chrome的&lt;code&gt;Port forwarding&lt;/code&gt;转发&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;chrome://inspect/#devices&lt;/code&gt;中开启&lt;code&gt;Port forwarding&lt;/code&gt;并配置待调试应用端口转发&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PC打开&lt;code&gt;npm run&lt;/code&gt;的服务(如:&lt;code&gt;localhost:8080&lt;/code&gt;),选择&lt;code&gt;分享&lt;/code&gt;→&lt;code&gt;创建二维码&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;打开浙里办APP扫描二维码&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样操作后并不能如&lt;code&gt;ZWEurope&lt;/code&gt;一样在手机上触发代码中的断点;因为浙里办APP并未启用调试功能;只能通过&lt;code&gt;log&lt;/code&gt;确认问题;唯一的好处是可以即时查看代码效果,避免发布再测试的流程&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[&lt;a href=&quot;https://stackoverflow.com/questions/4779963/how-can-i-access-my-localhost-from-my-android-device&quot;&gt;How can I access my localhost from my Android device?&lt;/a&gt;](https://stackoverflow.com/a/46795769/7960551)&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>远程无线调试安卓手机(mi9)的chrome浏览器</title><link>https://blog.wangshuai.app/2023-01-20-%E8%BF%9C%E7%A8%8B%E6%97%A0%E7%BA%BF%E8%B0%83%E8%AF%95%E5%AE%89%E5%8D%93%E6%89%8B%E6%9C%BA(mi9)%E7%9A%84chrome%E6%B5%8F%E8%A7%88%E5%99%A8/</link><guid isPermaLink="true">https://blog.wangshuai.app/2023-01-20-%E8%BF%9C%E7%A8%8B%E6%97%A0%E7%BA%BF%E8%B0%83%E8%AF%95%E5%AE%89%E5%8D%93%E6%89%8B%E6%9C%BA(mi9)%E7%9A%84chrome%E6%B5%8F%E8%A7%88%E5%99%A8/</guid><pubDate>Fri, 20 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近使用&lt;a href=&quot;http://zh.uniapp.dcloud.io/&quot;&gt;uni-app&lt;/a&gt;创建了一个H5项目;里面使用了自带的video播放&lt;code&gt;m3u8&lt;/code&gt;格式的直播视频;由于播放器的限制PC上没有对应解码器,因此只能在手机上看到视频播放效果;这就给debug调试带来了很大的不便.&lt;/p&gt;
&lt;p&gt;既然电脑上可以debug调试,手机同为chrome浏览器难道没有对应的调试方法吗?&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;答案当然是肯定的,chrome为我们提供了远程调试方法(&lt;a href=&quot;https://developer.chrome.com/docs/devtools/remote-debugging/&quot;&gt;Remote debug Android devices&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;我在公司没有USB数据线,因此决定采用无线调试的方法,若你可以有线连接PC则可以跳过这部分&lt;/p&gt;
&lt;h4&gt;无线调试&lt;/h4&gt;
&lt;p&gt;我手机的系统是&lt;code&gt;MIUI 12.5.6&lt;/code&gt; 是&lt;code&gt;Android 11&lt;/code&gt;的版本&lt;/p&gt;
&lt;p&gt;无线调试的方法就是首先使用&lt;code&gt;adb&lt;/code&gt;工具和手机建立连接,然后再调试&lt;/p&gt;
&lt;p&gt;因为我不做安卓开发,没有&lt;a href=&quot;https://developer.android.com/studio?hl=zh-cn&quot;&gt;Android Studio&lt;/a&gt;,因此需要下载&lt;code&gt;adb&lt;/code&gt;命令行工具:&lt;a href=&quot;https://developer.android.com/studio/releases/platform-tools?hl=zh-cn&quot;&gt;SDK 平台工具版本说明&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;下载解压后将解压目录添加到环境变量中;&lt;/p&gt;
&lt;p&gt;手机打开&lt;code&gt;设置&lt;/code&gt;→&lt;code&gt;更多设置&lt;/code&gt;→&lt;code&gt;开发者选项&lt;/code&gt;→&lt;code&gt;无线调试&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;手机在&lt;code&gt;无线调试&lt;/code&gt;中点击&lt;code&gt;使用配对码配对设备&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/android_wifi_paire.png&quot; alt=&quot;adb pair&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PC根据手机的IP 端口 和配对码 输入命令配对&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;命令为&lt;code&gt;adb pair 192.168.1.12:42213&lt;/code&gt;根据提示输入配对码&lt;code&gt;633060&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PC输入手机的IP地址和端口与之建立连接&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;命令为&lt;code&gt;adb connect 192.168.1.12:39715&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PC输入&lt;code&gt;adb devices&lt;/code&gt;查看设备连接状态&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/android_adb_command.png&quot; alt=&quot;adb command&quot; /&gt;&lt;/p&gt;
&lt;p&gt;连接成功后手机将会显示:&lt;code&gt;已连接到无线调试&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;USB调试&lt;/h4&gt;
&lt;p&gt;有线连接手机后打开&lt;code&gt;设置&lt;/code&gt;→&lt;code&gt;更多设置&lt;/code&gt;→&lt;code&gt;开发者选项&lt;/code&gt;→&lt;code&gt;USB调试&lt;/code&gt;即可&lt;/p&gt;
&lt;h4&gt;远程调试&lt;/h4&gt;
&lt;p&gt;手机&lt;code&gt;Chrome&lt;/code&gt;打开任意网页&lt;/p&gt;
&lt;p&gt;PC在Chrome中打开&lt;code&gt;chrome://inspect/#devices&lt;/code&gt;将可以看到手机中打开的网页&lt;/p&gt;
&lt;p&gt;点击&lt;code&gt;Inspect&lt;/code&gt;即可看到手机端的开发者工具;若开发者工具白屏,则可点击&lt;code&gt;inspect fallback&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/chrome_inspect_devices.png&quot; alt=&quot;inspect devices&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;/h3&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/devtools/remote-debugging/&quot;&gt;Remote debug Android devices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://remysharp.com/2016/12/17/chrome-remote-debugging-over-wifi&quot;&gt;Chrome remote debugging over wifi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1Ey4y147Bu/&quot;&gt;MIUI如何无线调试安卓应用&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>使用clash代理SSR的内网请求</title><link>https://blog.wangshuai.app/2023-01-14-%E4%BD%BF%E7%94%A8clash%E4%BB%A3%E7%90%86SSR%E7%9A%84%E5%86%85%E7%BD%91%E8%AF%B7%E6%B1%82/</link><guid isPermaLink="true">https://blog.wangshuai.app/2023-01-14-%E4%BD%BF%E7%94%A8clash%E4%BB%A3%E7%90%86SSR%E7%9A%84%E5%86%85%E7%BD%91%E8%AF%B7%E6%B1%82/</guid><pubDate>Sat, 14 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://zh.m.wikipedia.org/zh-hans/Shadowsocks#ShadowsocksR&quot;&gt;SSR&lt;/a&gt;是非常好网络代理协议,通过它我们可以用服务端的网络访问特定资源，我们一般都用它来科学上网；也就是上公网&lt;/p&gt;
&lt;p&gt;但它其实也能访问服务端内网,只是需要做一些设置.&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;这里我以&lt;code&gt;Clash for Windows&lt;/code&gt;为例介绍连接方法&lt;/p&gt;
&lt;p&gt;SSR服务端可以使用&lt;a href=&quot;https://github.com/winterssy/SSR-Docker&quot;&gt;SSR-Docker&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在安装好服务后本地先使用SSR(ShadowsocksR)连接测试以确定配置无误&lt;/p&gt;
&lt;p&gt;连接成功后可在&lt;code&gt;docker logs&lt;/code&gt;中看到连接请求&lt;/p&gt;
&lt;p&gt;Clash支持SSR的连接方式,只是不能直接导入SSR的节点;&lt;/p&gt;
&lt;p&gt;这里可以使用配置转换工具,如:&lt;a href=&quot;https://acl4ssr-sub.github.io/&quot;&gt;ACL4SSR 在线订阅转换&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;复制配置好的SSR链接(&lt;code&gt;ssr://&lt;/code&gt;)到订阅链接中,点击&lt;code&gt;生成订阅链接&lt;/code&gt;,然后把这个链接导入到&lt;code&gt;Clash&lt;/code&gt;中&lt;/p&gt;
&lt;p&gt;这时候尝试输入服务端内网的一个服务地址,发现无法访问;查看Clash生成的日志(点击Clash左下角的&lt;code&gt;Connected&lt;/code&gt;可以打开日志目录)&lt;/p&gt;
&lt;p&gt;会发现没有触发代理,这是因为默认Clash会忽略本地地址的请求&lt;/p&gt;
&lt;p&gt;因此需要调整几处配置&lt;/p&gt;
&lt;p&gt;订阅导入成功后可以在本地看到yml文件(或者也可以直接在&lt;code&gt;Profiles&lt;/code&gt;中&lt;code&gt;yml&lt;/code&gt;中右键&lt;code&gt;Edit&lt;/code&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.在&lt;code&gt;rules&lt;/code&gt;中添加需要代理的网段或者IP地址&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;IP-CIDR,192.168.1.1/32,🔰 节点选择&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/clash_local_rules.png&quot; alt=&quot;image-20230115221349239&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;然后在&lt;code&gt;Settings&lt;/code&gt;→&lt;code&gt;System Proxy&lt;/code&gt;→&lt;code&gt;Bypass Domain/IpNet&lt;/code&gt;中移除&lt;code&gt;192.168.*&lt;/code&gt;的配置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(或者改成本地的Ip段,因此这里就要求本地的网段和服务的网段不同;这样可以方便配置)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/clash_local_bypass.png&quot; alt=&quot;clash&quot; /&gt;&lt;/p&gt;
&lt;p&gt;调整后就可以访问了.&lt;/p&gt;
&lt;h3&gt;扩展&lt;/h3&gt;
&lt;p&gt;这样配置后切换到对应的&lt;code&gt;Proxy&lt;/code&gt;后当然可以访问服务器的内网,但假如本身有Google的需求每次访问内网的时候还要切换代理;这里可以使用&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Parsers&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在你的&lt;code&gt;Proxy&lt;/code&gt;中右键&lt;code&gt;Parsers&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;然后输入对应的配置项&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;parsers: # array
  - url: https://xxx.com #Google代理订阅地址 不知道的话可以从配置的Settings中查看
    yaml:
      prepend-rules:
        - IP-CIDR,192.168.1.0/24,🔰 节点选择
        - DOMAIN-SUFFIX,xxx.cn,🔰 节点选择
      append-proxies:
        - {...}#添加的代理配置
      append-proxy-groups:
        - name: 🔰 节点选择
          type: select
          proxies:
            - XX
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后再右键&lt;code&gt;yml&lt;/code&gt;配置选择&lt;code&gt;Update&lt;/code&gt;更新完后可以在&lt;code&gt;yml&lt;/code&gt;配置的&lt;code&gt;Rules&lt;/code&gt;中看到自动修改的配置&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.cfw.lbyczf.com/contents/parser.html&quot;&gt;Clash 配置文件预处理&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>.Net自定义密码验证</title><link>https://blog.wangshuai.app/2023-01-12-net%E8%87%AA%E5%AE%9A%E4%B9%89%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81/</link><guid isPermaLink="true">https://blog.wangshuai.app/2023-01-12-net%E8%87%AA%E5%AE%9A%E4%B9%89%E5%AF%86%E7%A0%81%E9%AA%8C%E8%AF%81/</guid><pubDate>Thu, 12 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;.Net Core可以很方便的集成EF和Identity;假如你的系统已经使用了Token验证,这在内部系统中很好的做认证和授权&lt;/p&gt;
&lt;p&gt;那要是还需要单独给一个API提供外部验证呢?&lt;/p&gt;
&lt;p&gt;比如你的接口需要提供给第三方平台,而你的系统又没做开放平台的功能本文就可参考一二.&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;用户名密码配置于&lt;code&gt;appsettings.json&lt;/code&gt;文件中&lt;/p&gt;
&lt;p&gt;假如有管理第三方API的功能,也可以存表里&lt;/p&gt;
&lt;p&gt;代码如下:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;        [ProducesResponseType(typeof(Response&amp;lt;string&amp;gt;), 200)]
        [AllowAnonymous]
        public async Task&amp;lt;IActionResult&amp;gt; TestAsync(string parm1, int param2)
        {
            //HttpContext.
            //HttpContext.Request.Headers.Values

            var headStr = Request.Headers[&quot;Authorization&quot;];
            if (string.IsNullOrEmpty(headStr))
            {
                throw new Exception( &quot;请填写用户名和密码&quot;);
            }
            var header = AuthenticationHeaderValue.Parse(headStr);
            if (header == null || header.Parameter == null)
                throw new Exception( &quot;请填写用户名和密码&quot;);
            if (header.Scheme != &quot;Basic&quot;) throw new Exception(&quot;仅支持基础身份验证&quot;);

            Encoding encoding = Encoding.GetEncoding(&quot;iso-8859-1&quot;);
            string usernamePassword = encoding.GetString(Convert.FromBase64String(header.Parameter));

            int seperatorIndex = usernamePassword.IndexOf(&apos;:&apos;);

            var username = usernamePassword.Substring(0, seperatorIndex);
            var password = usernamePassword.Substring(seperatorIndex + 1);

            if (_options.Password != password || _options.UserName != username)
            {
                throw new Exception(&quot;用户名/密码错误&quot;);
            }
            //你的代码
		}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调用非常简单:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; var client = new HttpClient();

 //请求头部 base64加密
 var bytes = Encoding.UTF8.GetBytes(
     $&quot;{_option.UserName}:{_option.Password}&quot;);
 client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(&quot;Basic&quot;, Convert.ToBase64String(bytes));
 var msg = await client
     .PostAsync(
         _option.BaseUrl + $&quot;/api/Test?param1={param1}&amp;amp;param2={param2}&quot;, null);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在Postman测试时&lt;code&gt;Authorization&lt;/code&gt;选择 &lt;code&gt;Basic Auth&lt;/code&gt;即可&lt;/p&gt;
</content:encoded></item><item><title>c#返回风力等级</title><link>https://blog.wangshuai.app/2023-01-09-net%E8%BF%94%E5%9B%9E%E9%A3%8E%E5%8A%9B%E7%AD%89%E7%BA%A7/</link><guid isPermaLink="true">https://blog.wangshuai.app/2023-01-09-net%E8%BF%94%E5%9B%9E%E9%A3%8E%E5%8A%9B%E7%AD%89%E7%BA%A7/</guid><pubDate>Mon, 09 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近对接了风向传感器，需要根据风速计算风力等级&lt;/p&gt;
&lt;p&gt;这个问题其实就是数值分段&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;风力等级取自百度百科：&lt;a href=&quot;https://baike.baidu.com/item/%E9%A3%8E%E5%8A%9B%E7%AD%89%E7%BA%A7/8477486&quot;&gt;风力等级&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;代码如下:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; /// &amp;lt;summary&amp;gt;
    /// 风力等级
    /// &amp;lt;/summary&amp;gt;
    public static class WindPower
    {
        /// &amp;lt;summary&amp;gt;
        /// 风力等级定义 :https://baike.baidu.com/item/%E9%A3%8E%E5%8A%9B%E7%AD%89%E7%BA%A7/8477486
        /// &amp;lt;/summary&amp;gt;
        public static List&amp;lt;PowerRange&amp;gt; PowerDefined = new List&amp;lt;PowerRange&amp;gt;()
        {
            new PowerRange(0.0, 0.3),
            new PowerRange(0.3, 1.6),
            new PowerRange(1.6, 3.4),
            new PowerRange(3.4, 5.5),
            new PowerRange(5.5, 8.0),
            new PowerRange(8.0, 10.8),
            new PowerRange(10.8, 13.9),
            new PowerRange(13.9, 17.2),
            new PowerRange(17.2, 20.8),
            new PowerRange(20.8, 24.5),
            new PowerRange(24.5, 28.5),
            new PowerRange(28.5, 32.7),
            new PowerRange(32.7, 37.0),
            new PowerRange(37.0, 41.5),
            new PowerRange(41.5, 46.2),
            new PowerRange(46.2, 51.0),
            new PowerRange(51.0, 56.1),
            new PowerRange(56.1, 61.3),
            new PowerRange(61.3, -1),
            new PowerRange(69.4, -1)
        };
        /// &amp;lt;summary&amp;gt;
        /// 返回指定风速(单位m/s)的风力等级
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;param name=&quot;speed&quot;&amp;gt;&amp;lt;/param&amp;gt;
        /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
        public static int GetWindPower(double speed)
        {
            return GetIndexInRange(PowerDefined, speed);
        }
        /// &amp;lt;summary&amp;gt;
        /// 从 指定的范围中返回找到的序号
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;param name=&quot;ranges&quot;&amp;gt;&amp;lt;/param&amp;gt;
        /// &amp;lt;param name=&quot;value&quot;&amp;gt;&amp;lt;/param&amp;gt;
        /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
        public static int GetIndexInRange(IList&amp;lt;PowerRange&amp;gt; ranges, double value)
        {
            int min = 0;
            int max = ranges.Count - 1;

            while (min &amp;lt;= max)
            {
                int mid = (min + max) / 2;
                double comparison = ranges[mid].CompareTo(value);
                if (comparison == 0)
                {
                    return mid;
                }

                if (comparison &amp;lt; 0)
                {
                    min = mid + 1;
                }
                else if (comparison &amp;gt; 0)
                {
                    max = mid - 1;
                }
            }

            return min;
        }
    }

    public class PowerRange : IComparable&amp;lt;PowerRange&amp;gt;
    {
        private readonly double bottom; // Add properties for these if you want
        private readonly double top;

        public PowerRange(double bottom, double top)
        {
            this.bottom = bottom;
            this.top = top;
        }

        public int CompareTo(PowerRange other)
        {
            if (bottom &amp;lt; other.bottom &amp;amp;&amp;amp; top &amp;lt; other.top)
            {
                return -1;
            }
            if (bottom &amp;gt; other.bottom &amp;amp;&amp;amp; top &amp;gt; other.top)
            {
                return 1;
            }
            if (bottom == other.bottom &amp;amp;&amp;amp; top == other.top)
            {
                return 0;
            }
            throw new ArgumentException(&quot;Incomparable values (overlapping)&quot;);
        }

        /// &amp;lt;summary&amp;gt;
        /// Returns 0 if value is in the specified range;
        /// less than 0 if value is above the range;
        /// greater than 0 if value is below the range.
        /// &amp;lt;/summary&amp;gt;
        public int CompareTo(double value)
        {
            if (value &amp;lt; bottom)
            {
                return 1;
            }
            if (value &amp;gt; top)
            {
                return -1;
            }
            return 0;
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调用非常简单:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;WindPower.GetWindPower(Convert.ToDouble(windSpeed))&lt;/code&gt;&lt;/p&gt;
</content:encoded></item><item><title>win11的输入法频繁自动切换中英文的问题</title><link>https://blog.wangshuai.app/2022-11-30-win11%E7%9A%84%E8%BE%93%E5%85%A5%E6%B3%95%E9%A2%91%E7%B9%81%E8%87%AA%E5%8A%A8%E5%88%87%E6%8D%A2%E4%B8%AD%E8%8B%B1%E6%96%87%E7%9A%84%E9%97%AE%E9%A2%98/</link><guid isPermaLink="true">https://blog.wangshuai.app/2022-11-30-win11%E7%9A%84%E8%BE%93%E5%85%A5%E6%B3%95%E9%A2%91%E7%B9%81%E8%87%AA%E5%8A%A8%E5%88%87%E6%8D%A2%E4%B8%AD%E8%8B%B1%E6%96%87%E7%9A%84%E9%97%AE%E9%A2%98/</guid><pubDate>Wed, 30 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近在家办公的时候远程公司电脑遇到了输入法重复切换的问题:&lt;/p&gt;
&lt;p&gt;&amp;lt;video src=&quot;/video/win11_tim_mstsc.mp4&quot; style=&quot;width: 800px&quot;&amp;gt;&amp;lt;/video&amp;gt;&lt;/p&gt;
&lt;p&gt;问题很好复现:&lt;/p&gt;
&lt;p&gt;1.打开&lt;code&gt;tim&lt;/code&gt;和远程桌面&lt;/p&gt;
&lt;p&gt;2.取消远程桌面的全屏,使用&lt;code&gt;ctrl&lt;/code&gt;+&lt;code&gt;alt&lt;/code&gt;+&lt;code&gt;A&lt;/code&gt;截图&lt;/p&gt;
&lt;p&gt;3.出现如图所示问题&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;2023年2月20日 更新:&lt;/p&gt;
&lt;p&gt;经测试,无需关闭&lt;code&gt;wsl&lt;/code&gt;;在&lt;code&gt;tim&lt;/code&gt;中使用&lt;code&gt;ctrl&lt;/code&gt;+&lt;code&gt;c&lt;/code&gt;复制任意文本覆盖原剪切板内容即可,解决原理尚且是个迷&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;一开始怀疑是输入法问题:&lt;/p&gt;
&lt;p&gt;卸载后无果&lt;/p&gt;
&lt;p&gt;后面怀疑是远程桌面(remote desktop connection) mstsc 的问题(这里点击输入法会出现远程桌面未响应的情况,此时确实会暂停自动切换的现象,但只是指标不治本)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/tim_mstsc_ime_no_response.png&quot; alt=&quot;tim_mstsc_ime_no_response&quot; /&gt;&lt;/p&gt;
&lt;p&gt;关闭远程后还是有这个问题&lt;/p&gt;
&lt;p&gt;经多次测试&lt;/p&gt;
&lt;p&gt;每次出现这种情况的时候我CPU的风扇就会狂转刷存在感,于是我打开任务管理器查看当前CPU占用&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ime_wsl_error.png&quot; alt=&quot;ime_wsl_error&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到是wsl导致的(PS:上图并不是出问题时的截图,出现问题时CPU占用30%左右,wsl占用10%,此处只为说明方法)&lt;/p&gt;
&lt;p&gt;于是在控制台中执行&lt;code&gt;wsl --shutdown&lt;/code&gt;命令关闭linux子系统,终于解决了问题&lt;/p&gt;
&lt;p&gt;(PS:如果因为这个bug导致不能使用键盘输入,可以使用输入法旁边的软键盘用鼠标输入)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/windows_soft_keyboard.png&quot; alt=&quot;soft keyboard&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wsl&lt;/code&gt;是windows中的linux子系统,如果你的电脑使用了&lt;code&gt;docker&lt;/code&gt;或者其它的虚拟机系统,可能会遇到问题;此时重启&lt;code&gt;docker desktop&lt;/code&gt; &lt;code&gt;wsl&lt;/code&gt;就会重新启动了&lt;/p&gt;
</content:encoded></item><item><title>a free ramdisk tool--软媒内存盘</title><link>https://blog.wangshuai.app/2022-10-26-a-free-ramdisk-tool--%E8%BD%AF%E5%AA%92%E5%86%85%E5%AD%98%E7%9B%98/</link><guid isPermaLink="true">https://blog.wangshuai.app/2022-10-26-a-free-ramdisk-tool--%E8%BD%AF%E5%AA%92%E5%86%85%E5%AD%98%E7%9B%98/</guid><pubDate>Wed, 26 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;Description&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://www.radeonramdisk.com/software_downloads.php&quot;&gt;AMD Radeon™ RAMDisk&lt;/a&gt; is a good ramdisk tool .&lt;/p&gt;
&lt;p&gt;But it only support &lt;code&gt;4096MB&lt;/code&gt; space for free account.&lt;/p&gt;
&lt;p&gt;If you want create a larger disk,you can try &lt;a href=&quot;http://dl.360crx.com/files/pc/2020/ramdisk.zip&quot;&gt;&lt;code&gt;软媒内存盘&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Solution&lt;/h3&gt;
&lt;p&gt;There is no official website for &lt;code&gt;软媒内存盘&lt;/code&gt;,this application created by &lt;a href=&quot;https://mofang.ruanmei.com/&quot;&gt;软媒魔方&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A simple tutorial is like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ramdisk_create.png&quot; alt=&quot;image-20221026173108504&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ramdisk_context_menu.png&quot; alt=&quot;ramdisk_context_menu&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When click &lt;strong&gt;Unload &amp;amp; Remove&lt;/strong&gt; menu,It will show a dialog:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/ramdisk_delete_dialog.png&quot; alt=&quot;delete_dialog&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Syncthing 失去同步 Out of Sync</title><link>https://blog.wangshuai.app/2022-10-22-syncthing-%E5%A4%B1%E5%8E%BB%E5%90%8C%E6%AD%A5-out-of-sync/</link><guid isPermaLink="true">https://blog.wangshuai.app/2022-10-22-syncthing-%E5%A4%B1%E5%8E%BB%E5%90%8C%E6%AD%A5-out-of-sync/</guid><pubDate>Sat, 22 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近刚开始尝试自建文件备份服务,尝试了&lt;a href=&quot;https://syncthing.net/&quot;&gt;Syncthing&lt;/a&gt;;用它来同步手机相册到黑群的外接硬盘上;一天下来体感很不错,于是准备将手机相册备份一份到我的电脑上.&lt;/p&gt;
&lt;p&gt;在同步之后遇到一个错误 &lt;code&gt;Syncthing 失去同步&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/syncthing_out_of_sync.png&quot; alt=&quot;image-20221022154214554&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;这里的问题是同步遇到问题,导致同步不能继续了;&lt;/p&gt;
&lt;p&gt;点击&lt;code&gt;失去同步&lt;/code&gt;就能看到有一个失败的项目,点开即可知道此处为qq创建的某一个文件名不被windows支持&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/syncthing_error_item.png&quot; alt=&quot;image-20221022154610577&quot; /&gt;&lt;/p&gt;
&lt;p&gt;因此只要把这个目录忽略即可.&lt;/p&gt;
&lt;p&gt;点击&lt;code&gt;选项&lt;/code&gt;在&lt;code&gt;忽略模式&lt;/code&gt;中输入有问题的文件夹名称&lt;code&gt;.qqpim_guid&lt;/code&gt;,点击保存即可看到&lt;code&gt;同步完成状态&lt;/code&gt;,并且本地状态显示&lt;code&gt;已由忽略模式缩减&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/syncthing_ignore_folder.png&quot; alt=&quot;image-20221022160533608&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;​	https://docs.syncthing.net/v1.22.0/users/ignoring&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>moonlight 首次连接失败/无效问题</title><link>https://blog.wangshuai.app/2022-10-08-moonlight-%E9%A6%96%E6%AC%A1%E8%BF%9E%E6%8E%A5%E5%A4%B1%E8%B4%A5%E6%97%A0%E6%95%88%E9%97%AE%E9%A2%98/</link><guid isPermaLink="true">https://blog.wangshuai.app/2022-10-08-moonlight-%E9%A6%96%E6%AC%A1%E8%BF%9E%E6%8E%A5%E5%A4%B1%E8%B4%A5%E6%97%A0%E6%95%88%E9%97%AE%E9%A2%98/</guid><pubDate>Sat, 08 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;moonlight 串流自己PC玩游戏确实感觉非常舒适,这让ipad的功能更上一个台阶;但我之前却经常遇到首次连接问题。&lt;/p&gt;
&lt;p&gt;表现为，输入验证码后moonlight无任何变化（锁还在）&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;h4&gt;1.从贴吧的帖子可知，降级GeForce Experience 的版本到&lt;a href=&quot;https://us.download.nvidia.cn/GFE/GFEClient/3.22.0.32/GeForce_Experience_v3.22.0.32.exe&quot;&gt;v3.22&lt;/a&gt;可以连接，只要连接成功后可以再升级GeForce Experience&lt;/h4&gt;
&lt;p&gt;但每次连接新设备需要重新卸载安装再升级还是太麻烦了,通过官方讨论组:&lt;a href=&quot;http://moonlight-stream.org/discord&quot;&gt;discord&lt;/a&gt;我找到了另一个解决方法&lt;/p&gt;
&lt;h4&gt;2.勾选控制面板→区域设置中的Beta版:使用Unicode UTF-8提供全球语言支持&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/img/moonlingt_region.png&quot; alt=&quot;image-20221010093738011&quot; /&gt;&lt;/p&gt;
&lt;p&gt;首次连接失败的问题就在于,我的windows是用中文昵称登陆的,而新版本的GeForce Experience在登陆时会使用用户路径,所以登陆失败&lt;/p&gt;
&lt;p&gt;详情可查看&lt;a href=&quot;https://github.com/moonlight-stream/nvidia-gamestream-issues/issues/30#issuecomment-1172924002&quot;&gt;Github issue #30&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/moonlight-stream/nvidia-gamestream-issues/issues/30#issuecomment-1172924002&quot;&gt;&quot;PC not paired&quot; issue with non-ASCII character in username #30&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>记一次移动硬盘掉盘后的数据拯救之旅</title><link>https://blog.wangshuai.app/2022-10-06-%E8%AE%B0%E4%B8%80%E6%AC%A1%E7%A7%BB%E5%8A%A8%E7%A1%AC%E7%9B%98%E6%8E%89%E7%9B%98%E5%90%8E%E7%9A%84%E6%95%B0%E6%8D%AE%E6%8B%AF%E6%95%91%E4%B9%8B%E6%97%85/</link><guid isPermaLink="true">https://blog.wangshuai.app/2022-10-06-%E8%AE%B0%E4%B8%80%E6%AC%A1%E7%A7%BB%E5%8A%A8%E7%A1%AC%E7%9B%98%E6%8E%89%E7%9B%98%E5%90%8E%E7%9A%84%E6%95%B0%E6%8D%AE%E6%8B%AF%E6%95%91%E4%B9%8B%E6%97%85/</guid><pubDate>Thu, 06 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;刚开始工作的时候，为了方便，买了1T西数移动硬盘做为数据存储；&lt;/p&gt;
&lt;p&gt;才两年空间就不够了，于是又买了2T的希捷移动硬盘；新的硬盘用了快4年。&lt;/p&gt;
&lt;p&gt;最近开发的时候发现硬盘的目录下会有路径无法访问的问题，工作时间又没空管，恰巧，前两天用M3U8-Downloader下载了几个视频的时候硬盘掉盘了（我不知道掉盘是什么意思，但我这种情况我称它为掉盘，就是硬盘不见了，只有重新插拔才能重新识别，在读某些数据的时候就会掉盘）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这里提醒一下各位，只要在日常使用中发现有路径无法访问问题；就要立马停止硬盘写入并做全盘备份防止数据丢失！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;硬盘掉盘这个问题，我在网上并没有找到多少非常有效的处理指南；这里只写出我这一个多月来的数据拯救经历：&lt;/p&gt;
&lt;p&gt;一开始有文章说可以用&lt;strong&gt;chdsk&lt;/strong&gt;命令来修复硬盘,但我担心损坏数据决定先全盘备份&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;根据之前装系统接触到的工具来看我先尝试了&lt;a href=&quot;https://www.disktool.cn/backup/backup-software.html&quot;&gt;傲梅轻松备份&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用了傲梅轻松备份的磁盘克隆功能，但我低估了磁盘克隆的速度；再加上傲梅轻松备份没有进度提示；所以到一半的时候我就手动停止了&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;傲梅没有明显的进展，我通过网上的&lt;a href=&quot;https://www.disktool.cn/backup/backup-software.html&quot;&gt;文章&lt;/a&gt;又尝试了另一款工具：&lt;a href=&quot;https://www.diskgenius.cn/&quot;&gt;diskgenius&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;我首先使用了全盘扇区复制功能，才两个小时就遇到了之前的问题：掉盘&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://picx.zhimg.com/80/v2-3dbaa1399409b6e6d23ac4fae8d80392_720w.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;根据提示我重新尝试了好几次（每次重试都没有继续，而是从0开始所以重试没有太大的用），都是掉盘的问题；我怀疑我的硬盘有坏道。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;于是做了&lt;strong&gt;坏道检测与修复&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最后花了5个小时查出来有16000+的柱面损坏(坏道检测的时候非常慢,有的一个坏块都要几分钟,这让我对扇区复制有了一个底)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.zhimg.com/80/v2-4a91decaab3f29f43a834d4221463efa_720w.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;后面我又多次尝试扇区复制功能，分别勾选了&lt;strong&gt;逆向复制&lt;/strong&gt;和&lt;strong&gt;跳过大段扇区&lt;/strong&gt;都会在中途掉盘&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;根据坏道检测的结果来看，是有3大块扇区损坏的；这部分数据我也不打算弄回来了，那怎么能把剩余好的数据复制出来呢？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有没有手动控制复制哪部分扇区的功能呢？查看扇区复制报告，可以看到上面标注了起止扇区和未复制扇区&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://picx.zhimg.com/80/v2-54346403b136eff59955451dd9486d3b_720w.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pica.zhimg.com/80/v2-8797684c0e575583fb79cedf5b6d55fa_720w.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;而扇区复制中也有相应的参数设置，于是我手动设置参数将大段的有效扇区复制了出来(下图只展示了一般的复制情况,实际上每段扇区都可能再次出错而需要再次手动复制)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.zhimg.com/80/v2-5e5dbf8fb17c7d5491730bdb4130d47b_720w.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;只要扇区复制的次数够多,就可以获得几乎完好的拷贝扇区。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;复制完后就可以在diskgenius里面看到拷贝出的扇区，但是在我的电脑里面却看不到这个驱动器，且在磁盘管理器中也看不到这个完整的磁盘（磁盘管理中只能看到凌乱的无法识别的分区，而不像diskgenius中展示的整个分区，所以这里扇区复制出的分区可能是diskgenius分区格式）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;尝试给分区指定新的盘符时遇到错误：句柄无效&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pic1.zhimg.com/80/v2-76cd49ddaa6a0290e6687a9569590b0a_720w.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这时候我去问了，diskgenius的客服，答复是分区损坏，只能复制文件别考虑其它的办法&lt;/p&gt;
&lt;p&gt;&lt;em&gt;假如我只能文件复制，那我还扇区复制做什么呢?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;我认为客服这里可能说的是有几分道理的，我也没有从损坏的分区中恢复出磁盘，我发现diskgenius和磁盘管理中显示的分区不同后，我就把扇区复制出的分区做了&lt;strong&gt;备份分区到镜像文件&lt;/strong&gt;操作，备份成了&lt;strong&gt;pmf&lt;/strong&gt;文件，然后重新格式化HD0后再从&lt;strong&gt;pmf&lt;/strong&gt;还原出这个分区。&lt;/p&gt;
&lt;p&gt;还原后访问硬盘还是有问题：&lt;strong&gt;硬盘无法访问 参数错误&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;(这里的问题我考虑好几天，我的文件明明在硬盘里面；也能复制出来使用，为什么硬盘读不到呢？)&lt;/p&gt;
&lt;p&gt;我思考到这里猜测可能是文件还在，只是文件索引不对；这时候我尝试了一开始的&lt;strong&gt;chdsk&lt;/strong&gt;命令（PS：一开始不这么做而现在做数据修复是因为我在扇区复制完后对完整的坏硬盘拷贝做了分区备份（diskgenius中的功能叫：&lt;strong&gt;备份分区到镜像文件&lt;/strong&gt;），万一失败或出问题还能从镜像文件中还原分区）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;chdsk&lt;/strong&gt;命令删除了一些损毁的数据同时修复了文件错误，这时候再打开我的电脑就能正常访问硬盘了！！&lt;/p&gt;
</content:encoded></item><item><title>博客从Jekyll迁移到Hexo</title><link>https://blog.wangshuai.app/2022-10-05-%E5%8D%9A%E5%AE%A2%E4%BB%8Ejekyll%E8%BF%81%E7%A7%BB%E5%88%B0hexo/</link><guid isPermaLink="true">https://blog.wangshuai.app/2022-10-05-%E5%8D%9A%E5%AE%A2%E4%BB%8Ejekyll%E8%BF%81%E7%A7%BB%E5%88%B0hexo/</guid><pubDate>Wed, 05 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;国庆闲来无事，打算补上之前没写的博客；碍于Jekyll的以下限制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;日常开发用的是Windows，Jekyll的环境略显麻烦&lt;/li&gt;
&lt;li&gt;之前博客放在GitHub Page的，GitHub Page对Jekyll适配不佳，部分插件不支持&lt;/li&gt;
&lt;li&gt;最近有几篇文章想做SEO，但是效果不好&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;打算试试其它博客方案&lt;/p&gt;
&lt;h3&gt;迁移步骤&lt;/h3&gt;
&lt;h4&gt;1.参照&lt;a href=&quot;https://blog.csdn.net/yaorongke/article/details/119089190&quot;&gt;网上&lt;/a&gt;教程搭建基本的Hexo博客环境,本博客使用的是&lt;a href=&quot;https://github.com/fluid-dev/hexo-theme-fluid&quot;&gt;Fluid主题&lt;/a&gt;&lt;/h4&gt;
&lt;h4&gt;2.将原博客的所有文章迁移到Hexo&lt;/h4&gt;
&lt;p&gt;Jekyll和Hexo都是静态博客网站，文章迁移很方便；将**_posts&lt;strong&gt;目录下的所有文章和文章的图片目录&lt;/strong&gt;img&lt;strong&gt;都复制到&lt;/strong&gt;source**路径下&lt;/p&gt;
&lt;p&gt;执行&lt;code&gt;hexo g&lt;/code&gt;、 &lt;code&gt;hexo s&lt;/code&gt;预览网站，发现内容可以一个字都不用动&lt;/p&gt;
&lt;h4&gt;3.发布博客&lt;/h4&gt;
&lt;p&gt;上面的教程展示的是将博客放到&lt;code&gt;GitHub Page&lt;/code&gt;上 ，但我看其它文章建议把博客源码设为私有仓库（防止git的deploy密钥泄漏）;但&lt;strong&gt;免费账号&lt;/strong&gt;的私有仓库不支持GitHub Page,于是考虑&lt;code&gt;cloudflare Page&lt;/code&gt;，&lt;code&gt;cloudflare Page&lt;/code&gt;的架构预设上没有&lt;code&gt;Hexo&lt;/code&gt;的模板,考虑到它们都是静态博客于是使用自定义命令发布了博客:&lt;/p&gt;
&lt;p&gt;构建命令填&lt;code&gt;hexo g&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;构建输出目录填&lt;code&gt;/public&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/cloudflare_pages_custom_command.png&quot; alt=&quot;image-20221006104514958&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;4.其它调整&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Jekyll和Hexo的文章生成方式略有不同，之前文章中是有&lt;code&gt;subtitle&lt;/code&gt;显示在&lt;code&gt;title&lt;/code&gt;之下的，但&lt;a href=&quot;https://github.com/fluid-dev/hexo-theme-fluid&quot;&gt;Fluid主题&lt;/a&gt;默认优先显示&lt;code&gt;subtitle&lt;/code&gt;于是需要把所有文章&lt;code&gt;subtitle&lt;/code&gt;去掉(或者改成&lt;code&gt;excerpt&lt;/code&gt;标签)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;由于设置了最近修改时间的显示,参考此文:&lt;a href=&quot;https://sqiang.net/post/2792803495.html&quot;&gt;解决Hexo文章更新时间问题&lt;/a&gt; 添加&lt;code&gt;updated&lt;/code&gt;字段到&lt;code&gt;Front-matter&lt;/code&gt;中&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;参考&lt;a href=&quot;https://www.zywvvd.com/notes/hexo/theme/fluid/fluid-run-how-long/fluid-run-how-long/&quot;&gt;Fluid -4- 页脚增加网站运行时长&lt;/a&gt;为网站添加运行时间&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;重新在各搜索引擎上提交&lt;code&gt;sitemap.xml&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[&lt;a href=&quot;https://github.com/fluid-dev/hexo-theme-fluid&quot;&gt;Fluid主题&lt;/a&gt;](https://github.com/xiaoyang-sde/reflare)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.zywvvd.com/notes/hexo/theme/fluid/fluid-run-how-long/fluid-run-how-long/&quot;&gt;Fluid -4- 页脚增加网站运行时长&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>任天堂各区最新实时会员价格 任天堂会员最新价格 任天堂会员实时价格</title><link>https://blog.wangshuai.app/2022-09-21-%E4%BB%BB%E5%A4%A9%E5%A0%82%E5%90%84%E5%8C%BA%E6%9C%80%E6%96%B0%E5%AE%9E%E6%97%B6%E4%BC%9A%E5%91%98%E4%BB%B7%E6%A0%BC/</link><guid isPermaLink="true">https://blog.wangshuai.app/2022-09-21-%E4%BB%BB%E5%A4%A9%E5%A0%82%E5%90%84%E5%8C%BA%E6%9C%80%E6%96%B0%E5%AE%9E%E6%97%B6%E4%BC%9A%E5%91%98%E4%BB%B7%E6%A0%BC/</guid><pubDate>Wed, 21 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;说明&lt;/h3&gt;
&lt;p&gt;最近打算开通任天堂在线会员，但是找了一圈互联网却并没有那么一个汇总各区价格的地方；于是花两个月时间做了这样一个任天堂会员价格表。&lt;/p&gt;
&lt;p&gt;已知任天堂会员可以全区通用，所以找到一个最便宜的区开通在线会员是一个省钱的方法。&lt;/p&gt;
&lt;p&gt;下面汇总了已知的常用区的会员价格，原价和汇率每日更新。&lt;/p&gt;
&lt;h3&gt;实时价格(点击后可查看原价)&lt;a href=&quot;https://nsonlineprice.wangshuai.app/&quot;&gt;点击查看&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;640&quot; height=&quot;450&quot; src=&quot;https://nsonlineprice.wangshuai.app/#/home&quot; border=&quot;0&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot; &lt;strong&gt;idm_id&lt;/strong&gt;=&quot;2465793&quot; style=&quot;
width: 100%;frameborder=&quot;no&quot;&amp;gt; &amp;lt;/iframe&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>使用非80端口部署微信公众号后台</title><link>https://blog.wangshuai.app/2022-07-22-%E4%BD%BF%E7%94%A8%E9%9D%9E80%E7%AB%AF%E5%8F%A3%E9%83%A8%E7%BD%B2%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7%E5%90%8E%E5%8F%B0/</link><guid isPermaLink="true">https://blog.wangshuai.app/2022-07-22-%E4%BD%BF%E7%94%A8%E9%9D%9E80%E7%AB%AF%E5%8F%A3%E9%83%A8%E7%BD%B2%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7%E5%90%8E%E5%8F%B0/</guid><pubDate>Fri, 22 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近给家里换了软路由装上了PVE,于是又重拾了之前的公众号.&lt;/p&gt;
&lt;p&gt;在修改微信公众号服务器后台URL地址时,微信提示:&lt;code&gt;请输入合法的URL&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这里之前用的是&lt;code&gt;ngrok&lt;/code&gt;和&lt;code&gt;frp&lt;/code&gt;的内网穿透方案;但我的VPS已经到期了,暂时也没有新入主机的需求&lt;/p&gt;
&lt;p&gt;最终,在网上找到了两个解决方法&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;2023-1-12 14:05:12更新&lt;/p&gt;
&lt;h4&gt;3.cloudflare zero trust  tunnels&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://one.dash.cloudflare.com/&quot;&gt;cloudflare zero trust  tunnels&lt;/a&gt;的支持内网穿透,可以在NAS中部署服务端达到穿透内网的目的&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;打开&lt;code&gt;Access&lt;/code&gt;→&lt;code&gt;Tunnels&lt;/code&gt;→&lt;code&gt;Create a tunnel&lt;/code&gt;根据提示安装对应客户端&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果安装的docker版本,可以什么都不做;直接在&lt;code&gt;Public Hostname&lt;/code&gt;中新建一个服务器内网上的一个地址即可&lt;/li&gt;
&lt;li&gt;如果安装的是windwos版本
&lt;ul&gt;
&lt;li&gt;执行&lt;code&gt;cloudflared tunnel login&lt;/code&gt;登录账号&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h4&gt;1.免费内网穿透服务&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;a href=&quot;https://freefrp.net/&quot;&gt;免费 FRP 内网穿透服务frp.freefrp.net&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根据&lt;a href=&quot;https://freefrp.net/docs&quot;&gt;网站&lt;/a&gt;提示配置&lt;code&gt;frpc.ini&lt;/code&gt;,使用以下docker命令启动&lt;code&gt;frpc&lt;/code&gt;客户端&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker run --restart=always --network host -d -v /mnt/docker/frp/frpc.ini:/etc/frp/frpc.ini --name frpc snowdreamtech/frpc&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;再将自己原本的域名CNAME指向&lt;code&gt;frp.freefrp.net&lt;/code&gt;即可&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用免费visual studio扩展:&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=vs-publisher-1448185.ConveyorbyKeyoti&quot;&gt;Conveyor by Keyoti&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你只是临时debug调试,且使用visual studio作为开发工具,那可以使用&lt;a href=&quot;https://conveyor.cloud/&quot;&gt;conveyor&lt;/a&gt;的服务,安装插件后F5调试即可使用&lt;/p&gt;
&lt;h4&gt;2.使用cloudflare提供的免费serverless服务:cloudflare workers&lt;/h4&gt;
&lt;p&gt;上面的方法是之前就了解到的方案,在知道&lt;a href=&quot;https://github.com/xiaoyang-sde/reflare&quot;&gt;reflare&lt;/a&gt;之后才算发现一条康庄大道&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/xiaoyang-sde/reflare&quot;&gt;reflare&lt;/a&gt;是一个反向代理库,既然是反向代理;那就可以解决端口映射问题&lt;/p&gt;
&lt;p&gt;使用&lt;a href=&quot;https://workers.cloudflare.com/&quot;&gt;Cloudflare Workers®&lt;/a&gt;需要将域名托管到cloudflare上才行&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;参照&lt;a href=&quot;https://github.com/xiaoyang-sde/reflare&quot;&gt;reflare&lt;/a&gt;提供的安装步骤&lt;a href=&quot;https://github.com/xiaoyang-sde/reflare#-installation&quot;&gt;引用reflare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;修改&lt;code&gt;index.ts&lt;/code&gt;中的配置为自己的域名和端口&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;reflare.push({
    path: &apos;/*&apos;,
    upstream: {
      domain: &apos;wechat.aa.com&apos;,      
      port: 7788,
      protocol: &apos;https&apos;,
    }
  });
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;执行&lt;code&gt;npx wrangler publish&lt;/code&gt;命令发布worker&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果需要修改子域名,可以调整&lt;code&gt;wrangler.toml&lt;/code&gt;中的&lt;code&gt;name&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在workers后台中添加自定义触发路由&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意,这里要写&lt;code&gt;/*&lt;/code&gt;,它表示处理所有&lt;code&gt;location&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/cloudflare_workers_router.png&quot; alt=&quot;cloudflare_workers_router&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在DNS设置中添加自定义触发路由到你的worker域名的CNAME记录&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意,这里要点亮橙色云朵,因为直接用xxx.workers.dev访问会有522 Connection timed out error&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/cloudflare_workers_cname.png&quot; alt=&quot;cname&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;访问你设置的dns记录即可看到效果&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xiaoyang-sde/reflare&quot;&gt;https://github.com/xiaoyang-sde/reflare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.cloudflare.com/hc/zh-cn/articles/115003011431-Cloudflare-5XX-%E9%94%99%E8%AF%AF%E6%95%85%E9%9A%9C%E6%8E%92%E9%99%A4&quot;&gt;https://support.cloudflare.com/hc/zh-cn/articles/115003011431-Cloudflare-5XX-%E9%94%99%E8%AF%AF%E6%95%85%E9%9A%9C%E6%8E%92%E9%99%A4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://briteming.blogspot.com/2022/06/cloudflare.html&quot;&gt;https://briteming.blogspot.com/2022/06/cloudflare.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/XIU2/CloudflareSpeedTest/issues/205#issuecomment-1121170176&quot;&gt;https://github.com/XIU2/CloudflareSpeedTest/issues/205#issuecomment-1121170176&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>如何解决devextreme_angular错误TypeError_Cannot_assign_to_read_only_property_name_of_function_data</title><link>https://blog.wangshuai.app/2022-07-03-%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3devextreme-angular%E9%94%99%E8%AF%AFtypeerror/</link><guid isPermaLink="true">https://blog.wangshuai.app/2022-07-03-%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3devextreme-angular%E9%94%99%E8%AF%AFtypeerror/</guid><pubDate>Sun, 03 Jul 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近使用&lt;code&gt;devextreme&lt;/code&gt;动态绑定&lt;code&gt;grid&lt;/code&gt;数据的时候遇到一个异常&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;core.mjs:6494 ERROR TypeError: Cannot assign to read only property &apos;name&apos; of function &apos;data =&amp;gt; {
                    var isCacheUpdated = storeData &amp;amp;&amp;amp; storeData !== this._cachedStoreData;
        ...&amp;lt;omitted&amp;gt;... }&apos;
    at inheritor.setName (ui.grid_core.columns_controller.js:1920:1)
    at createColumn (ui.grid_core.columns_controller.js:133:1)
    at Function.&amp;lt;anonymous&amp;gt; (ui.grid_core.columns_controller.js:169:1)
    at each (iterator.js:25:1)
    at createColumnsFromOptions (ui.grid_core.columns_controller.js:167:25)
    at inheritor.updateSortingGrouping (ui.grid_core.columns_controller.js:1758:1)
    at inheritor.updateColumns (ui.grid_core.columns_controller.js:1682:1)
    at inheritor.applyDataSource (ui.grid_core.columns_controller.js:950:1)
    at inheritor._handleDataChanged (ui.grid_core.data_controller.js:320:29)
    at inheritor._handleDataChanged (ui.grid_core.selection.js:511:1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但本地测试的时候确实正常,只有发布后才有这个错误&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/devextreme_assign_to_readonly_property.png&quot; alt=&quot;image-20220703105032197&quot; /&gt;&lt;/p&gt;
&lt;p&gt;进一步研究之后确定是数据加载过程的问题,本地测试时数据用的静态的;加载瞬间完成,从服务器取数据后绑定列延后了导致报错&lt;/p&gt;
&lt;p&gt;只要给本地测试数据添加延迟就能复现问题&lt;/p&gt;
&lt;p&gt;&lt;code&gt;return of(dataTemp).pipe(delay(3000));&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;解决方法就是动态列的创建放在数据取完之后&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  constructor(private service: Service) {
          this.service.getdata().subscribe(data =&amp;gt; {              
            	    this.columns.push(&amp;lt;Column&amp;gt;{ isBand: true, caption: xxx,columns:listmidcol });//先创建列
                    this.dataGrid.instance.option(&apos;dataSource&apos;,data);//再绑定数据
          }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;p&gt;无&lt;/p&gt;
</content:encoded></item><item><title>如何简单的启动一组dotnet服务,powershell?</title><link>https://blog.wangshuai.app/2022-03-23-%E5%A6%82%E4%BD%95%E7%AE%80%E5%8D%95%E7%9A%84%E5%90%AF%E5%8A%A8%E4%B8%80%E7%BB%84dotnet%E6%9C%8D%E5%8A%A1powershell/</link><guid isPermaLink="true">https://blog.wangshuai.app/2022-03-23-%E5%A6%82%E4%BD%95%E7%AE%80%E5%8D%95%E7%9A%84%E5%90%AF%E5%8A%A8%E4%B8%80%E7%BB%84dotnet%E6%9C%8D%E5%8A%A1powershell/</guid><pubDate>Wed, 23 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;我们知道使用&lt;code&gt;dotnet&lt;/code&gt;命令可以方便的启动c#程序，假如我有一组相互关联的服务，一般在测试的时候可以打开多个visual studio启动新实例&lt;/p&gt;
&lt;p&gt;不过这只在需要调试它们的时候才有必要，一般也只需要分别在&lt;code&gt;bin\debug&lt;/code&gt;中启动命令就行&lt;/p&gt;
&lt;p&gt;每天写代码的时候都去启动这些服务未免有些麻烦,因此有了这篇文章.&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;h5&gt;更简单的方案&lt;/h5&gt;
&lt;p&gt;alter 进程管理&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/thechandanbhagat/alter-pm&quot;&gt;alter-pm&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个不仅可以管理本地;还可以局域网&lt;/p&gt;
&lt;p&gt;启动简单,支持配置&lt;/p&gt;
&lt;h5&gt;复杂的解决方案&lt;/h5&gt;
&lt;p&gt;要用命令行启动这些服务是要解决一些问题的,最关键的是状态检测;因为你直接用&lt;code&gt;dotnet&lt;/code&gt;命令启动的是一个&lt;code&gt;dotnet&lt;/code&gt;命令行窗口&lt;/p&gt;
&lt;p&gt;一旦有多个服务启动了,且每个服务有很多的日志你就不知道哪些服务没启动成功;下面放出我写的一个示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 确保管理员权限运行脚本
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] &quot;Administrator&quot;))  
{  
  $arguments = &quot;&amp;amp; &apos;&quot; +$myinvocation.mycommand.definition + &quot;&apos;&quot;
  Start-Process powershell -Verb runAs -ArgumentList $arguments
  Break
}

$version=&quot;net5.0&quot;
# 相对路径启动dll
$dlls=@(
    &quot;XXXX.XXXX&quot;
    &quot;YYYY.YYYY&quot;
)

# $dlls | ForEach-Object { &amp;amp;()}
for ( $index = 0; $index -lt $dlls.count; $index++)
{
    # $path=&quot; $PSScriptRoot\{0}\bin\Debug\$version\{0}.dll&quot; -f $dlls[$index]
    $dllName=$dlls[$index];

    $path=&quot; $PSScriptRoot\{0}\bin\Debug\$version\&quot; -f $dllName

    $cmd=&apos;cd &apos;+ $path;
    Invoke-Expression $cmd

    $file=$path+$dllName+&quot;.dll&quot;

    cmd.exe /c &quot;start  &quot;&quot;$dllName&quot;&quot; powershell.exe -NoExit -Command  &quot;&quot;dotnet .\$dllName.dll&quot;&quot;&quot;

    &quot;$dllName execed&quot;
}

# 绝对路径启动dll
$exes=@(
    &quot;D:ZZZZ\bin\Debug\$version&quot;,
    &quot;D:XXXX\bin\Debug\$version&quot;
)
for ( $index = 0; $index -lt $exes.count; $index++)
{
    # $path=&quot; $PSScriptRoot\{0}\bin\Debug\$version\{0}.dll&quot; -f $dlls[$index]
    $path=$exes[$index]

    $cmd=&apos;cd &apos;+ $exes[$index];
    Invoke-Expression $cmd

    $name=Get-ChildItem -filter *.exe -name
    $name=[System.IO.Path]::GetFileNameWithoutExtension($name)
    $file=$path+$name

    cmd.exe /c &quot;start  &quot;&quot;$file&quot;&quot; powershell.exe -NoExit -Command  &quot;&quot;dotnet .\$name.dll&quot;&quot;&quot;


    &quot;$file execed&quot;
}

&quot;all server started.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里还推荐使用&lt;a href=&quot;https://datalust.co/seq&quot;&gt;Seq&lt;/a&gt;,方便在启用这些服务后查看日志。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;以上为文章原文：&lt;/p&gt;
&lt;p&gt;下面放出简单的解决方案：&lt;/p&gt;
&lt;h5&gt;简单的解决方案&lt;/h5&gt;
&lt;p&gt;那就是&lt;a href=&quot;https://github.com/dotnet/tye&quot;&gt;Tye&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;使用Tye,只需要:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;安装&lt;code&gt;tye&lt;/code&gt;命令行工具&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;dotnet tool install -g Microsoft.Tye --version &quot;0.11.0-alpha.22111.1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;编写程序启动&lt;code&gt;yaml&lt;/code&gt;配置文件&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;name: servername
services:
- name: identityService
  project: .\InterFace\xxxx.IdentityService.csproj
  build: false #不编译项目直接启动
  bindings:
  - port: 8080
- name: xxxxService
  project: .\InterFace\xxxxService.csproj
  build: false
  bindings:
  - port: 8088  
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;tye run D:\test\tye.yml&lt;/code&gt;或直接使用&lt;code&gt;tye run&lt;/code&gt;启动工具&lt;/li&gt;
&lt;li&gt;打开&lt;code&gt;localhost:8000&lt;/code&gt;查看服务启动情况&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/tye_dashboard.png&quot; alt=&quot;tye dashboard&quot; /&gt;&lt;/p&gt;
&lt;p&gt;现在,你的服务都由&lt;code&gt;tye&lt;/code&gt;帮你接管了&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dotnet/tye/tree/main/docs&quot;&gt;tye documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thecloudblog.net/post/distributed-tracing-in-asp.net-core-with-jaeger-and-tye-part-2-project-tye/&quot;&gt;https://thecloudblog.net/post/distributed-tracing-in-asp.net-core-with-jaeger-and-tye-part-2-project-tye/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/10262231/obtaining-exitcode-using-start-process-and-waitforexit-instead-of-wait&quot;&gt;https://stackoverflow.com/questions/10262231/obtaining-exitcode-using-start-process-and-waitforexit-instead-of-wait&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/3382082/whats-the-nohup-on-windows&quot;&gt;https://stackoverflow.com/questions/3382082/whats-the-nohup-on-windows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/6604089/dynamically-generate-command-line-command-then-invoke-using-powershell&quot;&gt;https://stackoverflow.com/questions/6604089/dynamically-generate-command-line-command-then-invoke-using-powershell&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dotnet/runtime/issues/2688#issuecomment-339577646&quot;&gt;https://github.com/dotnet/runtime/issues/2688#issuecomment-339577646&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>devexpress如何隐藏PropertyGridControl中的编辑器(Editor)</title><link>https://blog.wangshuai.app/2021-09-29-devexpress%E5%A6%82%E4%BD%95%E9%9A%90%E8%97%8Fpropertygridcontrol%E4%B8%AD%E7%9A%84%E7%BC%96%E8%BE%91%E5%99%A8editor/</link><guid isPermaLink="true">https://blog.wangshuai.app/2021-09-29-devexpress%E5%A6%82%E4%BD%95%E9%9A%90%E8%97%8Fpropertygridcontrol%E4%B8%AD%E7%9A%84%E7%BC%96%E8%BE%91%E5%99%A8editor/</guid><pubDate>Wed, 29 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/119885/controls-and-libraries/property-grid&quot;&gt;&lt;code&gt;PropertyGridControl&lt;/code&gt;&lt;/a&gt;有两种视图模式(&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/DevExpress.XtraVerticalGrid.PropertyGridControl.ActiveViewType&quot;&gt;ActiveViewType&lt;/a&gt;)，经典和office&lt;/p&gt;
&lt;p&gt;如果使用office视图（经典视图为Grid展示方式，因此无法隐藏，只能自定义&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.expandableobjectconverter?view=netframework-4.7.2&amp;amp;f1url=%3FappId%3DDev16IDEF1%26l%3DEN-US%26k%3Dk(System.ComponentModel.ExpandableObjectConverter);k(TargetFrameworkMoniker-.NETFramework,Version%253Dv4.7.2);k(DevLang-csharp)%26rd%3Dtrue&quot;&gt;ExpandableObjectConverter&lt;/a&gt;，具体可参考&lt;a href=&quot;https://github.com/wangshuai-007/BlogsSample/blob/daf39bab640c7fdd4b8a5504ed64c4d9b1368f97/DevPropertyGridControlExpandCollection/DevPropertyGridControlExpandCollection/Student.cs#L123&quot;&gt;CourseConverter&lt;/a&gt;）绑定一个前文：&lt;a href=&quot;/2021/09/26/devexpress%E5%A6%82%E4%BD%95%E5%9C%A8PropertyGridControl%E4%B8%AD%E7%9B%B4%E6%8E%A5%E7%BC%96%E8%BE%91%E9%9B%86%E5%90%88%E6%88%90%E5%91%98/&quot;&gt;devexpress如何在PropertyGridControl中直接编辑集合成员&lt;/a&gt;中的对象结果就是这样:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/dev_PropertyGridControl_customCollectionExpand_officeView.png&quot; alt=&quot;office view&quot; /&gt;&lt;/p&gt;
&lt;p&gt;现在,&lt;code&gt;ListCourses&lt;/code&gt;这个对象已经被展开了,所以它的集合编辑器没那么必要,如果我想要把这个&lt;code&gt;Editor&lt;/code&gt;去掉,有什么办法吗?&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;官方并没有针对这个问题提供简单的解决方案,但是它有&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/DevExpress.XtraVerticalGrid.VGridControlBase.CustomDrawRowValueCell&quot;&gt;CustomDrawRowValueCell&lt;/a&gt;事件&lt;/p&gt;
&lt;p&gt;这个事件可以自定义Editor&lt;/p&gt;
&lt;p&gt;所以,问题就比较简单了;只需要不绘制Cell即可达到隐藏控件的目的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private void PropertyGridControl1_CustomDrawRowValueCell(object sender, DevExpress.XtraVerticalGrid.Events.CustomDrawRowValueCellEventArgs e)
{
    if(e.Row.Properties.FieldName== nameof(Student.ListCourses))
    {
        e.Properties.AllowEdit = false;//解决点击编辑框位置时Editor出现的问题
        e.Handled = true;//不执行绘制操作即可隐藏编辑器
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;效果如图:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/dev_PropertyGridControl_hideCollection_officeView.png&quot; alt=&quot;hide collection editor&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/DevExpress.XtraVerticalGrid.VGridControlBase.CustomDrawRowValueCell&quot;&gt;CustomDrawRowValueCell&lt;/a&gt;Event&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>devexpress如何隐藏PropertyGridControl中的行</title><link>https://blog.wangshuai.app/2021-09-27-devexpress%E5%A6%82%E4%BD%95%E9%9A%90%E8%97%8Fpropertygridcontrol%E4%B8%AD%E7%9A%84%E8%A1%8C/</link><guid isPermaLink="true">https://blog.wangshuai.app/2021-09-27-devexpress%E5%A6%82%E4%BD%95%E9%9A%90%E8%97%8Fpropertygridcontrol%E4%B8%AD%E7%9A%84%E8%A1%8C/</guid><pubDate>Mon, 27 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/119885/controls-and-libraries/property-grid&quot;&gt;&lt;code&gt;PropertyGridControl&lt;/code&gt;&lt;/a&gt;实际是&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/2449/controls-and-libraries/vertical-grid&quot;&gt;Vertical Grid&lt;/a&gt;，它的每一行对应的是&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/DevExpress.XtraVerticalGrid.PropertyGridControl.SelectedObject&quot;&gt;PropertyGridControl.SelectedObject&lt;/a&gt;中的属性，如果我想根据值隐藏行应该怎么做呢？&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;假如我现在只需要显示&lt;code&gt;Course.Id=2&lt;/code&gt;的&lt;code&gt;DicClassRoomId_Name&lt;/code&gt;栏位，查看&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/479/controls-and-libraries/vertical-grid/data-layout-records-rows-and-cells/rows/tree-traversal&quot;&gt;Tree Traversal&lt;/a&gt;可知,可以通过&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/DevExpress.XtraVerticalGrid.Rows.VGridRowsIterator.DoOperation(DevExpress.XtraVerticalGrid.Rows.RowOperation)&quot;&gt;VGridRowsIterator.DoOperation&lt;/a&gt; 方法遍历整个树达到自定义行内容的功能。&lt;/p&gt;
&lt;p&gt;为此需要实现一个&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/DevExpress.XtraVerticalGrid.Rows.RowOperation&quot;&gt;RowOperation&lt;/a&gt; (完整代码在文末)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class SetDicRowVisibleRowOperation : DevExpress.XtraVerticalGrid.Rows.RowOperation
{
    public override void Execute(DevExpress.XtraVerticalGrid.Rows.BaseRow row)
    {
        Console.WriteLine(row.Properties.Caption);   
        if (row.Tag is Course course)
        {
            var fieldName = nameof(Student.ListCourses) + &quot;.&quot; + course.IndexName + &quot;.&quot; +
                               nameof(Course.DicClassRoomId_Name);
            if (row.Properties.FieldName == fieldName)
                row.Visible = course.Id == 2;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在遍历之前，还需为Row保存相关信息&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private void Form1_Load(object sender, EventArgs e)
{
    var student = GetTestStudent();
    propertyGridControl1.SelectedObject = student;
    propertyGridControl1.OptionsBehavior.PropertySort = DevExpress.XtraVerticalGrid.PropertySort.NoSort;
    propertyGridControl1.ExpandAllRows();

    //set row Tag info
    student.ListCourses.ForEach(x =&amp;gt;
                {
                    //IndexName set by CourseCollectionPropertyDescriptor constructor
                    var fieldName = nameof(Student.ListCourses) + &quot;.&quot; + x.IndexName + &quot;.&quot; +
                    nameof(Course.DicClassRoomId_Name);
                    var row = propertyGridControl1.GetRowByFieldName(fieldName);
                    row.Tag = x;
                });

    propertyGridControl1.RowsIterator.DoOperation(new SetDicRowVisibleRowOperation());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于上一篇文章&lt;a href=&quot;/2021/09/26/devexpress%E5%A6%82%E4%BD%95%E5%9C%A8PropertyGridControl%E4%B8%AD%E7%9B%B4%E6%8E%A5%E7%BC%96%E8%BE%91%E9%9B%86%E5%90%88%E6%88%90%E5%91%98/&quot;&gt;devexpress如何在PropertyGridControl中直接编辑集合成员&lt;/a&gt;的影响，&lt;code&gt;FieldName&lt;/code&gt;发生了变化，此处根据&lt;code&gt;PropertyDescriptor&lt;/code&gt;的逻辑生成了&lt;code&gt;FieldName&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/dev_PropertyGridControl_hideDicRow.png&quot; alt=&quot;hide dic row&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://supportcenter.devexpress.com/ticket/details/t432094/propertygridcontrol-custom-rowoperation-doesn-t-retrieve-all-properties&quot;&gt;PropertyGridControl custom RowOperation doesn&apos;t retrieve all properties&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/479/controls-and-libraries/vertical-grid/data-layout-records-rows-and-cells/rows/tree-traversal&quot;&gt;Tree Traversal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;示例项目源码：&lt;a href=&quot;https://github.com/wangshuai-007/BlogsSample/tree/HideDicRow/DevPropertyGridControlExpandCollection&quot;&gt;DevPropertyGridControlExpandCollection_HideDicRow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>devexpress如何在PropertyGridControl中直接编辑集合成员</title><link>https://blog.wangshuai.app/2021-09-26-devexpress%E5%A6%82%E4%BD%95%E5%9C%A8propertygridcontrol%E4%B8%AD%E7%9B%B4%E6%8E%A5%E7%BC%96%E8%BE%91%E9%9B%86%E5%90%88%E6%88%90%E5%91%98-/</link><guid isPermaLink="true">https://blog.wangshuai.app/2021-09-26-devexpress%E5%A6%82%E4%BD%95%E5%9C%A8propertygridcontrol%E4%B8%AD%E7%9B%B4%E6%8E%A5%E7%BC%96%E8%BE%91%E9%9B%86%E5%90%88%E6%88%90%E5%91%98-/</guid><pubDate>Sun, 26 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/119885/controls-and-libraries/property-grid&quot;&gt;&lt;code&gt;PropertyGridControl&lt;/code&gt;&lt;/a&gt;默认使用&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.propertygrid&quot;&gt;&lt;code&gt;PropertyGrid&lt;/code&gt;&lt;/a&gt;的集合编辑器，如果你要编辑集合中的项，必须点击按钮打开集合编辑器&lt;/p&gt;
&lt;p&gt;如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/dev_PropertyGridControl_defaultCollectionExpand.png&quot; alt=&quot;default collection editor&quot; /&gt;&lt;/p&gt;
&lt;p&gt;从图中不难发现，List是可以直接编辑&lt;code&gt;Capacity&lt;/code&gt;属性的，那可以直接把&lt;code&gt;Course&lt;/code&gt;对象变成List的成员脱离集合编辑器来编辑吗？&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;答案当然是可以的，通过观察可以发现，&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/119885/controls-and-libraries/property-grid&quot;&gt;&lt;code&gt;PropertyGridControl&lt;/code&gt;&lt;/a&gt;可展开编辑的就是Object，所以只需把集合中的成员映射为List中的Object即可，&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.propertygrid&quot;&gt;&lt;code&gt;PropertyGrid&lt;/code&gt;&lt;/a&gt;已经有相应的解决方案了。&lt;/p&gt;
&lt;p&gt;使用中间类型&lt;code&gt;CourseCollection&lt;/code&gt;然后实现&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.icustomtypedescriptor?view=netframework-4.7.2&amp;amp;f1url=%3FappId%3DDev16IDEF1%26l%3DEN-US%26k%3Dk(System.ComponentModel.ICustomTypeDescriptor);k(TargetFrameworkMoniker-.NETFramework,Version%253Dv4.7.2);k(DevLang-csharp)%26rd%3Dtrue&quot;&gt;&lt;code&gt;ICustomTypeDescriptor&lt;/code&gt;&lt;/a&gt;接口和&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.propertydescriptor?view=netframework-4.7.2&amp;amp;f1url=%3FappId%3DDev16IDEF1%26l%3DEN-US%26k%3Dk(System.ComponentModel.PropertyDescriptor);k(TargetFrameworkMoniker-.NETFramework,Version%253Dv4.7.2);k(DevLang-csharp)%26rd%3Dtrue&quot;&gt;PropertyDescriptor&lt;/a&gt;即可完成此需求，完整代码在文末&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/dev_PropertyGridControl_customCollectionExpand.png&quot; alt=&quot;expand collection as property&quot; /&gt;&lt;/p&gt;
&lt;p&gt;假如你想要自定义类型名称可继承&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.expandableobjectconverter?view=netframework-4.7.2&amp;amp;f1url=%3FappId%3DDev16IDEF1%26l%3DEN-US%26k%3Dk(System.ComponentModel.ExpandableObjectConverter);k(TargetFrameworkMoniker-.NETFramework,Version%253Dv4.7.2);k(DevLang-csharp)%26rd%3Dtrue&quot;&gt;&lt;code&gt;ExpandableObjectConverter&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/dev_PropertyGridControl_customExpandableObjectConverter.png&quot; alt=&quot;custom property name&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.codeproject.com/Articles/4448/Customized-Display-of-Collection-Data-in-a-Propert#_articleTop&quot;&gt;Customized Display of Collection Data in a PropertyGrid&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;示例项目源码：&lt;a href=&quot;https://github.com/wangshuai-007/BlogsSample/tree/master/DevPropertyGridControlExpandCollection&quot;&gt;DevPropertyGridControlExpandCollection&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>devexpress如何使用PropertyGridControl编辑字典(Dictionary)</title><link>https://blog.wangshuai.app/2021-09-24-devexpress%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8propertygridcontrol%E7%BC%96%E8%BE%91%E5%AD%97%E5%85%B8dictionary/</link><guid isPermaLink="true">https://blog.wangshuai.app/2021-09-24-devexpress%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8propertygridcontrol%E7%BC%96%E8%BE%91%E5%AD%97%E5%85%B8dictionary/</guid><pubDate>Fri, 24 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/119885/controls-and-libraries/property-grid&quot;&gt;&lt;code&gt;PropertyGridControl&lt;/code&gt;&lt;/a&gt;默认使用&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.propertygrid&quot;&gt;&lt;code&gt;PropertyGrid&lt;/code&gt;&lt;/a&gt;的集合编辑器，对于一个Dictionary对象是不能添加和删除的&lt;/p&gt;
&lt;p&gt;如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/dev_PropertyGridControl_defaultDictionaryEditor.png&quot; alt=&quot;image-20210924163519947&quot; /&gt;&lt;/p&gt;
&lt;p&gt;要想实现添加/删除功能，需要继承&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.drawing.design.uitypeeditor?view=netframework-4.7.2&amp;amp;f1url=%3FappId%3DDev16IDEF1%26l%3DEN-US%26k%3Dk(System.Drawing.Design.UITypeEditor);k(TargetFrameworkMoniker-.NETFramework,Version%253Dv4.7.2);k(DevLang-csharp)%26rd%3Dtrue&quot;&gt;&lt;code&gt;UITypeEditor&lt;/code&gt;&lt;/a&gt;实现一个自定义编辑器&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;Google之后发现已经有人实现了这一功能：&lt;a href=&quot;https://github.com/TechSmith/GenericDictUiTypeEditor&quot;&gt;GenericDictUiTypeEditor&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;由于&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/119885/controls-and-libraries/property-grid&quot;&gt;&lt;code&gt;PropertyGridControl&lt;/code&gt;&lt;/a&gt;继承自&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.propertygrid&quot;&gt;&lt;code&gt;PropertyGrid&lt;/code&gt;&lt;/a&gt;，因此&lt;a href=&quot;https://docs.devexpress.com/WindowsForms/119885/controls-and-libraries/property-grid&quot;&gt;&lt;code&gt;PropertyGridControl&lt;/code&gt;&lt;/a&gt;的大部分需求可参照&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.propertygrid&quot;&gt;&lt;code&gt;PropertyGrid&lt;/code&gt;&lt;/a&gt;实现&lt;/p&gt;
&lt;p&gt;参考&lt;a href=&quot;https://github.com/TechSmith/GenericDictUiTypeEditor&quot;&gt;Github&lt;/a&gt;为项目添加&lt;a href=&quot;https://www.nuget.org/packages/GenDictEdit/&quot;&gt;Nuget&lt;/a&gt;引用然后使用&lt;code&gt;EditorAttribute(typeof(GenericDictionaryEditor&amp;lt;string,string&amp;gt;), typeof(System.Drawing.Design.UITypeEditor))&lt;/code&gt;即有增删功能&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/dev_PropertyGridControl_GenericDictUiTypeEditor.png&quot; alt=&quot;image-20210924165415825&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/a/13107534/7960551&quot;&gt;Using a Dictionary in a propertygrid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/TechSmith/GenericDictUiTypeEditor&quot;&gt;https://github.com/TechSmith/GenericDictUiTypeEditor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>devexpress使用模板打印报表</title><link>https://blog.wangshuai.app/2021-05-27-devexpress%E4%BD%BF%E7%94%A8%E6%A8%A1%E6%9D%BF%E6%89%93%E5%8D%B0%E6%8A%A5%E8%A1%A8/</link><guid isPermaLink="true">https://blog.wangshuai.app/2021-05-27-devexpress%E4%BD%BF%E7%94%A8%E6%A8%A1%E6%9D%BF%E6%89%93%E5%8D%B0%E6%8A%A5%E8%A1%A8/</guid><pubDate>Thu, 27 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;https://docs.devexpress.com/XtraReports/2666/detailed-guide-to-devexpress-reporting/store-and-distribute-reports/store-report-layouts-and-documents/load-report-layouts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;https://docs.devexpress.com/XtraReports/DevExpress.XtraReports.UI.ReportDesignTool.ShowRibbonDesigner&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;设计器demo地址：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>devexpress使用设计器编辑报表模板</title><link>https://blog.wangshuai.app/2021-04-16-devexpress%E4%BD%BF%E7%94%A8%E8%AE%BE%E8%AE%A1%E5%99%A8%E7%BC%96%E8%BE%91%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF/</link><guid isPermaLink="true">https://blog.wangshuai.app/2021-04-16-devexpress%E4%BD%BF%E7%94%A8%E8%AE%BE%E8%AE%A1%E5%99%A8%E7%BC%96%E8%BE%91%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF/</guid><pubDate>Fri, 16 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;fastreport由于是单独的报表解决方案，因此原生支持报表模板修改，如果遇到改字体颜色这种小的需求，客户可以自己打开设计器编辑报表搞定；而devexpress的报表如果是在visual studio里面创建的话默认为带设计器的cs文件，如果要让客户可以直接修改报表模板，需要额外做一些操作。&lt;/p&gt;
&lt;p&gt;以下示例所用devexpress的版本为18.1&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;h5&gt;1.准备repx模板文件&lt;/h5&gt;
&lt;p&gt;首先需要准备报表模板文件，devexpress的模板文件类型为repx，可以从设计器里面新建或者另存为也可以在visual studio中把cs设计文件导出&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/devexpress_saveCSToRepx.png&quot; alt=&quot;image-20210416094435696&quot; /&gt;&lt;/p&gt;
&lt;p&gt;repx文件其实是一个xml，它保存了界面布局&lt;/p&gt;
&lt;h5&gt;2.准备报表编辑器&lt;/h5&gt;
&lt;p&gt;fastreport在安装程序之后会自动有一个Report Designer.exe的报表编辑器，但是devexpress（版本18.1）却没有这样的设计器，所以还需要新建一个项目来编辑模板（如果你用的旧版如12.1，在安装的DemoCenter目录&lt;code&gt;&quot;C:\Users\Public\Documents\DXperience 12.1 Demos\XtraReports\Bin\ReportDesigner.exe&quot;&lt;/code&gt;会有一个自带的设计器，也可以用这个exe来编辑报表）&lt;/p&gt;
&lt;p&gt;设计器启动代码如下，完整demo见文末&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private string repxPath = &quot;&quot;;
private void Form1_Load(object sender, EventArgs e)
{
    this.Hide();

    var rpt = new XtraReport();
    if (File.Exists(repxPath))
        rpt.LoadLayout(repxPath);
    ReportDesignTool designTool = new ReportDesignTool(rpt);

    // Access the standard or ribbon-based Designer form and its MDI Controller.
    // IDesignForm designForm = designTool.DesignForm;
    XRDesignRibbonForm designForm = designTool.DesignRibbonForm as XRDesignRibbonForm;
    if (designForm != null)
    {
        designForm.OpenReport(rpt);
        if (File.Exists(repxPath))
        {
            var fileInfo = new FileInfo(repxPath);
            designForm.RibbonControl.AutoSaveLayoutToXmlPath = repxPath;
            designForm.Text = fileInfo.Name;
        } 

        designForm.ShowDialog();
    }

    this.Close();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;3.界面效果&lt;/h5&gt;
&lt;p&gt;编译好后打开生成的exe文件，打开之前导出的repx文件，这样就有了报表编辑功能，界面如图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/devexpress_DevReportDesigner_v18.1.4.0.png&quot; alt=&quot;report&quot; /&gt;&lt;/p&gt;
&lt;p&gt;此设计器已发布，下载地址：&lt;a href=&quot;https://github.com/wangshuai-007/BlogsSample/releases/tag/DevReportDesigner_v18.1.4.0&quot;&gt;DevReportDesigner_v18.1.4.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;对于repx文件，&lt;code&gt;右键&lt;/code&gt;→&lt;code&gt;打开方式&lt;/code&gt;→&lt;code&gt;在这台电脑上查找其它应用&lt;/code&gt;→&lt;code&gt;选择DevReportDesigner.exe&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果要直接使用repx文件打印出报表，请参考官方文章：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.devexpress.com/XtraReports/DevExpress.XtraReports.UI.XtraReport.FromFile(System.String-System.Boolean)&quot;&gt;Loads the report definition from the specified REPX file.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;数据绑定方法可查看官方问答：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://supportcenter.devexpress.com/ticket/details/t369657/binding-datatable-to-xrtable-in-xtrareport&quot;&gt;Binding Datatable to XRTable in XtraReport&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.devexpress.com/XtraReports/2666/detailed-guide-to-devexpress-reporting/store-and-distribute-reports/store-report-layouts-and-documents/load-report-layouts&quot;&gt;https://docs.devexpress.com/XtraReports/2666/detailed-guide-to-devexpress-reporting/store-and-distribute-reports/store-report-layouts-and-documents/load-report-layouts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.devexpress.com/XtraReports/DevExpress.XtraReports.UI.ReportDesignTool.ShowRibbonDesigner&quot;&gt;https://docs.devexpress.com/XtraReports/DevExpress.XtraReports.UI.ReportDesignTool.ShowRibbonDesigner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;设计器demo地址：&lt;a href=&quot;https://github.com/wangshuai-007/BlogsSample/tree/master/DevReportDesigner&quot;&gt;https://github.com/wangshuai-007/BlogsSample/tree/master/DevReportDesigner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;设计器下载地址：&lt;a href=&quot;https://github.com/wangshuai-007/BlogsSample/releases/tag/DevReportDesigner_v18.1.4.0&quot;&gt;DevReportDesigner_v18.1.4.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>fastreport每页显示固定行数</title><link>https://blog.wangshuai.app/2021-03-18-fastreport%E6%AF%8F%E9%A1%B5%E6%98%BE%E7%A4%BA%E5%9B%BA%E5%AE%9A%E8%A1%8C%E6%95%B0/</link><guid isPermaLink="true">https://blog.wangshuai.app/2021-03-18-fastreport%E6%AF%8F%E9%A1%B5%E6%98%BE%E7%A4%BA%E5%9B%BA%E5%AE%9A%E8%A1%8C%E6%95%B0/</guid><pubDate>Thu, 18 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;最近遇到客户提的一个需求，他要一页只显示20行明细数据；一番Google之后找到了官方博客介绍相关解决办法:&lt;a href=&quot;https://www.fast-report.com/en/blog/148/show/&quot;&gt;How to display an estimated number of records on a report page&lt;/a&gt;于是我写了下面的代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int counter = 0;
 
 private void Data1_BeforePrint(object sender, EventArgs e)
 {
     if (counter &amp;gt;= 20)
     {
         Engine.StartNewPage();//打印到新的页面
         counter = 0;
     }
     counter++;
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是我预览出来，一页却只有18行；而且从设计器打印和直接打印预览的结果还不一样；&lt;/p&gt;
&lt;p&gt;一番思考之后，我认为此处是&lt;code&gt;Report.DoublePass&lt;/code&gt;设置导致的，在&lt;a href=&quot;https://www.fast-report.com/documentation/UserMan/index.html?report_options.htm&quot;&gt;Report settings&lt;/a&gt;的官方教程中有说明；为解决这个问题，才有了这篇文章&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;由于&lt;code&gt;BeforePrint&lt;/code&gt;事件的问题，我查看官方文档，找到一个&lt;code&gt;Report.Engine.RowNo&lt;/code&gt;属性，于是我这样写&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (Report.Engine.RowNo%20==0)
    Engine.StartNewPage();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试发现还是不行，每页数量依旧不对&lt;/p&gt;
&lt;p&gt;多次预览调整后找到正确的条件判断：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (Report.Engine.RowNo&amp;gt;20&amp;amp;&amp;amp; Report.Engine.RowNo%20==1)
    Engine.StartNewPage();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;参考资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.fast-report.com/en/blog/148/show/&quot;&gt;How to display an estimated number of records on a report page&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://fastreports.github.io/FastReport.Documentation/ClassReference/api/FastReport.Engine.ReportEngine.html&quot;&gt;FastReport.Documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>使用Zxing.Net 创建透明背景艺术二维码</title><link>https://blog.wangshuai.app/2020-08-05-%E4%BD%BF%E7%94%A8zxingnet-%E5%88%9B%E5%BB%BA%E9%80%8F%E6%98%8E%E8%83%8C%E6%99%AF%E8%89%BA%E6%9C%AF%E4%BA%8C%E7%BB%B4%E7%A0%81/</link><guid isPermaLink="true">https://blog.wangshuai.app/2020-08-05-%E4%BD%BF%E7%94%A8zxingnet-%E5%88%9B%E5%BB%BA%E9%80%8F%E6%98%8E%E8%83%8C%E6%99%AF%E8%89%BA%E6%9C%AF%E4%BA%8C%E7%BB%B4%E7%A0%81/</guid><pubDate>Wed, 05 Aug 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;用过微信的都知道，里面有一个个性化二维码名片的功能；&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/wechat_qrcode_businesscard.png&quot; alt=&quot;image-20200805152257211&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最近接到需求要做一个类似的好看一点的二维码，微信里面不支持手动添加背景图片，估计是做的模板；但是配色不是我擅长的事，要做很多模板出来让用户选是不现实的，我期望有自动合成颜色的方法，一番搜索之后找到一个商用产品&lt;a href=&quot;https://qrcode.studio/&quot;&gt;qrcode.studio&lt;/a&gt;，里面有一个透明背景的功能，可以实现我的要求。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/qrcode_studio_link_zhidao.png&quot; alt=&quot;image-20200805154119073&quot; /&gt;&lt;/p&gt;
&lt;p&gt;于是参照实现了一个可以融合背景图片的透明背景图功能&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;在visual studio中打开从&lt;a href=&quot;https://qrcode.studio/&quot;&gt;qrcode.studio&lt;/a&gt;下载下来的png二维码文件，放大之后可以看到module的形状&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/qrcode_studio_link_zoom_moduleShape.png&quot; alt=&quot;image-20200805155633543&quot; /&gt;&lt;/p&gt;
&lt;p&gt;因此，在黑的地方画一个点，周围再自动做颜色渐变就可以了；这里的径向渐变可以使用&lt;a href=&quot;https://docs.microsoft.com/zh-cn/dotnet/api/system.windows.media.radialgradientbrush?view=netcore-3.1&quot;&gt;RadialGradientBrush&lt;/a&gt;，但是我是Winform，没有这个画刷；于是我参考&lt;a href=&quot;https://stackoverflow.com/questions/3519835/c-sharp-radial-gradient-brush-effect-in-gdi-and-winforms&quot;&gt;c# radial gradient brush effect in GDI and winforms&lt;/a&gt;使用了&lt;code&gt;PathGradientBrush&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;主要的代码在这里，完整demo在最后&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public override Bitmap Render(BitMatrix matrix, BarcodeFormat format, string content, EncodingOptions options)
{
    int width = matrix.Width;
    int height = matrix.Height;
    Foreground = Color.Black;
    bool hasBackImage = false;
    var qrCode = Encoder.encode(content, ErrorCorrectionLevel.L,options.Hints);

    var qrCodeMatrix = qrCode.Matrix;
    Console.WriteLine(&quot;-----------------------------&quot;);
    foreach (var point in qrCodeMatrix.Array)
    {
        Console.WriteLine(string.Join(string.Empty, point.Select(s =&amp;gt;
        {
            if (s == 0) return &quot; &quot;;
            return s.ToString();
        })));
    }

    var det=new Detector(matrix);
    var detPoints = det.detect().Points;


    var backgroundBrush = new LinearGradientBrush(
       new Rectangle(0, 0, width, height), BackgroundGradientColor, BackgroundGradientColor, LinearGradientMode.Vertical);
    var foregroundBrush = new LinearGradientBrush(
       new Rectangle(0, 0, width, height), ForegroundGradientColor, ForegroundGradientColor, LinearGradientMode.ForwardDia

    var bmp = new Bitmap(width,height);
    var gg = Graphics.FromImage(bmp);
    if (File.Exists(BackImageFullName))
    {
        var backImageTemp=new Bitmap(BackImageFullName);
        bmp= new Bitmap(backImageTemp, new Size(width, height));
        gg=Graphics.FromImage(bmp);
        hasBackImage = true;
    }
    else
    {
        gg.Clear(BackgroundGradientColor);
    }

   
    Point startPoint=new Point(0,0);
    Point endPoint=new Point(0,0);
    bool isStartPoint = false;

    //var foreColor = Color.Black;
    //var backColor = Color.White;
    var foreColor = ForegroundGradientColor;
    var backColor = BackgroundGradientColor;

    if (hasBackImage &amp;amp;&amp;amp; IsMergeBackColor)
    {
        var rateX = bmp.Width / qrCodeMatrix.Width;
        var rateY = bmp.Height / qrCodeMatrix.Height;
        if (rateY &amp;gt; rateX)
        {
            rateY = rateX;
        }
        else
        {
            rateX = rateY;
        }

        //todo boarder效果
        var defaultBorder = 0;
        var largeImage = new Bitmap(bmp, qrCodeMatrix.Width * rateX+defaultBorder, qrCodeMatrix.Height * rateY+defaultBord
        var largeG = Graphics.FromImage(largeImage);
        for (int inputY = 0; inputY &amp;lt; qrCodeMatrix.Height; inputY++)
        {
            for (int inputX = 0; inputX &amp;lt; qrCodeMatrix.Width; inputX++)
            {
                Rectangle foreRectangle = new Rectangle(inputX * rateX+defaultBorder, inputY * rateY+defaultBorder, rateX,

                //定点位使用黑白,其它位置使用PathGradientBrush根据module点的坐标画出图形
                if (IsInDetect(qrCodeMatrix, inputX, inputY))
                {
                    if (qrCodeMatrix[inputX, inputY] == 1)
                    {
                        largeG.FillRectangle(new SolidBrush(foreColor), foreRectangle);
                    }
                    else
                    {
                        largeG.FillRectangle(new SolidBrush(backColor), foreRectangle);
                    }
                }
                else
                {
                    using (var ellipsePath = new GraphicsPath())
                    {

                        ellipsePath.AddEllipse(foreRectangle);
                        var brush = new PathGradientBrush(ellipsePath);

                        var color = bmp.GetPixel(foreRectangle.X, foreRectangle.Y);

                        brush.CenterPoint = new PointF(foreRectangle.Width / 2 + foreRectangle.X,
                            foreRectangle.Height / 2 + foreRectangle.Y);
                        brush.SurroundColors = new[] {color};
                        brush.FocusScales = new PointF(0, 0);
                        if (qrCodeMatrix[inputX, inputY] == 1)
                        {

                            brush.CenterColor = foreColor;
                        }
                        else
                        {
                            brush.CenterColor = backColor;
                        }

                        largeG.FillRectangle(brush, foreRectangle);
                    }
                }
            }
        }

        return largeImage;
    }

    for (int x = 0; x &amp;lt; width - 1; x++)
    {
        for (int y = 0; y &amp;lt; height - 1; y++)
        {
            if (matrix[x, y])
            {
                if (!isStartPoint)
                {
                    isStartPoint = true;
                    startPoint = new Point(x, y);
                    endPoint = new Point(width - x, height - y);
                }
                
                gg.FillRectangle(foregroundBrush, x, y, 1, 1);
            }
            else
            {
                if (x &amp;lt; endPoint.X &amp;amp;&amp;amp; y &amp;lt; endPoint.Y &amp;amp;&amp;amp; x &amp;gt; startPoint.X &amp;amp;&amp;amp; y &amp;gt; startPoint.Y)
                {
                    if (hasBackImage)
                        gg.FillRectangle(backgroundBrush, x, y, 1, 1);

                }
            }
        }
    }



    if (File.Exists(IconFullName))
    {
        var icon = new Bitmap(IconFullName);
        Image circleIcon;
        if (IconShape == IconShape.Round)
        {
            circleIcon = CutCircle(IconFullName, 0, 0, icon.Height);
        }
        else
        {
            circleIcon = icon;
        }
        circleIcon = new Bitmap(circleIcon, new Size(33, 33));
        gg.DrawImage(circleIcon,
            new PointF((startPoint.X + endPoint.X) / 2 - circleIcon.Width / 2,
                (startPoint.Y + endPoint.Y) / 2 - circleIcon.Height / 2));
    }

    if (hasBackImage)
        gg.DrawRectangle(new Pen(Color.White, 5), startPoint.X - 3, startPoint.Y - 3,
            endPoint.X - startPoint.X + 4,
            endPoint.Y - startPoint.Y + 4);

    return bmp;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还是使用林克作为背景，生成的二维码如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/qrcode_studio_link_zhidao_custom.png&quot; alt=&quot;testQR000007&quot; /&gt;&lt;/p&gt;
&lt;p&gt;虽然看起来差了点，但是基本可以实现需求&lt;/p&gt;
&lt;h3&gt;常见问题&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;关于二维码&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;初次接触二维码建议看一下&lt;a href=&quot;https://www.qrcode.com/en/howto/cell.html&quot;&gt;qrcode.com&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如何debug查看生成的图片&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;中断的时候调用&lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.drawing.image.save&quot;&gt;bmp.Save(&quot;123.png“)&lt;/a&gt;方法就行了&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;其它参考资料&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.codeproject.com/Articles/20018/Gradients-made-easy&quot;&gt;https://www.codeproject.com/Articles/20018/Gradients-made-easy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.qrstuff.com/2015/09/09/colour-qr-codes&quot;&gt;https://blog.qrstuff.com/2015/09/09/colour-qr-codes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;完整demo见此仓库：&lt;a href=&quot;https://github.com/wangshuai-007/QRCode&quot;&gt;https://github.com/wangshuai-007/QRCode&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>使用PowerShell查看使用RDP登录到远程服务器的客户端IP</title><link>https://blog.wangshuai.app/2020-06-26-%E4%BD%BF%E7%94%A8powershell%E6%9F%A5%E7%9C%8B%E4%BD%BF%E7%94%A8rdp%E7%99%BB%E5%BD%95%E5%88%B0%E8%BF%9C%E7%A8%8B%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84%E5%AE%A2%E6%88%B7%E7%AB%AFip/</link><guid isPermaLink="true">https://blog.wangshuai.app/2020-06-26-%E4%BD%BF%E7%94%A8powershell%E6%9F%A5%E7%9C%8B%E4%BD%BF%E7%94%A8rdp%E7%99%BB%E5%BD%95%E5%88%B0%E8%BF%9C%E7%A8%8B%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84%E5%AE%A2%E6%88%B7%E7%AB%AFip/</guid><pubDate>Fri, 26 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;TeamViewer是一个非常好的远程支持工具，但是我们公司资金有限，所以只在局域网里面配备了一台正版客户端；当同事需要远程支持的时候再使用局域网RDP（mstsc）连接到那台PC。这个teamviewer有一个限制，就是一台电脑只能一个用户使用，当你其它用户登录的时候再连接就会提示无法连接。基于这个原因，我需要知道是谁登录到这个用户了。&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;参考&lt;a href=&quot;https://serverfault.com/questions/361565/how-can-i-get-the-ip-address-of-a-remote-desktop-client-and-how-can-i-trigger-a&quot;&gt;这篇文章&lt;/a&gt;我编写了一个PowerShell脚本来通知当前登录的IP&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$output= netstat -n | find --% &quot;:3389&quot; | find --% &quot;ESTABLISHED&quot;

##Invoke-RestMethod -Uri &quot;https://sc.ftqq.com/[SCKey].send?text=mstscLogin&amp;amp;desp=$output&quot;
$Uri = &apos;https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=[workWXkey]&apos;

$json2= &quot;{        &quot;&quot;msgtype&quot;&quot;: &quot;&quot;text&quot;&quot;,        &quot;&quot;text&quot;&quot;: {           &quot;&quot;content&quot;&quot;: &quot;&quot;$output&quot;&quot;        }   }&quot;
 $json2
$Result = Invoke-RestMethod -Uri $Uri -Method Post -Body $JSON2 -ContentType &quot;application/json&quot;
$Result
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通知的方式我选择的是微信，如果你常用企业微信，那你可以参考&lt;a href=&quot;https://work.weixin.qq.com/api/doc/90000/90136/91770&quot;&gt;这篇文章&lt;/a&gt;配置群机器人得到通知消息，如果不常用企业微信，那可以用&lt;a href=&quot;http://sc.ftqq.com/3.version&quot;&gt;Server酱&lt;/a&gt;，登录后把脚本中的Key替换成自己的就能收到通知了（微信不能发送空消息，所以你要在远程的电脑上执行脚本才有效果）&lt;/p&gt;
&lt;h3&gt;常见问题&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;脚本的执行方式&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于我登录的用户只有一般的权限，所以我使用任务计划（Task scheduler），创建一个每次连接到用户（虽然我期望的是登录用户执行我的脚本，但其实这里触发器的触发条件应该用连接用户)就执行的计划来执行这个脚本&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;连接到用户后弹出命令行执行窗口&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你选择连接到用户后执行上述的PowerShell脚本，那会弹出一个一闪而过的命令行黑窗，参考&lt;a href=&quot;https://social.technet.microsoft.com/Forums/windows/en-US/24d1b052-b56d-4a34-b39b-602ca84cf4bd/task-scheduler-hidden-powershell-with-no-popup?forum=winserverpowershell&quot;&gt;这个文章&lt;/a&gt;，使用以下VBS脚本执行PowerShell代码，这样就不会弹窗了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;command = &quot;powershell.exe -nologo -command C:\Scripts\YourScript.ps1&quot;
 set shell = CreateObject(&quot;WScript.Shell&quot;)
 shell.Run command,0
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>查看mcr.microsoft.com中所有docker 镜像的Tag</title><link>https://blog.wangshuai.app/2020-04-10-%E6%9F%A5%E7%9C%8Bmcrmicrosoftcom%E4%B8%AD%E6%89%80%E6%9C%89%E7%9A%84tag/</link><guid isPermaLink="true">https://blog.wangshuai.app/2020-04-10-%E6%9F%A5%E7%9C%8Bmcrmicrosoftcom%E4%B8%AD%E6%89%80%E6%9C%89%E7%9A%84tag/</guid><pubDate>Fri, 10 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;参考这个链接:&lt;a href=&quot;https://stackoverflow.com/questions/28320134/how-can-i-list-all-tags-for-a-docker-image-on-a-remote-registry&quot;&gt;how-can-i-list-all-tags-for-a-docker-image-on-a-remote-registry&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;直接访问：~~&lt;a href=&quot;https://mcr.microsoft.com/v2/dotnet/core/runtime/tags/list&quot;&gt;https://mcr.microsoft.com/v2/dotnet/core/runtime/tags/list&lt;/a&gt;~~即可&lt;/p&gt;
&lt;p&gt;参考此issue：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/dotnet/dotnet-docker/issues/2375&quot;&gt;.NET 5.0 repository rename announcement&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;新的URL为：&lt;a href=&quot;https://mcr.microsoft.com/v2/dotnet/runtime/tags/list&quot;&gt;https://mcr.microsoft.com/v2/dotnet/runtime/tags/list&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;其它的&lt;code&gt;sdk&lt;/code&gt;、&lt;code&gt;aspnet&lt;/code&gt;同理&lt;/p&gt;
</content:encoded></item><item><title>单元测试CommandLineUtils中的命令</title><link>https://blog.wangshuai.app/2020-04-6-%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95commandlineutils%E4%B8%AD%E7%9A%84%E5%91%BD%E4%BB%A4/</link><guid isPermaLink="true">https://blog.wangshuai.app/2020-04-6-%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95commandlineutils%E4%B8%AD%E7%9A%84%E5%91%BD%E4%BB%A4/</guid><pubDate>Mon, 06 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;引言&lt;/h3&gt;
&lt;p&gt;使用&lt;a href=&quot;https://github.com/natemcmaster/CommandLineUtils&quot;&gt;CommandLineUtils&lt;/a&gt;可以创建便捷的dotnet tool工具,官方的&lt;a href=&quot;https://github.com/natemcmaster/CommandLineUtils/blob/master/docs/samples&quot;&gt;docs / samples /&lt;/a&gt;中包含有很多示例;但是对于如何运行&lt;code&gt;Command&lt;/code&gt;却没有明示,如果你要单元测试自己创建的命令,要到&lt;a href=&quot;https://github.com/natemcmaster/CommandLineUtils/tree/master/test/CommandLineUtils.Tests&quot;&gt;test&lt;/a&gt;目录下参考官方的单元测试代码,以下列出自己实践的方法.&lt;/p&gt;
&lt;h2&gt;解决办法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;对于不使用依赖注入的&lt;code&gt;Command&lt;/code&gt;,直接使用&lt;code&gt;CommandLineApplication.Execute&amp;lt;Show&amp;gt;(&quot;-a&quot;);&lt;/code&gt;即可,其中&lt;code&gt;Show&lt;/code&gt;为&lt;code&gt;[Command(&quot;show&quot;, Description = &quot;&quot;)]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;对于使用依赖注入的&lt;code&gt;Command&lt;/code&gt;可以参考这个示例&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;var commandLineApplication = new CommandLineApplication&amp;lt;Show&amp;gt;();
var services = new ServiceCollection()
    .AddLogging()
    .AddSingleton(Program.Configuration)
    .AddSingleton(Program.Configuration.Get&amp;lt;APPConfig&amp;gt;())//添加依赖注入
    .BuildServiceProvider();

commandLineApplication.Conventions
    .UseDefaultConventions()
    .UseConstructorInjection(services);

commandLineApplication.Parse();

var result = commandLineApplication.Execute(@&quot;--path=/CUSTDB/1901-0048.rar&quot;, &quot;--taskid=1907-7777&quot;);//执行命令

result.Should().Be(0);//using FluentAssertions
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>fastreport 查询系统变量总页数 Report.GetVariableValue(&quot;TotalPages&quot;)总是返回1</title><link>https://blog.wangshuai.app/2020-03-16-fastreport%E6%9F%A5%E8%AF%A2%E7%B3%BB%E7%BB%9F%E5%8F%98%E9%87%8F%E6%80%BB%E9%A1%B5%E6%95%B0-reportgetvariablevaluetotalpages%E6%80%BB%E6%98%AF%E8%BF%94%E5%9B%9E1/</link><guid isPermaLink="true">https://blog.wangshuai.app/2020-03-16-fastreport%E6%9F%A5%E8%AF%A2%E7%B3%BB%E7%BB%9F%E5%8F%98%E9%87%8F%E6%80%BB%E9%A1%B5%E6%95%B0-reportgetvariablevaluetotalpages%E6%80%BB%E6%98%AF%E8%BF%94%E5%9B%9E1/</guid><pubDate>Mon, 16 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;解决办法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;返回0&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;参考官方文档&lt;a href=&quot;https://www.fast-report.com/documentation/UserManFrNET-en/index.html?usesystemvariablesinexpressions.htm&quot;&gt;&lt;strong&gt;Reference to system variables&lt;/strong&gt;&lt;/a&gt;和&lt;a href=&quot;https://www.fast-report.com/en/faq/3/24/&quot;&gt;The TOTALPAGES variable always returns 0.&lt;/a&gt;，如果&lt;code&gt;Report.GetVariableValue(&quot;TotalPages&quot;)&lt;/code&gt;总是返回0，那你应该在&lt;code&gt;Report&lt;/code&gt;→&lt;code&gt;Options&lt;/code&gt;中设置&lt;strong&gt;Double Pass&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/fastreport_doublepass.png&quot; alt=&quot;image-20200316175041619&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;返回1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;方法如果总是返回1，那你应该在取值上下文中取消&lt;strong&gt;Reset Page Number&lt;/strong&gt;选项&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/fastreport_GroupSetResetPageNumber.png&quot; alt=&quot;image-20200316175516777&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>fastreport debug 调试frx文件</title><link>https://blog.wangshuai.app/2020-03-13-fastreport-debug-%E8%B0%83%E8%AF%95frx%E6%96%87%E4%BB%B6/</link><guid isPermaLink="true">https://blog.wangshuai.app/2020-03-13-fastreport-debug-%E8%B0%83%E8%AF%95frx%E6%96%87%E4%BB%B6/</guid><pubDate>Fri, 13 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;引言&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Fastreport&lt;/code&gt;自带设计器的代码编辑功能非常糟糕，如果你写了很多逻辑在frx文件里面，你几乎无法debug调试，唯有使用&lt;code&gt;MessageBox.Show()&lt;/code&gt;方法查看变量的值，而如果你要查看一个循环中的值。。。&lt;/p&gt;
&lt;h2&gt;解决办法&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Fastreport&lt;/code&gt;的frx文件提供有导出为C#代码的功能，使用visual studio执行C#代码即可解决调试难的问题&lt;/p&gt;
&lt;h2&gt;步骤&lt;/h2&gt;
&lt;p&gt;打开frx文件，选择file→Save As→保存类型→C#file&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/fastreport_saveAsCSharpfile.png&quot; alt=&quot;saveAsC#&quot; /&gt;&lt;/p&gt;
&lt;p&gt;生成的文件可能有错误，删除无效的引用后一般就可以编译通过了&lt;/p&gt;
&lt;p&gt;在控制台中使用以下代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//using FastReport;
var reportCtrl = new frx();//frx保存的c#类

//设置报表参数
foreach (var item in paramsDic.Keys)
{
    reportCtrl.SetParameterValue(item, paramsDic[item]);
}

//注册报表数据源
reportCtrl.RegisterData(dataByte);

reportCtrl.Show();

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在生成的frx里面打上断点,执行上述代码就能中断并查看变量了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/fastreport_debug.png&quot; alt=&quot;image-20200313175310786&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>fastreport显示带[] 方括号的文本或表达式</title><link>https://blog.wangshuai.app/2020-03-12-fastreport%E6%98%BE%E7%A4%BA%E5%B8%A6-%E6%96%B9%E6%8B%AC%E5%8F%B7%E7%9A%84%E6%96%87%E6%9C%AC/</link><guid isPermaLink="true">https://blog.wangshuai.app/2020-03-12-fastreport%E6%98%BE%E7%A4%BA%E5%B8%A6-%E6%96%B9%E6%8B%AC%E5%8F%B7%E7%9A%84%E6%96%87%E6%9C%AC/</guid><pubDate>Thu, 12 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;引言&lt;/h2&gt;
&lt;p&gt;Fastreport默认使用&lt;code&gt;[]&lt;/code&gt;标识变量及表达式，如果你显示的文本中有方括号就会报错&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    FastReport.TextObjectBase.CalcAndFormatExpression(String expression,Int32 expressionIndex)
    FastReport.TextObject.GetData()
    FastReport.BandBase.GetData()
    FastReport.Engine.ReportEngine.PrepareBand(BandBase band, Boolean getData)        	FastReport.Engine.ReportEngine.ShowBandToPreparedPages(BandBase band, Boolean getData)
    FastReport.Engine.ReportEngine.ShowBand(BandBase band, Boolean getData)
    FastReport.Engine.ReportEngine.ShowDataBand(DataBand dataBand, Int32 rowCount)
    FastReport.Engine.ReportEngine.RunDataBand(DataBand dataBand, Int32 rowCount, Boolean keepFirstRow, Boolean keepLastRow)
    FastReport.Engine.ReportEngine.RunDataBand(DataBand dataBand)
    FastReport.Engine.ReportEngine.RunBands(BandCollection bands)       FastReport.Engine.ReportEngine.RenderOuterSubreports(BandBase parentBand)
    FastReport.Engine.ReportEngine.ShowBand(BandBase band, Boolean getData)
    FastReport.Engine.ReportEngine.ShowDataBand(DataBand dataBand, Int32 rowCount)
    FastReport.Engine.ReportEngine.RunDataBand(DataBand dataBand, Int32 rowCount, Boolean keepFirstRow, Boolean keepLastRow)    FastReport.Engine.ReportEngine.ShowGroupTree(GroupTreeItem root)     FastReport.Engine.ReportEngine.ShowGroupTree(GroupTreeItem root)
    FastReport.Engine.ReportEngine.RunGroup(GroupHeaderBand groupBand)
    FastReport.Engine.ReportEngine.RunBands(BandCollection bands)
    FastReport.Engine.ReportEngine.RunReportPage(ReportPage page)
    FastReport.Engine.ReportEngine.RunReportPages()
    FastReport.Engine.ReportEngine.RunReportPages(ReportPage page)
    FastReport.Engine.ReportEngine.Run(Boolean runDialogs, Boolean append, Boolean resetDataState, ReportPage page)
    FastReport.Report.Prepare(Boolean append)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时你首先想到的就是转义，其实&lt;code&gt;Fastreport&lt;/code&gt;提供了简单的方法&lt;/p&gt;
&lt;h2&gt;解决办法&lt;/h2&gt;
&lt;p&gt;变量标识符叫&lt;code&gt;Brackets&lt;/code&gt;，它默认是&lt;code&gt;[,]&lt;/code&gt;，你可以把它替换成其它的&lt;code&gt;&quot;&amp;lt;,&amp;gt;&quot;,&lt;/code&gt;、&lt;code&gt; &quot;&amp;lt;!,!&amp;gt;&quot;&lt;/code&gt;；但是任何其它的标识符也难免有作为文本的时候，此时你可以删除&lt;code&gt;Brackets&lt;/code&gt;属性，那它就会直接把你的值作为变量来使用，而不会解析文本中的&lt;code&gt;[]&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;参考资料：&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/12636732/escape-square-brackets-in-fast-report-strings&quot;&gt;escape-square-brackets-in-fast-report-strings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.fast-report.com/documentation/UserManFrNET-en/index.html?textobjectexpressions.htm&quot;&gt;Displaying the expressions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fastreports.github.io/FastReport.Documentation/Expressions.html&quot;&gt;Expressions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>官方支持,解决国内docker pull mcr.microsoft.com 镜像过慢的问题</title><link>https://blog.wangshuai.app/2020-03-09-%E5%AE%98%E6%96%B9%E6%94%AF%E6%8C%81%E8%A7%A3%E5%86%B3%E5%9B%BD%E5%86%85docker-pull-mcrmicrosoftcom-%E9%95%9C%E5%83%8F%E8%BF%87%E6%85%A2%E7%9A%84%E9%97%AE%E9%A2%98/</link><guid isPermaLink="true">https://blog.wangshuai.app/2020-03-09-%E5%AE%98%E6%96%B9%E6%94%AF%E6%8C%81%E8%A7%A3%E5%86%B3%E5%9B%BD%E5%86%85docker-pull-mcrmicrosoftcom-%E9%95%9C%E5%83%8F%E8%BF%87%E6%85%A2%E7%9A%84%E9%97%AE%E9%A2%98/</guid><pubDate>Mon, 09 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;解决办法&lt;/h2&gt;
&lt;p&gt;根据此文档:&lt;a href=&quot;https://github.com/microsoft/containerregistry/blob/main/docs/mcr-endpoints-guidance.md&quot;&gt;docs/mcr-endpoints-guidance.md&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Performance Impact on Azure CDN Endpoint in Azure China Starting November 27, 2025&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现在可以使用&lt;code&gt;mcr.azure.cn&lt;/code&gt;这个域名在国内拉取镜像&lt;/p&gt;
&lt;p&gt;比如:&lt;code&gt;docker pull mcr.azure.cn/dotnet/aspnet&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;s&gt;根据此issue:&lt;a href=&quot;https://github.com/Azure/container-service-for-azure-china/issues/52&quot;&gt;dockerhub.azk8s.cn how to get the multiple path image&lt;/a&gt;，可使用&lt;code&gt;mcr.azk8s.cn&lt;/code&gt;仓库替换&lt;code&gt;mcr.microsoft.com&lt;/code&gt;，即，如果你需要&lt;code&gt;mcr.microsoft.com/dotnet/core/aspnet:2.2&lt;/code&gt;，那你将&lt;code&gt;mcr.microsoft.com&lt;/code&gt;替换为&lt;code&gt;mcr.azk8s.cn&lt;/code&gt;;变成&lt;code&gt;mcr.azk8s.cn/dotnet/core/aspnet:2.2&lt;/code&gt;即可&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;此仓库已被限制为只能在Azure中使用，如果想要有更快的&lt;code&gt;docker pull&lt;/code&gt; 速度，可以参考另一篇文章&lt;a href=&quot;/2020-01-17-%E6%97%A0%E9%9C%80VPS%EF%BC%8C%E4%BD%BF%E7%94%A8%E9%87%8D%E6%96%B0%E6%89%93tag%E7%9A%84%E6%96%B9%E5%BC%8F%EF%BC%8C%E6%8B%89%E5%8F%96%E3%80%81%E8%BD%AC%E5%AD%98mcr.microsoft.com%E7%9A%84%E9%95%9C%E5%83%8F/&quot;&gt;无需VPS,使用重新打tag的方式 拉取、转存mcr.microsoft.com的镜像&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>无需VPS,使用重新打tag的方式 拉取、转存mcr.microsoft.com的镜像</title><link>https://blog.wangshuai.app/2020-01-17-%E6%97%A0%E9%9C%80VPS%EF%BC%8C%E4%BD%BF%E7%94%A8%E9%87%8D%E6%96%B0%E6%89%93tag%E7%9A%84%E6%96%B9%E5%BC%8F%EF%BC%8C%E6%8B%89%E5%8F%96%E3%80%81%E8%BD%AC%E5%AD%98mcr.microsoft.com%E7%9A%84%E9%95%9C%E5%83%8F/</link><guid isPermaLink="true">https://blog.wangshuai.app/2020-01-17-%E6%97%A0%E9%9C%80VPS%EF%BC%8C%E4%BD%BF%E7%94%A8%E9%87%8D%E6%96%B0%E6%89%93tag%E7%9A%84%E6%96%B9%E5%BC%8F%EF%BC%8C%E6%8B%89%E5%8F%96%E3%80%81%E8%BD%AC%E5%AD%98mcr.microsoft.com%E7%9A%84%E9%95%9C%E5%83%8F/</guid><pubDate>Fri, 17 Jan 2020 00:00:00 GMT</pubDate><content:encoded>&lt;hr /&gt;
&lt;p&gt;根据此文档:&lt;a href=&quot;https://github.com/microsoft/containerregistry/blob/main/docs/mcr-endpoints-guidance.md&quot;&gt;docs/mcr-endpoints-guidance.md&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Performance Impact on Azure CDN Endpoint in Azure China Starting November 27, 2025&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现在可以使用&lt;code&gt;mcr.azure.cn&lt;/code&gt;这个域名在国内拉取镜像&lt;/p&gt;
&lt;p&gt;比如:&lt;code&gt;docker pull mcr.azure.cn/dotnet/aspnet&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;因此本文现在仅做参考;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;引言&lt;/h2&gt;
&lt;p&gt;用&lt;code&gt;docker&lt;/code&gt;的都知道镜像加速，通过配置阿里云、腾讯云的镜像源确实可以大幅提升&lt;code&gt;docker pull&lt;/code&gt;的效果，但对于某些不常用或新版的镜像却收效甚微。&lt;/p&gt;
&lt;p&gt;比如最近想要部署私有ExceptionLess服务，它的dockerfile使用了&lt;code&gt;mcr.microsoft.com/dotnet/core/sdk:2.2.401&lt;/code&gt;和&lt;code&gt;mcr.microsoft.com/dotnet/core/aspnet:2.2&lt;/code&gt;，这两个镜像拉取非常慢，只能使用手动&lt;code&gt;tag&lt;/code&gt;的方式来解决。&lt;/p&gt;
&lt;p&gt;另外，本人在腾讯云中创建了&lt;code&gt;dotnet-core&lt;/code&gt;命名空间，源自&lt;code&gt;mcr.microsoft.com&lt;/code&gt;可直接使用&lt;code&gt;ccr.ccs.tencentyun.com/dotnet-core/runtime&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/tencenyun_aspnet_core_repository.png&quot; alt=&quot;image-20200415142923048&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;解决办法&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;2020-04-15更新&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;原标题为：&lt;em&gt;使用travis-ci的Trigger build功能一键转存&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;但是本人多次实测，&lt;code&gt;travis-ci&lt;/code&gt;的&lt;code&gt;Build&lt;/code&gt;带宽限制严重，如果你要Retag &lt;code&gt;Runtime&lt;/code&gt;还好，设置&lt;code&gt;travis_wait 30&lt;/code&gt;基本也能用；但是如果是SDK，基本都会超时&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Still running (50 of 300): sudo docker push ccr.ccs.tencentyun.com/dotnet-core/sdk:5.0
The job exceeded the maximum time limit for jobs, and has been terminated.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此改用&lt;code&gt;Azure Devops&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;2020-3-9&lt;/strong&gt;更新:&lt;/p&gt;
&lt;h3&gt;&lt;s&gt;使用Azure官方镜像mirror&lt;/s&gt;&lt;/h3&gt;
&lt;p&gt;&lt;s&gt;根据此issue:&lt;a href=&quot;https://github.com/Azure/container-service-for-azure-china/issues/52&quot;&gt;dockerhub.azk8s.cn how to get the multiple path image&lt;/a&gt;，可使用&lt;code&gt;mcr.azk8s.cn&lt;/code&gt;仓库替换&lt;code&gt;mcr.microsoft.com&lt;/code&gt;，即，将&lt;code&gt;mcr.microsoft.com/dotnet/core/aspnet:2.2&lt;/code&gt;替换为&lt;code&gt;mcr.azk8s.cn/dotnet/core/aspnet:2.2&lt;/code&gt;&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;此方案只能在Azure中使用。。。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;手动Tag&lt;/h3&gt;
&lt;p&gt;如果你有一台国外的VPS，那很简单；只需执行&lt;code&gt;docker pull&lt;/code&gt;、&lt;code&gt;docker login&lt;/code&gt;、&lt;code&gt;docker tag&lt;/code&gt;、&lt;code&gt;docker push&lt;/code&gt;就OK了。无奈我的VPS到期了，只能另寻它法，google之后从这篇文章找到灵感：&lt;a href=&quot;https://www.zhoujiangang.com/p/fetch-google-image-use-tag/&quot;&gt;使用重新打 tag 的方式，拉取 k8s.gcr.io 的镜像&lt;/a&gt;。&lt;/p&gt;
&lt;h3&gt;使用Azure Devops&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;首先，去腾讯云中创建保存&lt;code&gt;image&lt;/code&gt;的仓库
&lt;img src=&quot;/img/qcloud_images_list.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;参考&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&amp;amp;tabs=yaml#create-a-service-connection&quot;&gt;create-a-service-connection&lt;/a&gt;，在project→project settings →Service connections →New ervice connection中新建一个名为&lt;code&gt;tencentyun&lt;/code&gt;的&lt;code&gt;Docker Registry&lt;/code&gt;，其中&lt;code&gt;Docker Registry&lt;/code&gt;地址为：https://ccr.ccs.tencentyun.com/v2/，用户名和密码请查看腾讯云的镜像使用指引&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/AzureDevops_createTencentyun_DockerRegistry.png&quot; alt=&quot;image-20200415111820887&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在你项目里面的&lt;code&gt;Piplines&lt;/code&gt;中新建一个连接到Github仓库的yml，创建模板随便选&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/AzureDevops_CreatePipline_yml.png&quot; alt=&quot;image-20200415135902763&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最后把Review中的yml文件替换成如下：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;trigger:
- master

pool:
  vmImage: &apos;ubuntu-latest&apos;

variables:
  tag: &apos;aspnet:5.0&apos;

steps:
- task: Docker@2
  inputs:
    containerRegistry: &apos;tencentyun&apos;
    command: &apos;login&apos;
- task: PowerShell@2
  inputs:
    targetType: &apos;inline&apos;
    script: |
      $env:version=&quot;$(tag)&quot;
      
      docker pull mcr.microsoft.com/dotnet/core/$env:version
      
      docker tag mcr.microsoft.com/dotnet/core/$env:version ccr.ccs.tencentyun.com/dotnet-core/$env:version
      docker push ccr.ccs.tencentyun.com/dotnet-core/$env:version
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;然后点击&lt;code&gt;Save and run&lt;/code&gt;(或者也可以在自己的仓库中创建一个上述的&lt;code&gt;azure-pipelines.yml&lt;/code&gt;文件,在Azure Devops中连接好后提交)就可以了&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;s&gt;使用travis-ci自动Tag&lt;/s&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;travis-ci&lt;/code&gt;的&lt;code&gt;Build&lt;/code&gt;带宽限制严重，已改用Azure Devops，以下步骤仅作参考&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;使用第三方的CI、CD服务&lt;code&gt;push&lt;/code&gt;镜像到国内私有仓库中解决&lt;code&gt;docker pull&lt;/code&gt;过慢的问题，本例的CI-CD使用的是&lt;a href=&quot;https://travis-ci.com/&quot;&gt;travis-ci&lt;/a&gt;，由于自己使用了腾讯云的容器服务，因此私有库使用了腾讯云提供的免费镜像仓库。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先，去腾讯云中创建保存&lt;code&gt;image&lt;/code&gt;的仓库
&lt;img src=&quot;/img/qcloud_images_list.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/li&gt;
&lt;li&gt;其次，访问&lt;a href=&quot;https://travis-ci.com/&quot;&gt;travis-ci&lt;/a&gt;，并授权其对自己git库的访问权限(&lt;strong&gt;随便什么仓库都行，因为我们并不需要使用里面的代码&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;参照腾讯云提供的镜像使用指引，编写&lt;code&gt;.travis.yml&lt;/code&gt;脚本文件
&lt;img src=&quot;/img/qcloud_images_guid.png&quot; alt=&quot;image.png&quot; /&gt;
我的脚本文件如下：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;language: bash
env:
  - version=sdk:2.2
services:
  - docker
sudo: required
branches:
  only:
    - master
script:
  - echo &quot;$DOCKER_PASSWORD&quot; | docker login -u &quot;$DOCKER_USERNAME&quot; --password-stdin ccr.ccs.tencentyun.com
  - docker pull mcr.microsoft.com/dotnet/core/$version

after_success:
  - sudo docker tag mcr.microsoft.com/dotnet/core/$version ccr.ccs.tencentyun.com/dotnet-core/$version
  - sudo docker push ccr.ccs.tencentyun.com/dotnet-core/$version
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;在&lt;a href=&quot;https://travis-ci.com/&quot;&gt;travis-ci&lt;/a&gt;的首页上，任意选择一个仓库，然后在&lt;code&gt;More Options&lt;/code&gt;→&lt;code&gt;Settings&lt;/code&gt;里面添加docker登陆的用户名密码环境变量&lt;code&gt;DOCKER_USERNAME&lt;/code&gt;和&lt;code&gt;DOCKER_PASSWORD&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/travis-ci_setEnv.png&quot; alt=&quot;image-20200410113527361&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在&lt;code&gt;More Options&lt;/code&gt;→&lt;code&gt;Trigger build&lt;/code&gt;的&lt;code&gt;CUSTOM CONFIG&lt;/code&gt;中粘贴写好的&lt;code&gt;.travis.yml&lt;/code&gt;脚本并点击&lt;code&gt;Trigger custom build&lt;/code&gt;
&lt;img src=&quot;/img/travis-ci_Trigger_Custom_Build.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;稍等几分钟即可在&lt;code&gt;job log&lt;/code&gt;中看到脚本的执行结果&lt;/s&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里并不会立即出结果，图示是因为images已经存在于&lt;code&gt;ccr.ccs.tencentyun.com&lt;/code&gt;中了，所以给人以速度很快的假象&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/travis-ci_wait_job_result.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;参考链接&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/ecosystems/containers/push-image?view=azure-devops&quot;&gt;push-image&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.travis-ci.com/user/build-stages/share-docker-image/&quot;&gt;share-docker-image&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.travis-ci.com/user/docker/&quot;&gt;docker&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Devexpress打印预览(PrintPreview)时修改打印数据</title><link>https://blog.wangshuai.app/2017-10-7-devexpress%E6%89%93%E5%8D%B0%E9%A2%84%E8%A7%88printpreview%E6%97%B6%E4%BF%AE%E6%94%B9%E6%89%93%E5%8D%B0%E6%95%B0%E6%8D%AE/</link><guid isPermaLink="true">https://blog.wangshuai.app/2017-10-7-devexpress%E6%89%93%E5%8D%B0%E9%A2%84%E8%A7%88printpreview%E6%97%B6%E4%BF%AE%E6%94%B9%E6%89%93%E5%8D%B0%E6%95%B0%E6%8D%AE/</guid><pubDate>Sat, 07 Oct 2017 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;引言&lt;/h2&gt;
&lt;p&gt;打印功能对于一个系统来说通常是强需求，每个系统都会根据自己所提供的服务生成不同的打印模板，但光有模板往往不够用，因为有时会需要在打印预览(PrintPreview)时根据打印内容临时修改打印的数据，而且这种修改并不需要应用于打印数据源的修改，只需要打印出变更后的内容就行了；所以我们只需修改内存中的数据，然后重新打印就可以了。&lt;/p&gt;
&lt;p&gt;在最新的Devexpress 17.1中&lt;a href=&quot;https://documentation.devexpress.com/XtraReports/17403/Concepts/Report-Controls/Label&quot;&gt;XRLable&lt;/a&gt;，&lt;a href=&quot;https://documentation.devexpress.com/XtraReports/9741/Concepts/Report-Controls/Table&quot;&gt;XRTableCell&lt;/a&gt;和&lt;a href=&quot;https://documentation.devexpress.com/XtraReports/117157/Concepts/Report-Controls/Character-Comb&quot;&gt;Character Comb&lt;/a&gt;有&lt;a href=&quot;https://documentation.devexpress.com/XtraReports/DevExpress.XtraReports.UI.XRLabel.EditOptions.property&quot;&gt;EditOptions&lt;/a&gt;属性，可以实现直接在打印预览界面(PrintPreview)上修改打印的数据，具体请参考&lt;a href=&quot;https://documentation.devexpress.com/XtraReports/117343/Concepts/Creating-Reports/Navigation-and-Interaction/Content-Editing-in-Print-Preview&quot;&gt;官方示例&lt;/a&gt;；但很可惜我相信大部分的系统都还和我用的一样，没有使用17.1，所以只能乖乖修改数据然后重新打印。&lt;/p&gt;
&lt;h2&gt;效果图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/img/reportPreview.gif&quot; alt=&quot;打印预览界面双击修改打印数据&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;步骤&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h4&gt;首先我们设计好打印内容的模板&lt;/h4&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;打印内容模板:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/report1.png&quot; alt=&quot;打印内容模板&quot; /&gt;&lt;/p&gt;
&lt;p&gt;​	打印预览效果图:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/printpreviewbase0.png&quot; alt=&quot;打印预览效果图&quot; /&gt;&lt;/p&gt;
&lt;p&gt;打印预览(PrintPreview)中的每一个Page表示一个model，我们需要一种方式来修改对应model的数据；通过查看&lt;a href=&quot;https://documentation.devexpress.com/CoreLibraries/DevExpress.XtraPrinting.Brick.class&quot;&gt;官方示例&lt;/a&gt;我们知道，打印预览界面(PrintPreview)的元素实际上都是Brick类型的子类，你点击每一个Page都触发的是&lt;a href=&quot;https://documentation.devexpress.com/WindowsForms/DevExpress.XtraPrinting.Control.PrintControl.BrickClick.event&quot;&gt;BrickClick&lt;/a&gt;事件：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/brickclick.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过调试可以发现打印预览界面(PrintPreview)元素构成的层级关系：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/bricktable.png&quot; alt=&quot;层级关系&quot; /&gt;&lt;/p&gt;
&lt;p&gt;所以我们可以把每一个model的主键保存在每一个Page中，我选用了BrickTable（你也可以选择其它元素，只是在我的例子中刚好每一个Page对应于一个Table）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所以第二步是保存model的主键到BrickTable的AnchorName中（如果你在打印预览界面(PrintPreview)使用了目录功能，可能就需要用其它方式保存数据了）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// 保存的打印页主键列表
/// &amp;lt;/summary&amp;gt;
private List&amp;lt;int&amp;gt; ListStudentId { get;set; }
/// &amp;lt;summary&amp;gt;
/// 生成Table明细数据之后的事件处理方法
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&quot;sender&quot;&amp;gt;&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&quot;e&quot;&amp;gt;&amp;lt;/param&amp;gt;
private void Detail1_AfterPrint(object sender, EventArgs e)
{
    var dtRowView = DetailReport.GetCurrentRow() as DataRowView;
    if (dtRowView != null)
    {
        var studentId =Convert.ToInt32(dtRowView[&quot;StudentId&quot;]);
     
        ListStudentId.Add(studentId);//将Key记录下来
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;第三步，在生成打印明细数据时将主键赋值到BrickTable的AnchorName上&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// 将生成打印明细的数据打印到界面上的事件处理方法
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&quot;sender&quot;&amp;gt;&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&quot;e&quot;&amp;gt;&amp;lt;/param&amp;gt;
private void tbStudentDetail_PrintOnPage(object sender, PrintOnPageEventArgs e)
{
    if (ListStudentId.Any())
    {
        //将主键保存到生成的打印对象的ID中
        var iterator = new DevExpress.XtraPrinting.Native.NestedBrickIterator(Pages[e.PageIndex].InnerBricks);
        while (iterator.MoveNext())
        {
            var visualBrick = iterator.CurrentBrick as VisualBrick;
            if (visualBrick != null)
                //PutStateToBrick(visualBrick, PrintingSystem);
            {
                if (visualBrick.BrickType == &quot;Table&quot;)
                //if (string.IsNullOrEmpty(visualBrick.Text) &amp;amp;&amp;amp; string.IsNullOrEmpty(visualBrick.ID))
                {
                    if (ListStudentId.Any())
                    {
                        visualBrick.ID = &quot;StudentId&quot;;
                        //visualBrick.Text = listKey[visualBrick.ID];
                        visualBrick.AnchorName = ListStudentId[0].ToString();
                        ListStudentId.RemoveAt(0);
                    }
                    else
                    {
                        break;
                    }
                }
            }
            //Do some actions based on your requirements
        }
        //}
        //ListStudentId.RemoveAt(0);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h4&gt;剩下的就是在打印预览界面(PrintPreview)双击Page弹出修改属性窗体后重新打印数据&lt;/h4&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;/// &amp;lt;summary&amp;gt;
/// 双击打印预览页面的事件处理方法
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;param name=&quot;sender&quot;&amp;gt;&amp;lt;/param&amp;gt;
/// &amp;lt;param name=&quot;e&quot;&amp;gt;&amp;lt;/param&amp;gt;
private void documentViewer1_BrickDoubleClick(object sender, DevExpress.XtraPrinting.Control.BrickEventArgs e)
{
    var studentId = string.Empty;
    var iterator = new DevExpress.XtraPrinting.Native.NestedBrickIterator(e.Page.InnerBricks);
    while (iterator.MoveNext())
    {
        VisualBrick visualBrick = null;
        try
        {
            visualBrick = iterator.CurrentBrick as VisualBrick;
        }
        catch (ArgumentOutOfRangeException)
        {
            //对弹出的窗口点击确认后会报此异常,因为超出了report的范围
            //对这种异常不做处理
        }
        if (visualBrick != null)
            //PutStateToBrick(visualBrick, PrintingSystem);
            //读取打印模板界面保存的report主键数据
        {
            if (visualBrick.ID == &quot;StudentId&quot;)
            {
                studentId = visualBrick.AnchorName;
            }
        }

        //Do some actions based on your requirements
    }

    if(string.IsNullOrEmpty(studentId))return;
    else
    {
        //MessageBox.Show($&quot;点击的学生主键为{studentId}&quot;);
        var newForm = new EditProperties(ListReport.First().PModel.ListStudents
            .SingleOrDefault(c =&amp;gt; c.StudentId == Convert.ToInt32(studentId)));
        var dlg= newForm.ShowDialog();
        if (dlg == DialogResult.OK)
        {
            //重新打印
            var newReport=ListReport.First();
            newReport.SetDataSource(); 
            LoadData();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中，修改属性的窗体使用了类似设计器属性修改界面的&lt;a href=&quot;https://documentation.devexpress.com/WindowsForms/DevExpress.XtraVerticalGrid.PropertyGridControl.class&quot;&gt;PropertyGridControl&lt;/a&gt;控件，当然你也可以自定义界面来实现类似功能，只要可以修改属性的值就行了&lt;/p&gt;
&lt;p&gt;文章描述内容的示例项目已放在了&lt;a href=&quot;https://github.com/wangshuai-007/BlogsSample&quot;&gt;Github&lt;/a&gt;上。&lt;/p&gt;
</content:encoded></item><item><title>Devexpress自定义导出(Export)选定行数据到Excel简易版</title><link>https://blog.wangshuai.app/2017-09-19-devexpress%E8%87%AA%E5%AE%9A%E4%B9%89%E5%AF%BC%E5%87%BAexport%E9%80%89%E5%AE%9A%E8%A1%8C%E6%95%B0%E6%8D%AE%E5%88%B0excel%E7%AE%80%E6%98%93%E7%89%88/</link><guid isPermaLink="true">https://blog.wangshuai.app/2017-09-19-devexpress%E8%87%AA%E5%AE%9A%E4%B9%89%E5%AF%BC%E5%87%BAexport%E9%80%89%E5%AE%9A%E8%A1%8C%E6%95%B0%E6%8D%AE%E5%88%B0excel%E7%AE%80%E6%98%93%E7%89%88/</guid><pubDate>Tue, 19 Sep 2017 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;引言&lt;/h2&gt;
&lt;p&gt;我们知道，一般在winform中导出GridView的内容为Excel时会引用第三方库；如Aspose、NPOI等，它们需要你逐行设置导出的内容，虽然功能强大，但有时略显麻烦，可喜的是Devexpress中已经为我们实现了导出的方法&lt;a href=&quot;https://documentation.devexpress.com/WindowsForms/DevExpress.XtraGrid.GridControl.ExportToXls.overloads&quot;&gt;GridControl.ExportToXls&lt;/a&gt;，它可以将整个GridView全部导出，但通常我们仅需要导出选定行，此时需要做一些设置。&lt;/p&gt;
&lt;h2&gt;步骤&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;首先设置GridView允许选择多行以及选中行导出模式&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;//选择多行
GridView.OptionsSelection.MultiSelect = true;
//仅打印选中行
GridView.OptionsPrint.PrintSelectedRowsOnly = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;然后使用Devexpress自带的导出功能自动打印&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;var fileName = string.Empty;

var dialog = new SaveFileDialog();
dialog.Title = &quot;保存文件&quot;;
dialog.RestoreDirectory = true;
dialog.AddExtension = true;

////判断默认文件名称是否存在?若存在,则赋值
//if (string.IsNullOrEmpty(defaultFileName) == false)
//{
// dialog.FileName = defaultFileName;
//}

dialog.Filter = @&quot;Excel 97-2003 文件|*.xls&quot;;

if (dialog.ShowDialog() == DialogResult.OK)
{
    fileName = dialog.FileName;
}
if (string.IsNullOrEmpty(fileName)) return;
try
{
    GridView.ExportToXls(fileName);
    MessageBox.Show(&quot;导出Excel成功&quot;);
}
catch (Exception ex)
{
    throw ex;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;效果图&lt;/h2&gt;
&lt;p&gt;devexpress导出选中行效果图
&lt;img src=&quot;/img/%E5%AF%BC%E5%87%BA%E9%80%89%E4%B8%AD%E8%A1%8C.png&quot; alt=&quot;效果图&quot; /&gt;&lt;/p&gt;
</content:encoded></item></channel></rss>