<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
    <channel>
        <title>套路猿</title>
        <link>https://blog.taoluyuan.com</link>
        <description>工欲善其事,套路利其器,自古代码留不住,唯有套路得人心</description>
        <lastBuildDate>Sun, 16 Feb 2025 15:24:57 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Feed for Node.js</generator>
        <language>zh-CN</language>
        <image>
            <title>套路猿</title>
            <url>/static/images/banner.jpg</url>
            <link>https://blog.taoluyuan.com</link>
        </image>
        <copyright>All rights reserved 2025, 套路猿</copyright>
        <item>
            <title><![CDATA[完美解决 Cursor Markdown 格式代码块转义、缩进问题,完整 Apply markdown 到md文件]]></title>
            <link>https://blog.taoluyuan.com/blog/complete-fix-cursor-markdown-chaos</link>
            <guid>https://blog.taoluyuan.com/blog/complete-fix-cursor-markdown-chaos</guid>
            <pubDate>Tue, 11 Feb 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[
          <p><strong>详细介绍如何解决Cursor生成Markdown文档时的代码转义问题，如何正确使用 Cursor 生成 Markdown ,包括渲染断裂、代码块混乱、缩进不规范、无法完整应用到markdown文件等问题的解决方案</strong></p>
          <h2>Cursor Markdown 常见渲染问题</h2>
<p>如果想要通过 <code>Cursor</code> 写 <code>readme.md</code> 或者想要将 <code>Cursor</code> 的回答应用于 <code>markdown</code> 文件</p>
<p>你会要求 <code>Cursor</code> 输出 <code>markdown</code> 内容，那就很有可能就会出现以下问题：</p>
<ol>
<li><code>Cursor markdown</code> 内容在聊天框中显示断裂</li>
<li><code>Cursor markdown</code> 代码块渲染混乱</li>
<li><code>Cursor markdown</code> 代码 JSON 块缩进不规范</li>
<li><code>Cursor markdown</code> 内容无法完整应用到 markdown 文件</li>
</ol>
<p>这是 <code>Cursor markdown</code> 格式渲染和代码缩进问题，在使用 Cursor 生成 Markdown 文档时或多或少有点毛病</p>
<p>下面有个案例，要求 cursor 回答 我golang 协程相关的知识点, 并且输出 <code>markdown</code> 内容</p>
<pre><code class="language-text">
Create a Go tutorial about goroutines that explains core concepts, lifecycle, communication mechanisms (channels), concurrency control, and best practices. Include practical examples.

The tutorial should cover:

- Core concepts and fundamentals of goroutines
- Goroutine lifecycle and management
- Channel-based communication patterns
- Concurrency control mechanisms (mutexes, waitgroups)
- Best practices and common pitfalls
- Error handling in concurrent programs
- Practical examples demonstrating each concept

Format your response in markdown.

</code></pre>
<p>结果如下图,<code>cursor markdown</code> 包含代码和json的地方，会自动分割成快</p>
<p>只能局部应用到cursor,是无法全部应用到 markdown 文件。</p>
<p><img src="/static/blog/cursor/cursor-md-berfor.png" alt=""></p>
<h2>如何正确使用 Cursor 生成 Markdown 文档</h2>
<p><code>Cursor</code> 输出 <code>Markdown</code> 可以参考下面的规范要求</p>
<ul>
<li>所有代码块必须使用2个空格缩进</li>
<li>为每个代码块指定正确的语言标识符</li>
<li>严格禁止使用0空格或4空格缩进</li>
</ul>
<h3>Cursor 生成 Markdown 的最佳 <code>prompt</code></h3>
<p>解决 <code>cursor markdown</code> 格式渲染和代码缩进问题的<code>prompt</code>如下</p>
<p>要求 cursor 输出 markdown 内容时，复制下面提示词贴入cursor聊天框：</p>
<pre><code class="language-text">Format your response in markdown according to the following requirements:

- When proposing an edit to a markdown file, first evaluate whether the content will contain code snippets
- If the content contains no code snippets, enclose the entire response in backticks with 'markdown' as the language identifier
- If the content includes code snippets, ensure all code blocks are indented with exactly 2 spaces and specify the correct language for proper rendering
- Only 2-space indentation is allowed for code blocks - level 0 and 4 space indentations are not permitted
- Automatically correct any code block indentation that doesn't follow the 2-space rule
</code></pre>
<p>可以看到,输出后的markdwon 是一整块,点击 <code>apply</code> 就能应用到完整的 <code>markdown</code> 文件</p>
<p><img src="/static/blog/cursor/cursor-md-after.png" alt=""></p>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>cursor</category>
        </item>
        <item>
            <title><![CDATA[2025年香港银行开户攻略:中银、汇丰、众安、蓝狮子、万事达扣账卡港卡申请经验分享]]></title>
            <link>https://blog.taoluyuan.com/blog/how-to-open-a-bank-account-in-hongkong-2025</link>
            <guid>https://blog.taoluyuan.com/blog/how-to-open-a-bank-account-in-hongkong-2025</guid>
            <pubDate>Tue, 04 Feb 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[
          <p><strong>2025年香港银行开户完整攻略：详细分享在香港开通中银、汇丰、众安银行账户以及申请万事达扣账卡的经验。从预约、交通、材料准备到办理流程的实用信息。</strong></p>
          <h2>香港银行开户成功经验分享</h2>
<p><img src="/static/blog/hkcard/17386845208026jqt502851539b1d72ef012.jpg" alt="how-to-open-a-bank-account-in-hongkong"></p>
<p>2025年我在香港成功办理了中银和汇丰银行卡、蓝狮子和万事达扣账卡; 现在所有实体卡都已收到,其中众安数字虚拟卡也申请成功;分享一些经验给大家，希望想要办理香港银行卡的朋友们少走弯路。</p>
<p>主要内容包含以下几个方面</p>
<ol>
<li>如何预约香港银行开户、深圳到香港的交通路线</li>
<li>所需材料的准备、 银行面谈技巧</li>
<li>卡片激活流程、红狮子与蓝狮子的区别</li>
<li>香港办卡的常见问题</li>
</ol>
<h2>中银汇丰办卡预约</h2>
<ul>
<li>
<p>不预约就要早起现场排队,我工作日去,发现没有预约并且排队的人也挺多,如果周末去,估计要排更久. 如果想要一天办中银和汇丰,还是预约最好</p>
</li>
<li>
<p>我提前半个月把顺丰和中银都预约了</p>
</li>
<li>
<p>汇丰我约的是屯门市广场分行 9:00,一楼是ATM,可以通过ATM旁边的楼梯直上二楼汇丰办理</p>
</li>
<li>
<p>而同样的位置一楼就是中银,可惜的是我没有约到,记住:如果能同时约到屯门市广场分行的汇丰和中银,那将非常完美</p>
</li>
<li>
<p>最终中银约的是700米外的建荣街分行 时间是 11:10; 汇丰办理完走路过去,好消息是中银人不多,居然给我提前办理了</p>
<p><strong>港卡预约入口</strong></p>
</li>
</ul>
<ol>
<li>中银预约入口: [中银香港微服务] 公众号</li>
<li>汇丰预约入口: [汇丰香港] 公众号</li>
</ol>
<p><img src="/static/blog/hkcard/1738684531276p2ea51387bcafc2d8159203.jpg" alt="how-to-open-a-bank-account-in-hongkong"></p>
<h2>怎么去香港</h2>
<p>如果是跟我一样的在屯门办理,从深圳出发,那么路线最佳为:</p>
<ol>
<li>坐地铁到深圳湾口岸</li>
<li>深圳湾头口岸过关</li>
<li>过关后跟着人群出去坐B3X到屯门市广场(终点站下车)</li>
<li>下车后根据导航步行到银行即可,路盲不熟悉路况,切记多留点时间</li>
</ol>
<h2>在香港怎么上网</h2>
<p>很多人推荐到了香港,买香港的手机卡,在香港上网,这也是个不错的选择</p>
<p>我没办香港卡,选择的是开通漫游和买境外流量,中国移动对应的步骤为:</p>
<ol>
<li>下载无忧行(中国移动旗下),流量->境外流量->中国香港</li>
<li>选择套餐,我选择的套餐是1天3G,价格是8.8元,网速不错</li>
<li>上述完成后,在出发当天或者到达香港收到境外欢迎短信后,在手机设置启用漫游,这代表你的手机可以漫游上网</li>
</ol>
<p>其他运营商的漫游开通步骤类似,原理就是:</p>
<p><strong>开通漫游->买境外流量->手机启用漫游(到到境外或者收到境外欢迎短信后启用更好)</strong></p>
<p><strong>怎么判断漫游启用成功</strong></p>
<p>能访问谷歌,就代表漫游启用成功了吧,懂的都懂</p>
<p><img src="/static/blog/hkcard/1738684470344b2cq508870537bb2c76e9cd.jpg" alt="how-to-open-a-bank-account-in-hongkong"></p>
<h1>办理过程</h1>
<h2>账单地址材料</h2>
<p>办理员比较看重你实际的居住地址,所以我打印了信用卡账单,记得是盖章带有地址的账单.</p>
<p>最好是提前通过网上下载对应银行的pdf,线下自己打印</p>
<p>如果你是深圳招商银行,可以直接去银行自助机打印</p>
<h2>其他材料</h2>
<ol>
<li>含有盖章和地址的信用卡账单</li>
<li>投资证明(东方财富电子对账单)</li>
<li>纳税证明(个人所得税-纳税记录开具)</li>
<li>工资卡流水(工作人员也会看,我感觉有纳税证明也够了,它完全可以证明流水)</li>
<li>身份证,港澳通行证,过关小票</li>
</ol>
<h2>和工作人员面谈技巧</h2>
<ul>
<li>工作人员问办卡理由; 回答:投资港美股,并将准备投资证明给工作人员看(东方财富和电子对账单)</li>
<li>问 投资什么港美股,在哪投资; 回答一些知道的股票(美团,小米),和券商(富途,老虎)</li>
<li>问到投入金额, 根据情况吹下牛逼,说下自己打算投多少钱</li>
<li>会问到居住地址,正常回答即可,最终是要给工作人员看信用卡账单,用于地址验证,工作人员就不会问此类话题了</li>
</ul>
<h3>面谈后等着办卡</h3>
<ul>
<li>
<p>接下来就是工作人员在那录入,办卡,期间会帮你操作APP进行申请,等待就可以,都是工作人员在操作.</p>
</li>
<li>
<p>期间如果app申请失败,大概率是信息录入不全,工作人员会继续在电脑帮你录入,还会问你更详细的信息,直到成功拿到卡</p>
</li>
<li>
<p>由此可以推测,如果是自己网上申请,出现类似不通过的情况,大概率是信息录入不全;但不确定工作人员后面在电脑补录的数据,在app上是否有权限再次调整</p>
</li>
</ul>
<p><img src="/static/blog/hkcard/1738684528500ztlv50080f94f59c2801075.jpg" alt="how-to-open-a-bank-account-in-hongkong"></p>
<h2>激活汇丰中银卡</h2>
<p>中银和汇丰下卡后,拿着卡去改密码,然后存钱,存钱后就可以正常使用,很简单按照 工作人员教你的操作即可</p>
<p>至于存多少,没人跟我说,一开始汇丰我存了100,中银我存了1000,手机看app已经能看到余额了,这肯定已经算激活了</p>
<p>我感觉是否激活与存多少钱没关系,因为拿到卡后,去ATM是自主的行为没人干预你</p>
<h2>申请蓝狮子(万事达扣账卡)</h2>
<p>到了这一步,汇丰和中银的卡都拿到了,接下来就是申请蓝狮子和中银万事达扣账卡</p>
<p>这一步 就不需要人在香港,累了回大陆,躺在家里也能申请,打开对应的银行APP,按照提示操作即可,具体如下:</p>
<h3>1. 中银申请万事达扣账卡</h3>
<ol>
<li>打开中银APP,点击 选单->账户->申请中银卡/扣账卡</li>
<li>通过后已经有虚拟卡了,接下来按照操作申请实体卡</li>
<li>中银发的是平邮,平邮寄送时间长,如果怕弄丢,可以联系客服改成挂号信(需要额外给20多港币,看情况而定),会得到一个号码,这个号码是挂号信的编号,可以查询到快递进度</li>
<li>收到后,结合APP和信封的提示操作即可激活,大概是输入信封的里的一个code</li>
</ol>
<h3>2. 汇丰申请蓝狮子</h3>
<ol>
<li>打开汇丰APP,点击 汇丰ONE账户->港元储蓄(随便一个账户都可以)->管理借记卡->就能看到申请的界面了(我已经申请成功,忘记具体的页面长啥样)</li>
<li>汇丰卡发深圳用的是奢华的EMS,且用自己的手机号就能查询到快递进度,3天到深圳</li>
<li>收到后,按照操作就能激活,不同的是汇丰蓝狮子激活需要打电话到银行,也是简单的人机操作
<img src="/static/blog/hkcard/1738684531276p2ea51387bcafc2d8159203.jpg" alt="how-to-open-a-bank-account-in-hongkong"></li>
</ol>
<h2>什么是红狮子蓝狮子</h2>
<p>汇丰银行(HSBC)</p>
<ul>
<li>红狮子卡: 基础账户卡,申请通过即可获得</li>
<li>蓝狮子卡: 万事达扣账卡,需在获得红狮子卡后通过App申请</li>
</ul>
<p>中国银行(香港)</p>
<ul>
<li>本地银行卡: 对应汇丰红狮子</li>
<li>万事达扣账卡: 对应汇丰蓝狮子</li>
</ul>
<h2>红狮子蓝狮子核心区别</h2>
<p>主要区别如下:</p>
<p>红狮子:</p>
<ul>
<li>香港和大陆可通过 ATM 取现以及刷卡消费。</li>
<li>不支持网上支付(不支持支付宝微信绑定支付)</li>
<li>大陆取款需要手续费,非本行更贵</li>
</ul>
<p>蓝狮子:</p>
<ul>
<li>香港和大陆可通过 ATM 取现以及刷卡消费。</li>
<li>支持网上支付(支持支付宝微信绑定支付)</li>
<li>大陆汇丰取款不需要手续费</li>
</ul>
<h2>数字虚拟银行卡</h2>
<p>人到了香港,其实也可以多申请几张虚拟数字银行卡, 众安,汇立 两家都可以</p>
<p>开户流程完全在线上完成：</p>
<ol>
<li>人在香港连上香港网络(漫游即可有wifi也行)</li>
<li>身份证和港澳通行证</li>
<li>重要的是出入境记录PDF（移民局小程序可以下载）</li>
</ol>
<p>等待审核通过即可,另外如果想要开通股票账户,需要在香港进行申请,回到大陆就不能申请</p>
<p>目前我的众安已经通过,汇立还在审核中,它们的官方网站如下:</p>
<ul>
<li>众安: https://bank.za.group/hk</li>
<li>汇立: https://www.welab.bank/zh-CN/</li>
</ul>
<p><img src="/static/blog/hkcard/17386845208026jqt502851539b1d72ef012.jpg" alt="how-to-open-a-bank-account-in-hongkong"></p>
<h1>香港银行开户一些常见问题</h1>
<p>我在办卡,查了不少小红书和youtube的攻略记录在了备忘录
这里整理分享一下,不过需要注意随着政策变化也会变</p>
<p>Q: <strong>没有投资证明可以开户吗？</strong></p>
<p>A: 不确定,如果是以投资作为开户目的, 关键是要让银行觉得你有合理的开户需求，或者有投资理财需求,所以我准备了东方财富的电子对账单,因为有人分享以储蓄为目的被拒了</p>
<p>Q: <strong>一定要预约吗？可以直接去排队吗？</strong></p>
<p>A: 强烈建议预约！我去的是工作日汇丰都排了不少人，周末肯定更多,虽然中银工作日人少,但想要一天办两张卡,不预约了心里真没底,担心白跑一趟。</p>
<p>Q: <strong>存多少钱才能激活卡？</strong></p>
<p>A: 其实没有固定金额要求。我汇丰存了100港币，中银存了1000港币就可以了。主要是确认卡能正常使用。</p>
<p>Q: <strong>蓝狮子一定要申请吗？</strong></p>
<p>A: 基本一定吧,如果真的有需求用上了汇丰,那么肯定就要用蓝狮子,我个人觉得很值得，毕竟申请也不麻烦，在家用APP就能搞定。</p>
<p>Q: <strong>办理当天能拿到卡吗？</strong></p>
<p>A: 红狮子卡（基础卡）是可以当场拿到的。蓝狮子和万事达扣账卡需要后续申请，会寄到你在内地的地址。</p>
<p>Q: <strong>地址证明一定要信用卡账单吗？</strong></p>
<p>A: 肯定不一定。虽然我用的是信用卡账单,但我在面谈听工作人员没有指定要信用卡账单，听说水电煤气费账单、手机账单等只要能证明你的居住地址都可以。重点是要有盖章。</p>
<p>Q: <strong>可以用这些卡在内地网购吗？</strong></p>
<p>A: 红狮子不行，但蓝狮子和万事达扣账卡可以,我只绑定过微信</p>
<p>Q: <strong>香港银行开户要多少钱？</strong></p>
<p>A: 开户本身是免费的。主要花费在交通上,不要被黄牛给骗了,就相当于去香港旅游了。</p>
<p>Q: <strong>办完卡多久能收到蓝狮子？</strong></p>
<p>A: 汇丰蓝狮子用EMS发货,一般3天就能到。中银的万事达扣账卡用平邮会慢一些,建议加钱改挂号信,这样可以查进度,也能打电话给快递公司</p>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>hongkong bank</category>
        </item>
        <item>
            <title><![CDATA[xterm 配置: 一行命令搞定，开箱即用，和vscode终端效果一样]]></title>
            <link>https://blog.taoluyuan.com/blog/effortless-xterm-configuration-one-command-setup-out-of-the-box</link>
            <guid>https://blog.taoluyuan.com/blog/effortless-xterm-configuration-one-command-setup-out-of-the-box</guid>
            <pubDate>Mon, 02 Sep 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[
          <p><strong>xterm 一个高效、简洁、好用的终端，这篇博文介绍xterm的懒人配置，只需一行命令即可配置，且开箱即用。配合 oh my zsh 使用还挺漂亮,和vscode终端效果一样的体验</strong></p>
          <p>xterm 广泛运用于各种Linux操作系统。它轻量、快捷、可高度定制，非常适合需要高效终端体验的用户</p>
<p>不过它自带的效果不够好看,本配置文件提供 简洁清爽的色彩效果,和vscode终端效果一样 ,懒人配置,开箱即用</p>
<h2>xterm 配置文件详解</h2>
<p>以下是完整的.xresources配置文件:</p>
<pre><code class="language-shell">! XTerm resources
!
! Remember to run `xrdb &#x3C; .Xresources` after changing anything.
!
! Tavis Ormandy &#x3C;taviso@gmail.com>

! Set the default UI font (menus, toolbar, etc)
XTerm*XftFont:                  Segoe UI:size=10:antialias=true:style=Regular

! Color of UI Components
XTerm*.SimpleMenu.background:   #ffffff
XTerm*.SimpleMenu.foreground:   #000000

! Tone down the Xaw3D effect.
XTerm*shapeStyle:               Rectangle
XTerm*beNiceToColormap:         false
XTerm*relief:                   None
XTerm*highlightThickness:       0

! Dont automatically jump to the bottom on output, but do on keypress.
XTerm*scrollTtyOutput:          false
XTerm*scrollKey:                true
XTerm*scrollBar:                false
XTerm*eightBitInput:            false
XTerm*cursorBlink:              true

! Dont allow mouse ops - it can be enabled in the view Menu at runtime.
! The reason is it breaks copy/paste if an application requests mouse events,
! because the application gets the event instead of XTerm.
XTerm*allowMouseOps:            false

! Dont change my title by default, this can be re-enabled from the menu.
XTerm*allowTitleOps:            false
XTerm*allowFontOps:             false

! Mouse cursor configuration, I prefer to keep it simple.
XTerm*pointerColor:             white
XTerm*pointerColorBackground:   black
XTerm*pointerShape:             left_ptr

! Only select text, not empty space around it.
XTerm*highlightSelection:       true

! The distance between the edge of the screen and the characters.
XTerm*vt100.internalBorder:     6

! The border around elements.
XTerm*.BorderColor:             #ffffff
XTerm*vt100.BorderColor:        #d0d0d0
XTerm*BorderWidth:              3

! Menu Names, I dont like the default verbose names.
XTerm*.mainMenu.label:          File
XTerm*.vtMenu.label:            Options
XTerm*.fontMenu.label:          View

! You can rename menu entries, like this, I dont know why the menubar
! is called a "toolbar", probably historical reasons.
XTerm*.mainMenu.toolbar.label:  Menubar

! Adjust how the active menu item looks
XTerm*.SimpleMenu.*.shadowWidth: 3
XTerm*.SimpleMenu.*.topShadowPixel: SkyBlue
XTerm*.SimpleMenu.*.bottomShadowPixel: LightSkyBlue

! Try to hide the useless title displayed at the top of menus, I know what
! menu it is because I just clicked it.
XTerm*.*.menuLabel.vertSpace:   0
XTerm*.*.menuLabel.foreground:  #ffffff
XTerm*.*.menuLabel.XftFont:     Courier:size=0

! Space around menu entries.
XTerm*.SimpleMenu.*.topMargin:  0
XTerm*.SimpleMenu.*.bottomMargin: 0
XTerm*.SimpleMenu.*.leftMargin: 32
XTerm*.SimpleMenu.*.rightMargin: 64
! The height of each menu entry, the default calculates it from font size.
! XTerm*.SimpleMenu.*.rowHeight: 16

! Space around the checkmark in the menus.
XTerm*.SimpleMenu.*.leftWhitespace: 16
!XTerm*.SimpleMenu.*.rightWhitespace: 8

! Trim vertical padding around the toolbar, all elements have a form.
XTerm*Form.menubar.vSpace:      0
XTerm*Form.menubar.vertDistance: 0
!XTerm*Form.menubar.foreground: #000000
!XTerm*Form.menubar.background: #ffffff
!XTerm*Form.menubar.Thickness:  0

! Fonts
XTerm*vt100.faceName:           xft:Consolas:size=10:antialias=true:style=Regular
XTerm*vt100.boldFont:           xft:Consolas:size=10:antialias=true:style=Bold

! Size and Title
XTerm*vt100.geometry:           100x30
XTerm*title:                    Terminal
XTerm*iconHint:                 /home/taviso/.icons/computer_x11.xpm

! Terminal Colors
XTerm*vt100.background:     #1E1E1E
XTerm*vt100.foreground:     #D4D4D4
XTerm*cursorColor:          #FFFFFF

! I like bold items to be in bright white.
XTerm*veryBoldColors:       4
XTerm*colorBDMode:          true
XTerm*colorBD:              #ffffff
XTerm*colorRV:              #ffffff

! I can see a small line between box characters, this fixes it.
XTerm*forceBoxChars:        true
XTerm*vt100.scaleHeight:    1.04

! This (or similar id) is required for Sixel support.
XTerm*decTerminalID:        vt382

! Color Scheme
! 0-7:  Black, Red, Green, Yellow, Blue, Purple, Cyan, White
! 8-15: Bright Black, Bright Red, ...
XTerm*color0:               #000000
XTerm*color8:               #808080
XTerm*color1:               #CD3131
XTerm*color9:               #F14C4C
XTerm*color2:               #0DBC79
XTerm*color10:              #23D18B
XTerm*color3:               #CDCD00
XTerm*color11:              #FFFF00
XTerm*color4:               #2472C8
XTerm*color12:              #3B8EEA
XTerm*color5:               #BC3FBC
XTerm*color13:              #D670D6
XTerm*color6:               #11A8CD
XTerm*color14:              #29B8DB
XTerm*color7:               #E5E5E5
XTerm*color15:              #FFFFFF

!  XTerm Translations, i.e. keyboard remapping.
!
! Notes:
!   ~       means that that modifier must not be asserted.
!   !       means that the listed modifiers must be in the correct state and
!               no other modifiers can be asserted.
!   None    means no modifiers can be asserted.
!   :       directs the Intrinsics to apply any standard modifiers in the event.
!   ^       is an abbreviation for the Control modifier.
!   $       is an abbreviation for Meta
!
! Example:
!   No modifiers:                          None &#x3C;event> detail
!   Any modifiers:                              &#x3C;event> detail
!   Only these modifiers:           ! mod1 mod2 &#x3C;event> detail
!   These modifiers and any others:   mod1 mod2 &#x3C;event> detail

! Below:
!
! - I have some old apps that use Alt-F4, but thats sometimes difficult to
!       press so Ctrl-Shift-F4 does the same thing.
! - Ctrl-MouseWheel can be used to increase and descrease font size.
! - Ctrl-I is totally useless, because it is literally impossible for
!       applications to differentiate it from Tab. You can remap it to some
!       other harder-to-press keybinding here, I use it for Italics in
!       wordperfect.

XTerm*vt100.translations:   #override           \n\
    Ctrl Shift &#x3C;Key>F4:     string("\033[1;3S") \n\
  ! Ctrl &#x3C;Btn4Up>:          larger-vt-font()    \n\
  ! Ctrl &#x3C;Btn5Up>:          smaller-vt-font()   \n\
    ^&#x3C;Key>I:                string("\014")      \n\
</code></pre>
<h2>xterm 如何重新加载配置文件</h2>
<p>在终端中输入以下命令以重新加载.xresources配置文件：</p>
<pre><code class="language-shell">xrdb  ~/.Xresources
</code></pre>
<p>这将会使配置文件中的所有更改都立即生效,关闭终端重新打开即可看到效果.</p>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>xterm</category>
        </item>
        <item>
            <title><![CDATA[解决 Mac 上 VSCode 和 Cursor vim模式长按不生效问题]]></title>
            <link>https://blog.taoluyuan.com/blog/mac-vscode-cursor-vim-long-press-key-repeat-fix</link>
            <guid>https://blog.taoluyuan.com/blog/mac-vscode-cursor-vim-long-press-key-repeat-fix</guid>
            <pubDate>Sat, 31 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[
          <p><strong>解决如何在 Mac 系统上解决 vscode 和 Cursor 编辑器中长按按键不生效或重复按键失效的问题,以及如何将相同的方法应用于基于 VSCode 构建的 Cursor 编辑器。特别注意的是，虽然在 VSCode 中可以轻松解决 Vim 长按问题，但在 Cursor 中可能需要额外步骤。</strong></p>
          <h2>解决 Mac 上 VSCode vim模式 长按不生效问题</h2>
<p>在 Mac 系统上，VSCode 默认可能不支持长按按键重复功能。要启用这个功能，请按照以下步骤操作：</p>
<ol>
<li>打开终端 输入相应的命令：</li>
</ol>
<pre><code class="language-Bash"># 标准版 VSCode
defaults write com.microsoft.VSCode ApplePressAndHoldEnabled -bool false

# VSCode Insider 版
defaults write com.microsoft.VSCodeInsiders ApplePressAndHoldEnabled -bool false

# VS Codium
defaults write com.vscodium ApplePressAndHoldEnabled -bool false

# VS Codium Exploration 用户
defaults write com.microsoft.VSCodeExploration ApplePressAndHoldEnabled -bool false

# 全局设置（慎用）
defaults delete -g ApplePressAndHoldEnabled
</code></pre>
<ol start="2">
<li>执行完命令后，重启 VSCode。</li>
</ol>
<h2>Cursor vim模式长按不生效问题的解决方案</h2>
<p>Cursor 虽说是一个基于 VSCode 编辑器，上述可能对它不起作用。以下是针对 Cursor 的解决方法：</p>
<ol>
<li>首先，我们需要获取 Cursor 的应用 ID。在终端中运行：</li>
</ol>
<pre><code class="language-Bash">osascript -e 'id of app "Cursor"'
</code></pre>
<ol start="2">
<li>这会返回类似 <code>com.todesktop.230313mzl4w4u92</code> 的 ID。</li>
<li>然后，使用这个 ID 运行以下命令(ID需要替换成你自己的)：</li>
</ol>
<pre><code class="language-Bash">defaults write com.todesktop.230313mzl4w4u92 ApplePressAndHoldEnabled -bool false
</code></pre>
<ol start="4">
<li>完全退出 Cursor和VScode (从 Dock 中退出，而不仅仅是关闭窗口)</li>
<li>重新启动 Cursor 和 VSCode。</li>
</ol>
<p>如果你想使用一行命令完成这个操作，可以尝试：</p>
<pre><code class="language-Bash">defaults write "$(osascript -e 'id of app "Cursor"')" ApplePressAndHoldEnabled -bool false
</code></pre>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>cursor</category>
            <category>vscode</category>
            <category>vim</category>
        </item>
        <item>
            <title><![CDATA[使用Keepass实现跨平台密码同步：详细方案]]></title>
            <link>https://blog.taoluyuan.com/blog/keepass-cross-platform-password-sync-guide</link>
            <guid>https://blog.taoluyuan.com/blog/keepass-cross-platform-password-sync-guide</guid>
            <pubDate>Sun, 11 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[
          <p><strong>使用免费、开源的Keepass替代1Password和LastPass，实现跨平台密码同步。涵盖PC端使用KeepassXC、手机端使用Strongbox的具体步骤，并提供相关工具的下载链接。安全、免费地管理和同步密码</strong></p>
          <h2>背景</h2>
<p>最近一直在用 iOS 的 Safari 上网，发现一些网站密码只存在 LastPass，但它切换设备要收费，替换它刻不容缓，虽然现在都 2024 年了。</p>
<h2>有以下几个端需要同步：</h2>
<ol>
<li>Mac + 谷歌浏览器</li>
<li>Ubuntu + 谷歌浏览器</li>
<li>iPhone + Safari 浏览器</li>
<li>Windows + 谷歌浏览器（偶尔用也要有）</li>
<li>安卓 + 谷歌浏览器（基本不用也要有）</li>
</ol>
<h2>为什么选择Keepass而不是1Password或LastPass？</h2>
<p><code>在寻找替代1Password和LastPass的过程中，我发现Keepass具有以下优势</code>:</p>
<ol>
<li><strong>免费使用</strong>：与1Password和LastPass不同，Keepass免费。这对于我需要多设备同步但不想支付高额费用的用户来说，是很理想选择。</li>
<li><strong>开源安全</strong>：Keepass是开源软件，其代码公开透明，相比之下，1Password和LastPass本质是它们的云存储和同步。</li>
</ol>
<h2>实现过程</h2>
<p>上网查了资料后，Keepass满足了我的需求，大概理解了下：</p>
<ol>
<li>Keepass 的所有密码是存在一个后缀为 kdbx 的数据库加密文件里。</li>
<li>必须有一个正确的主密钥，才能访问和操作 kdbx 内容，主密钥可以是文本密码。</li>
<li>各个端使用 Keepass 兼容的图形软件工具，它们能做到添加、编辑、在网站自动填充密码，本质上就是操作 kdbx 文件。</li>
</ol>
<h2>思路</h2>
<ul>
<li>只要在一个端设置账号密码，其他端同步过去，就能实现共用。</li>
<li>实现 kdbx <code>文件云同步</code>是关键，文件同步我选择使用 OneDrive，单独申请了个账号，专用存 kdbx 文件，它免费额度有 5G，根本用不完。</li>
<li>主密钥我用的是一个文本密码，记在了脑子里。</li>
</ul>
<h2>最终的同步方案：</h2>
<h3>PC + 谷歌浏览器 使用 KeepassXC</h3>
<ol>
<li>PC + 谷歌浏览器用的 <code>KeepassXC</code>，它提供浏览器插件，支持识别 URL 写入和填充账号密码。</li>
<li>同步文件用的 <code>OneDrive</code>，KeepassXC 把 kdbx 数据设置在 OneDrive 对应的云文件夹里即可，会自动同步。
<ul>
<li>Mac 和 Windows 有现成的图形化软件。</li>
<li>Ubuntu 我用的是命令行安装，它默认的云文件路径是在 ~/OneDrive，需要后台运行 <code>onedrive --synchronize</code> 命令才能一直进行同步操作。</li>
</ul>
</li>
</ol>
<h3>手机使用 Strongbox</h3>
<ol>
<li>iOS 我使用了 <code>KeePassium</code> 和 <code>Strongbox</code>，最终选择了 <code>Strongbox</code>，原因是 <code>Strongbox</code> 免费版自动填充效果比较好。</li>
<li>虽然 <code>KeePassium</code> 和 <code>Strongbox</code> 软件自身都集成了 <code>OneDrive</code> 云同步的设置，但本质上还是第三方授权拉 OneDrive 的数据。</li>
<li>我试了下直接用手机装额外的 OneDrive，直接打开 OneDrive 的文件速度会快一些，也就是说没有用 Strongbox 的云同步功能（KeePassium 也是一样），而是像打开本地文件一样打开 OneDrive 的文件。</li>
</ol>
<h2>ubuntu安装使用命令行版本的OneDrive</h2>
<h3>安装</h3>
<p>OneDrive Mac 和 Windows 跟着官方客户端安装即可
Ubuntu我使用的是命令行版本，建议按照以下安装文档进行安装:
https://github.com/abraunegg/onedrive/blob/master/docs/ubuntu-package-install.md</p>
<h3>使用</h3>
<ol>
<li>执行 <code>onedrive</code> 命令，会提示你打开一个网址，然后登录 OneDrive，获得授权码链接，输入到终端里,如果出现4**错误,要考虑是否没安装对版本</li>
<li>安装完成后，需要先执行 <code>onedrive --synchronize</code> 命令，第一次同步会把 OneDrive 的文件同步到本地。</li>
<li>之后可以使用 <code>onedrive --monitor</code> 命令，后台一直监控文件变化,考虑加入开机启动,这样密码文件就能一直同步了。</li>
<li>如果已经连接了一段时间,可以使用 <code>onedrive --reauth</code> 命令，重新同步一次,会与第一步一样,重新登录授权。</li>
</ol>
<h2>结论</h2>
<ol>
<li>OneDrive 用来同步，需要自己记一下账号密码。</li>
<li>不能丢 kdbx 的主密钥，如果你存的密码很重要，建议你把它当加密钱包一样保存。</li>
<li>目前我都是免费用这几个工具，KeePassium 和 Strongbox 有进阶版收费，但免费版对我来说很够用。</li>
<li>安卓的我用的不多，不过我看大家推荐最多的还是 Keepass2Android，有需要的可以试一试。</li>
<li>本来也想付费用1Password，现在发现它是替换1Password和LastPass的好选择。</li>
</ol>
<h2>工具链接</h2>
<ul>
<li><a href="https://keepass.info/download.html">Keepass</a> 它的下载页面会推荐不同的平台工具</li>
<li><a href="https://keepassxc.org/">KeepassXC</a></li>
<li><a href="https://keepassium.com/">KeePassium</a></li>
<li><a href="https://strongboxsafe.com/">Strongbox</a></li>
<li><a href="https://onedrive.live.com/about/zh-cn/signin/">OneDrive</a></li>
<li><a href="https://github.com/abraunegg/onedrive/blob/onedrive-v2.5.0-release-candidate-1/docs/install.md">OneDrive ubuntu install</a> onedrive ubuntu 安装文档</li>
<li><a href="https://packages.ubuntu.com/focal/onedrive">OneDrive ubuntu 20.04</a> ubuntu 20.04版本对应的安装包</li>
<li><a href="https://play.google.com/store/apps/details?id=keepass2android.keepass2android">Keepass2Android</a></li>
</ul>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>password manager</category>
        </item>
        <item>
            <title><![CDATA[如何在 Notion 中设置重复任务和提醒]]></title>
            <link>https://blog.taoluyuan.com/blog/how-to-set-recurring-tasks-and-reminders-in-notion</link>
            <guid>https://blog.taoluyuan.com/blog/how-to-set-recurring-tasks-and-reminders-in-notion</guid>
            <pubDate>Mon, 08 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[
          <p><strong>How to Set Recurring Tasks and Reminders in Notion,在Notion中怎么设置重复任何和提醒,Notion的重复任务并不能和飞书任务一样丝滑,比如自动重复设置任务:飞书是刷新任务,Notion是定时任务启动</strong></p>
          <h1>Notion设置重复任务和提醒</h1>
<h2>重复任务</h2>
<p>有些任务我们并不是每天都需要创建,想利用 Notion 自动创建每天的固定任务<br>
需要用到 重复任务Repeat功能,具体实现如下</p>
<h3>1. 创建一个日常任务数据库</h3>
<p><img src="/static/blog/notion/1.png" alt=""></p>
<h3>2. 进行Reapeat设置</h3>
<p>这里对爬山任务 进行一周一次的重复创建设置</p>
<p>具体步骤为:右上角的New旁边的三个点,New Telmplate,其中 Due 字段选Today(当天)</p>
<p><img src="/static/blog/notion/2.png" alt=""></p>
<p><img src="/static/blog/notion/3.png" alt=""></p>
<p>以上设置成功后,就会在每周日的早上6点创建一个新的爬山任务,状态是待完成</p>
<p>虽然Notion的重复任务创建可以了,想要实现每天创建一个固定任务,并且在固定任务结束前提醒.这块难住了我,而飞书任务就比较好设置.</p>
<h3>Notion任务对比飞书任务</h3>
<blockquote>
<p>场景:比如我想要两天跑步一次</p>
</blockquote>
<p><strong>飞书任务</strong></p>
<p>飞书任务可以做到:设置一个两天后到期的跑步任务<br>
当点击完成后,会立马刷新一个新的任务,两天后到期.可以设置每天固定时间提醒,也可以设置到期前多久提醒</p>
<p><strong>Notion</strong></p>
<p>按照我目前Notion的设置,经过我的尝试,Notion只能按照固定时间提醒设置,也就是说不能设置每天多少点到期提醒</p>
<h3>最终设置重复提醒方案</h3>
<p>Notion Reapeat触发的时候会创建任务,如果创建的时候指定用户,会发送通知.</p>
<p>所以最终我是选择在数据库新增一个 people 类型的字段,默认选中自己.</p>
<p>这样就实现了任务创建时发送通知,而不是任务结束前发送通知,有点小遗憾</p>
<p><img src="/static/blog/notion/4.png" alt=""></p>
<h3>说明</h3>
<ul>
<li>Notion的任务机制我没有继续研究下去了,虽然是属于曲线救国的满足.但其实也够用了</li>
</ul>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>Notion</category>
        </item>
        <item>
            <title><![CDATA[Solving the Issue of Site Logo Not Showing on Google Search Results]]></title>
            <link>https://blog.taoluyuan.com/blog/website-icon-not-displaying</link>
            <guid>https://blog.taoluyuan.com/blog/website-icon-not-displaying</guid>
            <pubDate>Mon, 10 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[
          <p><strong>Why is my website icon not displaying in search engines like Google and Bing, while it shows up in the browser tab? Learn about the requirements for site logos to be square and have side lengths that are multiples of 48px, such as 48x48px, 96x96px, 144x144px, or a square SVG with a 1:1 aspect ratio.</strong></p>
          <h1>Why is My Website Icon Not Displaying in Search Engines</h1>
<p><img src="/static/blog/blog-search20240610.png" alt="Google search results are default icons"></p>
<h2>Displayed in Browser Tab but Not in Search Results</h2>
<ul>
<li>When you check your site using site:yourwebsite.com, the site logo is not showing on Google search results, but it is displayed on the browser tab.</li>
<li>This indicates that the path to your site logo icon is correct, but Google has either not yet successfully fetched it or the fetch time has not arrived.</li>
</ul>
<h2>New Sites Need to Wait</h2>
<p>If it's a new website, you need to wait for Google to fetch the icon. Check after some time to see if it appears.</p>
<h2>Check If It Meets the Requirements for Displaying Site Logos</h2>
<p>If it's an old website or you believe the logo icon is fine but it still hasn’t shown up after a long wait, check if it meets the requirements for displaying site logos in search results.
According to Google’s search documentation:</p>
<blockquote>
<p>Site logos must be square and have side lengths that are multiples of 48px, such as 48x48px, 96x96px, 144x144px, or a square SVG with a 1:1 aspect ratio.</p>
</blockquote>
<p>For more details, you can refer to the Google Search Documentation.</p>
<h1>Solving the Issue of Site Logo Not Showing on Google Search Results</h1>
<p>My solution was to change my site logo to three square icons with different resolutions: 16x16px, 32x32px, and 64x64px.<br>
I then included them in the webpage using the link tag, as shown below:</p>
<pre><code class="language-html">&#x3C;link rel="icon" type="image/png" sizes="32x32" href="/static/favicons/favicon-32x32.ico" />
&#x3C;link rel="icon" type="image/png" sizes="16x16" href="/static/favicons/favicon-16x16.ico" />
&#x3C;link rel="icon" type="image/png" sizes="64x64" href="/static/favicons/favicon.ico" />
</code></pre>
<p>After waiting for about a day, I checked site:blog.taoluyuan.com and found that the icon was displayed.</p>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>google search</category>
        </item>
        <item>
            <title><![CDATA[解决谷歌搜索结果中不显示网站图标问题]]></title>
            <link>https://blog.taoluyuan.com/blog/why-not-show-website-icon</link>
            <guid>https://blog.taoluyuan.com/blog/why-not-show-website-icon</guid>
            <pubDate>Mon, 10 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[
          <p><strong>为什么谷歌搜索结果中不显示网站图标(logo),进入网站浏览器标签页却展示,需要了解 网站图标必须为正方形，且其边长应是 48px 的倍数，例如：48x48px、96x96px、144x144px 等（或者宽高比为 1:1 的方形 SVG）</strong></p>
          <h1>为什么谷歌搜索结果中不显示网站图标</h1>
<p><img src="/static/blog/blog-search20240610.png" alt="谷歌搜索结果只展示默认图标而不是我网站的图标"></p>
<h2>进入网站浏览器标签页却展示图标/logo/icon</h2>
<ul>
<li>
<p>通过 <code>site:yourwebsite.com</code> 查看网站搜索结果,没有展示logo icon,但是进入网站浏览器标签上 页却展示了logo icon</p>
</li>
<li>
<p>那么说明你的网站logo icon 图标路径正常的.没有被google抓取成功或抓取的时间还没到</p>
</li>
</ul>
<h2>新网站需要等待</h2>
<p>如果是新网站,需要等待 google对icon进行抓取,看一段时间后是否会出现</p>
<h2>检查是否符合显示网站图标的要求</h2>
<p>如果是老网站,或者觉得logo icon 没问题,等待时间过长还没展示图标.</p>
<p>可以看 是否符合在搜索结果中显示的网站图标的要求</p>
<p>根据google搜索文档的说明</p>
<blockquote>
<p>网站图标必须为正方形，且其边长应是 48px 的倍数，例如：48x48px、96x96px、144x144px 等（或者宽高比为 1:1 的方形 SVG）</p>
</blockquote>
<p>具体的可以看这个文档 <a href="https://developers.google.com/search/docs/appearance/favicon-in-search?hl=zh-cn">google search 文档</a></p>
<h1>解决谷歌搜索结果中不展示网站图标问题</h1>
<p>我的解决方法是,将我的网站图标改成了三种分辨率的正方形图标<br>
分别是16x16px, 32x32px, 64x64px<br>
并用 <code>link</code> 标签引入到网页中 ,对应代码如下</p>
<pre><code class="language-html">&#x3C;link rel="icon" type="image/png" sizes="32x32" href="/static/favicons/favicon-32x32.ico" />
&#x3C;link rel="icon" type="image/png" sizes="16x16" href="/static/favicons/favicon-16x16.ico" />
&#x3C;link rel="icon" type="image/png" sizes="64x64" href="/static/favicons/favicon.ico" />
</code></pre>
<p>大概等了一天多,访问 site:blog.taoluyuan.com 发现图标已经展示了</p>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>google search</category>
        </item>
        <item>
            <title><![CDATA[Hugo博客迁移,使用Next.Js实现纯前端博客]]></title>
            <link>https://blog.taoluyuan.com/blog/hugo-to-nextjs</link>
            <guid>https://blog.taoluyuan.com/blog/hugo-to-nextjs</guid>
            <pubDate>Sun, 26 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[
          <p><strong>使用 Next.Js Tailwind Daisyui实现纯前端博客,Next.js迁移原有Hugo博客数据,兼容原有Hugo路由,Hugo原有外链跳转到新Next.js项目,Next.js部署到Cloudflare Pages</strong></p>
          <p>{/*  */}</p>
<h2>选Next.Js的原因</h2>
<ul>
<li>一直想学点纯粹的前端,听朋友介绍Next.js是前端的集大成者.我听进去了,啃了新手村的文档将近一半的进度,有了点感觉,决定把博客迁移到Next.js续上这份感觉.</li>
<li>在学Next.Js期间,还用了下TailWindCss 和 DaisyUI,感觉很好用</li>
<li>找到了一个清爽的博客模板<a href="https://vercel.com/templates/next.js/tailwind-css-starter-blog">nextjs-blog-starter</a>:因为它就是使用TailWindCss,就决定用它了</li>
</ul>
<h2>安装篇</h2>
<h3>初始化Next.js博客项目</h3>
<p>拉取<a href="https://github.com/timlrx/tailwind-nextjs-starter-blog">tailwind-nextjs-starter-blog</a>
代码(你应该拉取fork到自己仓库后的代码)</p>
<pre><code>git clone https://github.com/timlrx/tailwind-nextjs-starter-blog.git
</code></pre>
<p>本地运行检验</p>
<pre><code>npm install
npm run dev
</code></pre>
<p>不出意外的话,你的博客应该已经跑起来了,访问 http://localhost:3000 就能看到效果</p>
<h2>使用DaisyUI调整Next.js样式</h2>
<p>如果只是想使用模板的原始样式,可以跳过这一步</p>
<h3>安装配置DaisyUI</h3>
<p>安装DaisyUI,具体看官方文档<a href="https://daisyui.com/docs/install/">daisyui</a>,这里做个简单的示例</p>
<pre><code>
npm install daisyui
</code></pre>
<p>在<code>tailwind.config.js</code>中添加</p>
<pre><code class="language-js:tailwind.config.js">plugins: [
    require('daisyui'),
]
</code></pre>
<p>在<code>css/tailwind.css</code>中添加</p>
<pre><code class="language-js:css/tailwind.css">@tailwind base;
@tailwind components;
@tailwind utilities;
</code></pre>
<h3>使用DaisyUI调整样式</h3>
<ul>
<li>我使用DaisyUi最多组件是 <code>button</code>,<code>menu</code>,<code>badge</code> 配合TailWindCss 将大部分的样式调整成了我喜欢的黑白配色</li>
<li>还用daisyui接管了默认主题,目前黑夜模式还要再调一下</li>
</ul>
<h2>Next.js 部署到 CloudFlare Pages</h2>
<ul>
<li>CloudFlare Pages 它免费,支持自定义域名和自动部署.</li>
<li>我使用静态的方式部署,详细部署文档在 <a href="https://developers.cloudflare.com/pages/framework-guides/nextjs/deploy-a-static-nextjs-site/">deploy-a-static-nextjs-site</a></li>
</ul>
<h3>Next.js 部署到CloudFlare Pages的步骤</h3>
<ul>
<li>登录CloudFlare,选择 Workers &#x26;&#x26; Pages菜单</li>
<li>选择 Create an application->Pages</li>
<li>选择GitHub仓库,选择分支,点击部署</li>
<li>部署的 build command 和 output directory 一般是 <code>npm run build</code> 和 <code>out</code>,根据自己项目情况填写</li>
<li>部署完成后,可以选择自定义域名,CloudFlare Pages也会提供一个域名,类似于 <code>https://your-project.pages.dev</code></li>
</ul>
<h3>Next.js 发布到CloudFlare Pages</h3>
<ul>
<li>上面的部署步骤完成之后,已经能用CloudFlare Pages的域名访问你的博客了</li>
<li>可以尝试更改mdx的内容,push你的git仓库,CloudFlare Pages会自动部署.</li>
<li>注意在CloudFlare Pages的后台看Deployment details情况,如果部署失败,会有错误信息提示,这个很重要</li>
</ul>
<p>问: 为什么Next.js部署到CloudFlare Pages图片不展示</p>
<blockquote>
<p>很可能是因为Next.js 的〈Image〉组件默认使用的是Next.js的图片优化服务,它的路径.next/**
我目前发现它在Cloudflare Pages 上不适用,所以用的原始图片路径,更改了 images的loader</p>
</blockquote>
<ul>
<li>在next.config.js中添加</li>
</ul>
<pre><code class="language-js">module.exports = {
  images: {
    loader: './my-loader.ts',
    path: '',
  },
}
</code></pre>
<ul>
<li>在项目根目录下添加 my-loader.ts,大概意思是图片使用原始路径</li>
</ul>
<pre><code class="language-ts">export default function cloudinaryLoader({
  src,
  width,
  quality = 'auto',
}: {
  src: string
  width: number
  quality?: number | string
}) {
  return `${src}`
}
</code></pre>
<h2>迁移篇</h2>
<h3>Hugo 文档迁移到Next.js</h3>
<ul>
<li>Hugo 文档迁移到Next.js,我文章不多,使用了手动拷贝原有md文档到Next.js项目的<code>blog</code>目录,后缀调整成mdx,调整文件头部信息如下即可</li>
</ul>
<pre><code>---
date: '2024-05-21'
title: '博客标题'
summary: '博客摘要'
tags: ['标签']
draft: true
---
</code></pre>
<ul>
<li>Hugo的md文件头部信息和Next.js的mdx文件头部信息大同小异,我只调整了日期格式</li>
</ul>
<h3>兼容Hugo路由</h3>
<p>问: hugo迁移到next.js 路由变了,之前的路由能兼容吗</p>
<blockquote>
<p>答:可以
Hugo的路由是<code>/posts/xxxxx</code>,Next.js的路由是<code>/blog/xxxxx</code>.原hugo博客在一些平台上有外链,我希望这些外链能跳转到新的Next.js博客依然可用,所以我在next.js添加了重写规则</p>
</blockquote>
<ul>
<li>在<code>next.config.js</code>中添加</li>
</ul>
<pre><code>async rewrites() {
      return [
        {
          source: '/posts/:path*',
          destination: '/blog/:path*', // 将所有 /posts/* 重写到 /blog/*
        },
      ]
    },
</code></pre>
<p>上面是需要依赖Node Next环境的重写规则,如果你是静态文件部署,需要在CloudFlare Pages的配置中添加路由重写规则</p>
<ul>
<li>在根目录增加 _redirects文件,让CloudFlare Pages支持路由重写</li>
</ul>
<pre><code>/posts/* /blog/:splat 200
</code></pre>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>hugo-to-nextjs</category>
            <category>nextjs</category>
        </item>
        <item>
            <title><![CDATA[ 使用golang 基于 OpenAI Embedding + qdrant 实现k8s本地知识库]]></title>
            <link>https://blog.taoluyuan.com/blog/embedding-openai</link>
            <guid>https://blog.taoluyuan.com/blog/embedding-openai</guid>
            <pubDate>Thu, 23 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[
          <p><strong>利用 OpenAI Embedding 得到 数据集 问题 向量,存入 向量数据库 qdrant ,用户 通过问题向qdrant 得到相似最高的数据,再次组装对 openai chatgpt进行提问,得到答案 </strong></p>
          <h1>使用golang 基于 OpenAI Embedding + qdrant 实现k8s本地知识库</h1>
<p>文章博客地址:<a href="https://blog.taoluyuan.com/posts/embedding-openai/">套路猿-使用golang 基于 OpenAI Embedding + qdrant 实现k8s本地知识库</a></p>
<h2>流程</h2>
<p><img src="/static/blog/blog20230527115805.png?imageMogr2/auto-orient/thumbnail/!70p/blur/9x0/quality/75" alt=""></p>
<ol>
<li>将数据集 通过 openai embedding 得到向量+组装payload,存入 qdrant</li>
<li>用户进行问题搜索,通过 openai embedding 得到向量,从 qdrant 中搜索相似度大于0.8的数据</li>
<li>从 qdrant 中取出相似度高的数据</li>
<li>将获取到的QA,组装成 prompt 向chatgpt进行提问,得到回答</li>
</ol>
<h2>向量数据库 qdrant</h2>
<ul>
<li>qdrant 是一个开源的向量搜索引擎,支持多种向量距离计算方式</li>
<li>官方文档:https://qdrant.tech/documentation/quick_start/</li>
<li>本节 介绍 qdrant 都是基于官方文档的例子,如已熟悉可以直接阅读下一节 [数据导入k8s知识库]</li>
</ul>
<h3>安装 qdrant</h3>
<p>docker 安装</p>
<pre><code>docker pull qdrant/qdrant &#x26;&#x26; \
docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant
</code></pre>
<h3>collection 说明</h3>
<p>collection 是 qdrant 中的一个概念,类似于 mysql 中的 database,用于区分不同的数据集合
官方文档:https://qdrant.tech/documentation/collections/#collections
collection 下面是 collection 字段说明,以创建 collection 为例</p>
<pre><code class="language-bash">PUT /collections/{collection_name}
{
    "name": "example_collection",
    "vectors": {
      "size": 300,
      "distance": "Cosine"
    }
}
</code></pre>
<p>name: collection 名称
vectors: 向量的配置<br>
size: 向量的维度
distance: 向量的距离计算方式,Cosine(余弦距离), Euclidean(欧式距离),Dot product(点积)<br>
如果需要将 openai embedding 后 存入 qdrant，需要将 size 设置为 1536<a href="https://openai.com/blog/new-and-improved-embedding-model">openai embedding</a></p>
<h3>插入数据</h3>
<p>这个是官网 http add point 的例子,可以看到 payload 是可以存储任意的 json 数据,这个数据可以用于后续的过滤</p>
<pre><code class="language-bash">curl -L -X PUT 'http://localhost:6333/collections/test_collection/points?wait=true' \
    -H 'Content-Type: application/json' \
    --data-raw '{
        "points": [
          {"id": 1, "vector": [0.05, 0.61, 0.76, 0.74], "payload": {"city": "Berlin" }},
          {"id": 2, "vector": [0.19, 0.81, 0.75, 0.11], "payload": {"city": ["Berlin", "London"] }},
          {"id": 3, "vector": [0.36, 0.55, 0.47, 0.94], "payload": {"city": ["Berlin", "Moscow"] }},
          {"id": 4, "vector": [0.18, 0.01, 0.85, 0.80], "payload": {"city": ["London", "Moscow"] }},
          {"id": 5, "vector": [0.24, 0.18, 0.22, 0.44], "payload": {"count": [0] }},
          {"id": 6, "vector": [0.35, 0.08, 0.11, 0.44]}
        ]
    }'
</code></pre>
<ul>
<li>id:唯一</li>
<li>vector:向量,可在HuggingFace 找相应的模型训练,获取,也可以 openai embedding 得到</li>
<li>payload:任意的自定义 json 数据</li>
</ul>
<h3>搜索数据</h3>
<p>这是 qdrant 官方搜索数据的例子</p>
<pre><code class="language-bash">curl -L -X POST 'http://localhost:6333/collections/test_collection/points/search' \
    -H 'Content-Type: application/json' \
    --data-raw '{
        "vector": [0.2,0.1,0.9,0.7],
        "limit": 3
    }'
</code></pre>
<p>vector:向量,通过 openai embedding 得到
limit:返回的数据条数</p>
<h2>数据导入k8s知识库</h2>
<pre><code class="language-golang">// 模拟数据集 question:answer
var questions = []string{
	"什么是Kubernetes中的Deployment？",
	"Kubernetes中的Service有什么作用？",
}

var answers = []string{
	"Deployment是Kubernetes中用于管理应用程序副本的资源对象。它提供了副本的声明性定义，可以实现应用程序的部署、扩展和更新。",
	"Service用于定义一组Pod的访问方式和网络策略。它为Pod提供了一个稳定的网络地址，并可以实现负载均衡、服务发现和内部通信。",
}

func main() {
// 第一步：自己创建 一个collection:  kubernetes
	var err error
	err = qdrant.Collection("kubernetes").Create(1536)
	if err != nil {
		log.Fatalln("创建collection出错:", err.Error())
	}

	points := []*pb.PointStruct{}
	// 批量 进行BuildQdrantPoint
	for index, question := range questions {
		if index &#x3C; 9 {
			continue
		}
		p, err := BuildQdrantPoint(question, answers[index])
		if err != nil {
			log.Fatalln("创建point出错:", err.Error())
		}
		fmt.Println(p.Id)
		points = append(points, p)

	}
	err = qdrant.FastQdrantClient.CreatePoints("kubernetes", points)
	if err != nil {
		log.Fatalln("批量创建point出错:", err.Error())
	}
}
</code></pre>
<ul>
<li>模拟数据集,将数据集导入到 k8s 知识数据库中</li>
<li>BuildQdrantPoint 函数是将问题和答案转换成 qdrant 的 point</li>
<li>其中 vector 是通过 openai embedding 得到的,这里使用的是 <a href="https://openai.com/blog/new-and-improved-embedding-model">openai embedding</a></li>
</ul>
<h2>搜索数据</h2>
<h3>代码实现</h3>
<pre><code class="language-golang">import (
	"fmt"

	myai "embedding-knowledge-base/ai"
	"embedding-knowledge-base/qdrant"
)

func main() {
	prompt := "什么是Kubernetes中的DaemonSet？"
	// prompt := "苹果不削皮能吃吗"
	p_vec, err := myai.SimpleGetVec(prompt)
	if err != nil {
		panic(err)
	}
	points, err := qdrant.FastQdrantClient.Search("kubernetes", p_vec)
	if err != nil {
		panic(err)
	}

	fmt.Printf("用户的问题是:%s\n", prompt)
	if points[0].Score &#x3C; 0.8 {
		fmt.Println("违规问题或者超纲问题")
		return
	}
	answer := points[0].Payload["answers"].GetStringValue()
	fmt.Printf("知识库答案是:%s\n", answer)
	tmpl := "question: %s\n" + "reference answer: %s\n"
	finalPrompt := fmt.Sprintf(tmpl, prompt, points[0].Payload["question"].GetStringValue(), answer)
	fmt.Println("------------------------")
	fmt.Printf("结合知识库参考答案:chatgpt的回答是:%s\n", myai.K8sChat(finalPrompt))
	// 不结合知识库参考答案
	fmt.Printf("不依赖本地知识库, chatgpt的直接回答是:%s\n", myai.K8sChat(prompt))
}
</code></pre>
<ul>
<li>通过 prompt 搜索qdrant 知识库,如果相似度小于 0.8,有可能是用户乱提问,或问知识库无关的问题,直接返回</li>
<li>取相似度度大于 0.8,则取第一条数据,组装成promot向gpt进行提问,得到回答</li>
<li>具体的实现可以参考 main.go 的代码</li>
</ul>
<h3>示例</h3>
<ol>
<li>问无关的问题,比如:苹果不削皮能吃吗
<img src="/static/blog/blog20230526002303.png?imageMogr2/auto-orient/interlace/1/blur/1x0/quality/70%7Cwatermark/2/text/YmxvZy50YW9sdXl1YW4uY29t/font/5a6L5L2T/fontsize/500/fill/I0E4QTBBMA==/dissolve/100/gravity/NorthWest/dx/10/dy/10" alt="">
可以看到 相似度太低,提示违规问题或者超纲问题</li>
<li>问k8s 本地知识库的问题,比如:什么是Kubernetes中的Deployment？
<img src="/static/blog/blog20230526002640.png?imageMogr2/auto-orient/interlace/1/blur/1x0/quality/70%7Cwatermark/2/text/YmxvZy50YW9sdXl1YW4uY29t/font/5a6L5L2T/fontsize/500/fill/I0E4QTBBMA==/dissolve/100/gravity/NorthWest/dx/10/dy/1" alt=""></li>
<li>问k8s本地知识库的问题,但单独向chatgpt提问,得到的答案 并不是已定与k8s相关,比如问 网关是什么<br>
<img src="/static/blog/blog20230526003436.png?imageMogr2/auto-orient/interlace/1/blur/1x0/quality/70%7Cwatermark/2/text/YmxvZy50YW9sdXl1YW4uY29t/font/5a6L5L2T/fontsize/500/fill/I0E4QTBBMA==/dissolve/100/gravity/NorthWest/dx/10/dy/10" alt=""></li>
</ol>
<ul>
<li>可以看到,红线部分,是直接将用户问题 向 chatgpt 请求 得到的答案,跟k8s无关</li>
<li>红线前面的回答:是正确的,结合k8s本地知识库,可以让回答偏向 数据集设定的主题</li>
</ul>
<h2>示例源码地址及使用</h2>
<p>源码地址:<a href="https://github.com/webws/embedding-knowledge-base">embedding-knowledge-base</a>
进入根目录,将目录 ai/common.go 的 以下 const改成自己的</p>
<pre><code class="language-golang">    SocksProxy = "socks5://127.0.0.1:1080"
	AIKey      = "your api key"
</code></pre>
<h3>docker 安装 qdrant</h3>
<pre><code class="language-shell">make install-qdrant
</code></pre>
<h3>数据集导入qdrant</h3>
<ul>
<li>导入 adrant,我这边就是模拟 了十几条k8s相关的问题,在 prebuild/prebuild.go</li>
<li>更多的数据集,需要自己用脚本抓取,然后导入qdrant</li>
</ul>
<pre><code class="language-shell">make import-qdrant
</code></pre>
<h3>搜索</h3>
<pre><code class="language-shell">make search
</code></pre>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>embedding</category>
            <category>qdrant</category>
        </item>
        <item>
            <title><![CDATA[frp 和 nginx 搭建一个内网穿透服务器]]></title>
            <link>https://blog.taoluyuan.com/blog/frp-server</link>
            <guid>https://blog.taoluyuan.com/blog/frp-server</guid>
            <pubDate>Tue, 21 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[
          <p><strong>最终实现效果:通过域名打开家里主机建设的网站</strong></p>
          <p>文章博客地址: <a href="https://blog.taoluyuan.com/posts/frp-server/">https://blog.taoluyuan.com/posts/frp-server/</a></p>
<h3>相关资料</h3>
<ul>
<li>frp下载 ：<a href="https://github.com/fatedier/frp">https://github.com/fatedier/frp</a></li>
<li>相关文档: <a href="https://github.com/fatedier/frp">https://github.com/fatedier/frp</a></li>
</ul>
<h3>下载</h3>
<p>下载地址：https://github.com/fatedier/frp/releases
选择对应的版本进行下载</p>
<pre><code class="language-shell">wget https://github.com/fatedier/frp/releases/download/v0.21.0/frp_0.21.0_linux_386.tar.gz
</code></pre>
<p>如果是windows需要下载windos版本</p>
<pre><code>wget https://github.com/fatedier/frp/releases/download/v0.21.0/frp_0.21.0_darwin_amd64.tar.gz
</code></pre>
<p>下载后、我的服务端是centos 客户端是windows</p>
<blockquote>
<p>服务端需要关注的文件是 frps、frps.ini
客户端需要关注的文件是 frpc（或者是frpc.exe）、frpc.ini</p>
</blockquote>
<p>注意，如果运行的环境是windows就要运行windows版本的，也就是exe后缀的</p>
<h3>配置服务端</h3>
<h3>配置文件</h3>
<pre><code> frps.ini
[common]
#服务端需要开启的端口（与客户端绑定的进行通信的端口）
bind_port = 7000
#服务端需要开启的端口（访问客户端web服务自定义的端口号）
vhost_http_port = 8081
auth_token = websong

type = http
custom_domains = abc.baidu.com
auth_token = websong
</code></pre>
<h4>配置文件说明</h4>
<ul>
<li>bind_port
服务端需要开启的端口</li>
<li>vhost_http_port<br>
服务端需要开启的端口</li>
<li>auth_token
需要客户端的auth_token与此一样</li>
<li>type
其实除了http还有其他参数，比如tcp,这里只讲述http，其他的请看相关文档: <a href="https://github.com/fatedier/frp">https://github.com/fatedier/frp</a></li>
<li>custom_domains
域名<br>
具体接下来在配置客户端说明</li>
</ul>
<h4>启动服务</h4>
<p>正常启动，ctrl+c能推出</p>
<pre><code>./frps -c ./frps.ini

    ```

后台启动

    ```
    nohup ./frps -c ./frps.ini &#x26;
    ```
    如果有兴趣，更可以设置成开机启动（这里不讲述）

### 配置客户端

#### 配置文件

</code></pre>
<h1>frpc.ini</h1>
<p>[common]
server_addr = 48.104.176.184
server_port = 7000
auth_token = websong</p>
<p>[web6]
type = http
local_port = 80
custom_domains =b.abc.baidu.com</p>
<pre><code>
#### 配置文件讲解

- server_addr
  对应服务器ip ,
- server_port
  与服务端配置bind_port一样
- auth_token
  与服务端配置auth_token一样
- [web6]
  这个是唯一的，假如在另外一个客户端用了web6将会报明显的错误
- local_port
  此端口，假如是80，那就是访问客户端机器的80端口
- custom_domains
  域名 这里重点说一下,这个参数可以填的域名有
</code></pre>
<p>abc.baidu.com
*.abc.baidu.com</p>
<pre><code>但是，这些域名都是需要解析到服务器ip的
\*.abc.baidu.com 这里就需要使用到域名泛解析
具体百度即可

#### 客户端启动

</code></pre>
<p>./frpc -c ./frpc.ini</p>
<pre><code>
windows

</code></pre>
<p>./frpc.exe -c ./frpc.ini</p>
<pre><code>
后台启动前面加 nohup 跟服务端一样

</code></pre>
<p>nohup ./frpc.exe -c ./frpc.ini</p>
<pre><code>
### 穿透成功

#### 启动网站

> 如果以上服务端启动，客户端启动都没问题的话
> 以客户端的配置的域名：custom_domains
> 和 服务端配置的端口vhost_http_port在浏览器打开即可
> 也就是 b.abc.baidu.com：8081
> 其实这些就相当于访问你客户端本机的
> 127.0.0.1：80 或者localhost:80,
> 这个80端口是客户端配置文件的的local_port
>
> 至此内网穿透完成

#### 但是

- 但是刚刚有没有发现，访问的是带端口的网址，b.abc.baidu.com：8081
- 如果不想带端口呢，浏览器的默认端口是80，也就是说，我把服务端vhost_http_port，配置成80就好了，当然这样能解决
- 但是如果服务器有其他程序占用80端口呢，比如nginx,总不能把nginx换成其他端口吧，那我的博客www.blog.com就因为这个得改成www.blog.com:9090 假设改成9090，所以肯定有办法公用的
- 办法就是，我们可以利用nginx的反向代理就能完成，请接下来往下看 配置nginx  
  原文地址：[http://www.taoluyuan.com/index.php/archives/42/](http://www.taoluyuan.com/index.php/archives/42/)

### 配置nginx

#### 配置文件

</code></pre>
<p>server{
listen 80;
server_name *.abc.baidu.com;
index index.php index.html index.htm default.php default.htm default.html;
root /www/wwwroot/abc/;</p>
<p>location /
{
proxy_pass http://48.104.176.184:8081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
}
}</p>
<pre><code>
###说明

如配置文件所属

- 本来是需要访问 http://b.abc.baidu.com：8081 的
- nginx使用了泛域名配置，\*.abc.baidu.com (你自己的域名肯定不一样)

</code></pre>
<p>server_name *.abc.baidu.com;</p>
<pre><code>
- 反向代理配置

</code></pre>
<p>proxy_pass http://48.104.176.184:8081;</p>
<pre><code>
ip是服务端的ip，端口是服务端配置vhost_http_port 8081

> 至于nginx的其他参数，跟平常大多数nginx配置网站参数一样
> 这里使用到了nginx泛域名解析，和反向代理

#### 完成打开网站

如果客户端配置的custom_domains是b.abc.baidu.com
在浏览器输入 b.abc.baidu.com ，就不用带端口号8081 ，应为已经被nginx反向代理了；
这样做的好处是

- 使用的时候直接在浏览器输入域名就可以，不用输入端口，用户也不用知道服务端的vhost_http_port 端口是什么，
- 让服务器其他网站的端口可以不用改；
</code></pre>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>frp</category>
        </item>
        <item>
            <title><![CDATA[通过 Github workflows CI/CD 自动化部署 Github Pages hugo 免费博客]]></title>
            <link>https://blog.taoluyuan.com/blog/github-workflows</link>
            <guid>https://blog.taoluyuan.com/blog/github-workflows</guid>
            <pubDate>Tue, 21 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[
          <p><strong>通过 github actions,将hugo 博客自动部署到github pages,并且配置自定义域名</strong></p>
          <h1>通过 Github workflows CI/CD 自动化部署 Github Pages hugo 免费博客</h1>
<p>文章博客地址：<a href="https://blog.taoluyuan.com/posts/github-workflows/">https://blog.taoluyuan.com/posts/github-workflows/</a></p>
<h2>Github Workflows 介绍</h2>
<h3>GitHub Actions 介绍</h3>
<ul>
<li>GitHub 文档：https://docs.github.com/zh/actions/learn-github-actions/understanding-github-actions</li>
<li>官方介绍:<code>GitHub Actions</code> 是一种持续集成和持续交付 (CI/CD) 平台，可用于自动执行生成、测试和部署管道。 您可以创建工作流程来构建和测试存储库的每个拉取请求，或将合并的拉取请求部署到生产环境</li>
</ul>
<h3>流程及原理介绍</h3>
<ul>
<li>本文主要介绍使用GitHub Actions 来实现自动化部署博客网站 ,静态网站生成使用的是Hugo,部署使用的是Github pages,并且使用自定义域名。
<img src="/static/blog/github-workflows.jpg?imageMogr2/auto-orient/thumbnail/!70p/blur/9x0/quality/75" alt=""></li>
</ul>
<ol>
<li>本地写hugo-blog 博客,hugo-blog 是一个hugo的博客模板,使用<code>hugo new site hugo-blog</code>命令创建,可以在里面写markdown文件</li>
<li>写好后推送到github hugo-blog 仓库,触发github actions ci/cd,执行hugo命令生成静态网站,并且推送到github-pages 仓库</li>
<li>github-pages 仓库接收到推送后,会自动部署到github pages,公网可以通过 github pages 域名 访问,也可以通过CNAME配置自定义域名访问</li>
</ol>
<h2>Github Pages 介绍</h2>
<ul>
<li>Github Pages 是一个静态网站托管服务,可以通过github pages 托管静态网站,并且可以通过自定义域名访问</li>
<li>创建github pages 仓库,仓库名必须是<code>username.github.io</code>格式,username是你的github用户名,仓库名必须是这个,否则无法部署成功 访问地址就是 https://username.github.io</li>
<li>自定义域名访问,例如<code>www.abc.com</code>,在域名服务商添加CNAME记录,指向<code>username.github.io</code>, 然后在github pages 仓库设置中添加自定义域名, 这样通过www.abc.com 就能访问github pages</li>
<li>下面的 Actions 部分会介绍如何自动化部署到github pages,并且配置自定义域名</li>
</ul>
<h2>Hugo 介绍</h2>
<ul>
<li>Hugo 是一个静态网站生成器,可以通过markdown文件生成静态网站,官网:https://gohugo.io/</li>
<li>写好markdown文件后,执行hugo命令,在public目录生成静态网站,然后 将public目录推送到github pages 仓库</li>
<li>github actions工作流 就是通过hugo命令生成静态网站,并且推送到github pages 仓库</li>
</ul>
<h2>使用 Github Actions 自动化部署</h2>
<h3>创建 Github Actions</h3>
<p>在github 仓库中(hugo-blog)创建.github/workflows目录,并且在目录中创建deploy.yml文件,文件名可以自定义,但是后缀必须是yml,例如deploy.yml,这样就创建了一个github actions,并且会自动执行,下面介绍我的deploy.yml文件</p>
<pre><code class="language-yml">name: deploy

on:
  push:
  workflow_dispatch:
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          submodules: true
          fetch-depth: 0

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: 'latest'

      - name: Build Web
        run: hugo

      - name: Deploy Web
        uses: peaceiris/actions-gh-pages@v3
        with:
          PERSONAL_TOKEN: ${{ secrets.BLOG_TOKEN }}
          EXTERNAL_REPOSITORY: webws/webws.github.io
          PUBLISH_BRANCH: master
          PUBLISH_DIR: ./public
          commit_message: ${{ github.event.head_commit.message }}
          cname: ${{ secrets.DOMAIN }}
</code></pre>
<p>上面 GitHub Actions配置文件用于自动部署Hugo博客到我的 GitHub Pages。以下是每个步骤的功能和解释：</p>
<h4>步骤1：Checkout</h4>
<p>此步骤使用 <code>actions/checkout</code> 插件来检出 GitHub 仓库,具体使用文档地址是 <a href="https://github.com/marketplace/actions/checkout">checkout</a>
<code>submodules: true</code> 参数用于同时检出子模块，<code>fetch-depth: 0</code> 用于完整地检出所有历史记录。</p>
<h4>步骤2：Setup Hugo</h4>
<p>此步骤使用 <code>peaceiris/actions-hugo</code> 插件来安装最新版本的 Hugo。</p>
<pre><code class="language-yaml">- name: Setup Hugo
  uses: peaceiris/actions-hugo@v2
  with:
    hugo-version: 'latest'
</code></pre>
<h4>步骤3：Build Web</h4>
<p>此步骤在运行时调用 Hugo 构建静态网站，并在 <code>public</code> 目录中生成静态html文件</p>
<pre><code class="language-yaml">- name: Build Web
  run: hugo
</code></pre>
<h4>步骤4：Deploy Web</h4>
<p>此步骤使用 <code>peaceiris/actions-gh-pages</code> 插件将静态网站部署到 GitHub Pages 上。</p>
<pre><code class="language-yaml">- name: Deploy Web
  uses: peaceiris/actions-gh-pages@v3
  with:
    PERSONAL_TOKEN: ${{ secrets.BLOG_TOKEN }}
    EXTERNAL_REPOSITORY: webws/webws.github.io
    PUBLISH_BRANCH: master
    PUBLISH_DIR: ./public
    commit_message: ${{ github.event.head_commit.message }}
    cname: ${{ secrets.DOMAIN }}
</code></pre>
<p>参数的含义：</p>
<ul>
<li><code>PERSONAL_TOKEN</code>: GitHub Personal Access Tokens 用于访问 GitHub 仓库,需要 到<img src="https://github.com/settings/tokens" alt="github Personal access tokens设置"> ，添加权限 并将Token存储在仓库的 Secrets 中以供 Workflow 使用</li>
<li><code>EXTERNAL_REPOSITORY</code>: 部署到的 GitHub Pages 仓库,webws/webws.github.io 是我的github pages 仓库,需要修改为你的github pages 仓库</li>
<li><code>PUBLISH_BRANCH</code>: 要在其上部署站点的分支名称（通常为master）。</li>
<li><code>PUBLISH_DIR</code>: hugo 静态html文件目录。（在此例中，Hugo 输出位于 <code>./public</code> 目录中）。</li>
<li><code>commit_message</code>: 提交更改时使用的提交消息，从上游分支获取。</li>
<li><code>cname</code>: 自定义域名,CNAME记录,我自己的是 blog.taoluyuan.com,需要修改为你的自定义域名,如果没有,可以删除这个参数,使用默认的github pages域名也访问 webws.github.io</li>
</ul>
<p>设置 Secrets 变量,对应 yml 文件中的 PERSONAL_TOKEN 和 DOMAIN ,具体设置 在 仓库中(hugo-blog) 的 Settings -> secrets and variables->actions 中,hugo-blog 要换成你自己的仓库名</p>
<ul>
<li><code>BLOG_TOKEN</code>: GitHub Personal Access Token。</li>
<li><code>DOMAIN</code>: 你的自定义域名。</li>
</ul>
<h3>触发 Github Actions</h3>
<ul>
<li>在github 仓库中(hugo-blog)创建.md文件,并且提交到github,这样就会触发github actions,自动部署到github pages 仓库,并且可以通过自定义域名访问了</li>
<li>可以通过 仓库中 Actions 标签查看部署状态</li>
</ul>
<h3>访问 Github Pages</h3>
<ol>
<li>通过github pages域名访问, <a href="https://webws.github.io">https://webws.github.io</a>，因为我设置了自定义域名,所以这个域名会自动跳转到 <a href="https://blog.taoluyuan.com">https://blog.taoluyuan.com</a></li>
<li>通过自定义域名访问, <a href="https://blog.taoluyuan.com">https://blog.taoluyuan.com</a></li>
</ol>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>workflow</category>
            <category>github</category>
            <category>hugo</category>
            <category>blog</category>
            <category>github pages</category>
        </item>
        <item>
            <title><![CDATA[golang 使用 viper 加载配置文件 自动反序列化到结构]]></title>
            <link>https://blog.taoluyuan.com/blog/golang-viper-load</link>
            <guid>https://blog.taoluyuan.com/blog/golang-viper-load</guid>
            <pubDate>Tue, 21 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[
          <p><strong>golang使用 viper 无需设置 mapstructure tag 根据配置文件后缀 自动返序列化到结构,解决结构有下划线的字段解析不成功问题</strong></p>
          <h1></h1>
<p>文章博客地址:<a href="https://blog.taoluyuan.com/posts/golang-viper-load/">golang 使用 viper 加载配置 自动反序列化到结构</a></p>
<ul>
<li>golang使用 viper 无需设置 mapstructure tag 根据配置文件后缀 自动返序列化到结构</li>
<li>解决结构有下划线的字段解析不成功问题</li>
</ul>
<h3>viper 正常加载配置文件</h3>
<p>golang viper 其中可以用来 查找、加载和反序列化JSON、TOML、YAML、HCL、INI、envfile和格式的配置文件</p>
<p>配置文件 test_toml.toml</p>
<pre><code>http_addr = ":8082"
grpc_addr = ":8083"
jaeger_url= "http://localhost:14268/api/traces"
tracing= true
</code></pre>
<p>golang代码</p>
<pre><code class="language-golang">type ConfigTest struct {
	HttpAddr  string `json:"http_addr" toml:"http_addr" yaml:"http_addr"`
	GrpcAddr  string `json:"grpc_addr" toml:"grpc_addr" yaml:"grpc_addr"`
	JaegerUrl string `json:"jaeger_url" toml:"jaeger_url" yaml:"jaeger_url" mapstructure:"jaeger_url"`
	Tracing   bool   `toml:"tracing"  json:"tracing" yaml:"tracing" ` // opentelemetry tracing
}

// jaeger 加载配置文件
func TestSourceFile_Unmarshal(t *testing.T) {
	filePath := "./test_toml.toml"
	viper.SetConfigFile(filePath)
	if err := viper.ReadInConfig(); err != nil {
		t.Error(err)
	}

	c := &#x26;ConfigTest{}
	if err := viper.Unmarshal(c); err != nil {
		t.Error(err)
	}
	logger.Infow("Unmarshal file sucess", "v", c)
}
</code></pre>
<p>打印返序列化的配置结构</p>
<pre><code class="language-shell">{"level":"info","ts":"2023-08-27T21:35:27.041+0800","caller":"config/source_file_test.go:31","msg":"Unmarshal file sucess","v":{"http_addr":"","grpc_addr":"","jaeger_url":"http://localhost:14268/api/traces","tracing":true}}
</code></pre>
<p>可以看到带下划线的字段,不加 mapstructure 标签,是不会反序列化</p>
<h3>不加 mapstructure tag实现自动反序列化</h3>
<h4>查看viper Unmarshal 代码</h4>
<pre><code class="language-golang">func (v *Viper) Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error {
	return decode(v.AllSettings(), defaultDecoderConfig(rawVal, opts...))
}
func decode(input interface{}, config *mapstructure.DecoderConfig) error {
	decoder, err := mapstructure.NewDecoder(config)
	if err != nil {
		return err
	}
	return decoder.Decode(input)
}
func NewDecoder(config *DecoderConfig) (*Decoder, error) {
	if config.TagName == "" {
		config.TagName = "mapstructure"
	}
	// ...
}

</code></pre>
<ul>
<li>从代码看出 Viper使用的是 github.com/mitchellh/mapstructure来解析值</li>
<li>mapstructure 用于将通用的map[string]interface{}解码到对应的 Go 结构体中</li>
<li>默认情况下，mapstructure 使用结构体中字段的名称做这个映射,不区分大小写,比如 Name 字段可以映射到 name、NAME、NaMe 等等</li>
<li>如果没有指定 tagName ，则默认为 mapstructure,这也是为什么带下划线的字段不加 mapstructure 标签无法解析的原因</li>
<li>viper 中Unmarshal的第二个参数是可以指定 DecoderConfigOption 的,从而可以指定 tagName</li>
</ul>
<h4>viper根据文类型件自动解码到结构</h4>
<ol>
<li>读取文件后缀比如 toml</li>
<li>根据后缀设置 tagName</li>
<li>调用 viper.Unmarshal解析</li>
</ol>
<pre><code class="language-golang">func TestSourceFile_Unmarshal1(t *testing.T) {
	filePath := "./test_toml.toml"
	c := &#x26;ConfigTest{}
	if err := viperUnmarshal(c, filePath); err != nil {
		t.Error(err)
	}
	logger.Infow("Unmarshal file sucess", "v", c)
}

func viperUnmarshal(v interface{}, configPath string) error {
	var tagName string
	ext := filepath.Ext(configPath)
	if len(ext) > 1 {
		tagName = ext[1:]
	}
	// set decode tag_name, default is mapstructure
	decoderConfigOption := func(c *mapstructure.DecoderConfig) {
		c.TagName = tagName
	}
	cViper := viper.New()
	cViper.SetConfigFile(configPath)
	if err := cViper.ReadInConfig(); err != nil {
		return err
	}
	return cViper.Unmarshal(v, decoderConfigOption)
}
</code></pre>
<pre><code class="language-shell">{"level":"info","ts":"2023-08-27T21:35:34.553+0800","caller":"config/source_file_test.go:40","msg":"Unmarshal file sucess","v":{"http_addr":":8082","grpc_addr":":8083","jaeger_url":"http://localhost:14268/api/traces","tracing":true}}
</code></pre>
<p>我已将viper加载配置集成进自己的项目,完整example 代码可以查看 <a href="https://github.com/webws/go-moda/blob/main/config/source_file_test.go">source_file_test.go</a></p>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>scene</category>
        </item>
        <item>
            <title><![CDATA[k8s + docker 基于 kubeadm 多节点集群部署]]></title>
            <link>https://blog.taoluyuan.com/blog/install-k8s</link>
            <guid>https://blog.taoluyuan.com/blog/install-k8s</guid>
            <pubDate>Tue, 21 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[
          <p><strong>k8s + docker + flannel + kubeadmin 在 ubuntu-server 22.10 进行 多节点集群部署</strong></p>
          <h1>k8s + docker 基于 kubeadm 多节点集群部署</h1>
<p>博客文章地址:<a href="https://blog.taoluyuan.com/posts/install-k8s/">https://blog.taoluyuan.com/posts/install-k8s/</a></p>
<h2>各个节点环境准备</h2>
<p>[环境准备] 这章的操作都要在两台机器上分别执行,我准备了两台机器,如下:</p>
<ol>
<li>一台master,一台node</li>
<li>主机1(master) ip:192.168.31.122,主机2 192.168.31.166</li>
</ol>
<h3>1. 安装 docker</h3>
<p>如已经安装好docker 可跳过
docker 官方安装 https://docs.docker.com/engine/install/ubuntu/ 有点慢
清华大学 镜像安装方法 https://mirrors.tuna.tsinghua.edu.cn/help/docker-ce/</p>
<p>安装依赖</p>
<pre><code class="language-shell">sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu \
  "$(. /etc/os-release &#x26;&#x26; echo "$VERSION_CODENAME")" stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
</code></pre>
<p>安装 docker-ce</p>
<pre><code class="language-shell">sudo apt-get update
sudo apt-get install docker-ce
</code></pre>
<p>docker组授予用户根级权限,让当前登陆也可以使用docker</p>
<pre><code class="language-shell">sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker

</code></pre>
<p>镜像加速器</p>
<p>通过修改daemon配置文件/etc/docker/daemon.json修改 registry,我使用的是上海交大</p>
<pre><code class="language-shell">sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json &#x3C;&#x3C;-'EOF'
{
  "registry-mirrors": ["https://docker.mirrors.sjtug.sjtu.edu.cn/"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
</code></pre>
<h3>2. 安装 kubeadm, kubelet 和 kubectl</h3>
<p><a href="https://developer.aliyun.com/mirror/kubernetes?spm=a2c6h.13651102.0.0.560a1b11o3aTbI">阿里云官方推荐源</a></p>
<pre><code class="language-shell">apt-get update &#x26;&#x26; apt-get install -y apt-transport-https
curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -
cat &#x3C;&#x3C;EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF
sudo apt-get update
</code></pre>
<p>安装1.22.0版本</p>
<pre><code class="language-shell">apt-get install -y kubelet=1.22.0-00 kubeadm=1.22.0-00 kubectl=1.22.0-00
</code></pre>
<p>查看版本</p>
<pre><code>kubelet --version
kubeadm version
kubectl version
</code></pre>
<p>kubelet 开机自启</p>
<pre><code class="language-shell">systemctl enable kubelet
</code></pre>
<h3>3. 使用 systemd 作为 docker cgroup 驱动程序</h3>
<p>从 v1.22 开始，在使用 kubeadm 创建集群时,kubeadm 默认使用 systemd,而 docker 默认使用 cgroupfs,所以需要修改 docker 的 cgroup 驱动程序为 systemd,<a href="https://kubernetes.io/zh-cn/docs/setup/production-environment/container-runtimes/#cgroup-drivers">k8s cgroup-drivers说明</a>
打开 /etc/docker/daemon.json 文件,追加以下配置</p>
<pre><code class="language-shell">{
  "exec-opts": ["native.cgroupdriver=systemd"]
}
</code></pre>
<p>重启docker</p>
<pre><code class="language-shell">systemctl daemon-reload
systemctl restart docker
systemctl enable docker
</code></pre>
<p>查看cgroup驱动,必须是systemd,才行</p>
<pre><code class="language-shell">docker info|grep Cgroup
</code></pre>
<h3>4. swapoff 设置</h3>
<p>设置 swapoff</p>
<pre><code class="language-shell">sudo swapoff -a
</code></pre>
<p>永久设置 swapoff,注释掉swap那一行</p>
<pre><code>vim /etc/fstab
</code></pre>
<p>查看swapon,必须是空的,不然接下来的kubeadm init会报错</p>
<pre><code class="language-shell">swapon -s
</code></pre>
<h3>kubeadm 主节点 安装 k8s</h3>
<h4>kubeadm init 安装 k8s</h4>
<ol>
<li>可以先拉取镜像,这样kubeadm init的时候就不会拉取镜</li>
</ol>
<pre><code class="language-shell">sudo kubeadm config images pull --kubernetes-version=v1.22.0 --image-repository registry.aliyuncs.com/google_containers
</code></pre>
<ol start="2">
<li>执行init</li>
</ol>
<ul>
<li>--kubernetes-version 指定k8s 版本为1.22.0,</li>
<li>--image-repository 指定镜像仓库为阿里云:registry.aliyuncs.com/google_containers,因为 k8s 默认的镜像仓库是 gcr.io,国内访问不了</li>
<li>--pod-network-cidr 指定pod的网段,需要与cni插件的网段一致,否则会出现pod无法通信的问题 ,flannel的网段是 10.244.0.0/16</li>
</ul>
<pre><code class="language-shell">sudo kubeadm init --kubernetes-version=v1.22.0 --image-repository registry.aliyuncs.com/google_containers --pod-network-cidr=10.244.0.0/16
</code></pre>
<ol start="3">
<li>kubectl get nodes 查看节点状态</li>
</ol>
<pre><code class="language-shell">kubectl get nodes
</code></pre>
<p>应该会 出现 localhost:8080 was refused - did you specify the right host or port? 错误
将 /etc/kubernetes/admin.conf 拷贝到 $HOME/.kube/config</p>
<pre><code class="language-shell">mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown -R $USER:$USER $HOME/.kube
</code></pre>
<p>再查看nodes状态</p>
<h4>安装网络插件</h4>
<p>此时获取节点状态会发现有一个节点是 NotReady 状态，
而且,查看pod状态会发现,coredns 也是 Pending 状态</p>
<pre><code class="language-shell">kubectl get pods --all-namespaces
</code></pre>
<p>这是因为还没有安装网络插件,这里我选择安装 flannel</p>
<pre><code class="language-shell">kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
</code></pre>
<p>网络不好可以使用ghproxy</p>
<pre><code class="language-shell">kubectl apply -f https://ghproxy.com/https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
</code></pre>
<p>等待会再次查看pod状态,coredns状态是 Running,节点状态是 Ready</p>
<h2>子节点加入集群</h2>
<ol>
<li>master节点生成token,并且指定master节点的ip,生成join命令</li>
</ol>
<pre><code class="language-shell">kubeadm token create --print-join-command --ttl 0 --kubeconfig /etc/kubernetes/admin.conf
</code></pre>
<p>会出现 类似以下 join 命令</p>
<pre><code class="language-shell">kubeadm join 192.168.31.122:6443 --token kzmdey.dk0tcgyg4ivr8y87 --discovery-token-ca-cert-hash sha256:bc2e3252080ba81e342933955682ae119decc948fef2180e5135b0dd891e5891
</code></pre>
<ol start="2">
<li>在子节点执行上面的join命令,加入集群</li>
<li>在master节点查看节点状态</li>
</ol>
<pre><code class="language-shell"> k get nodes -o wide
</code></pre>
<p>可以看到两个节点都是 Ready 状态</p>
<pre><code>NAME    STATUS   ROLES                  AGE   VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE       KERNEL-VERSION      CONTAINER-RUNTIME
song    Ready    control-plane,master   45h   v1.22.0   192.168.31.122   &#x3C;none>        Ubuntu 22.10   5.19.0-21-generic   docker://24.0.2
song2   Ready    &#x3C;none>                 77m   v1.22.0   192.168.31.166   &#x3C;none>        Ubuntu 22.10   5.19.0-21-generic   docker://24.0.2
</code></pre>
<h2>安装相关问题排查</h2>
<ol>
<li>container runtime is not running: output: time="2023-06-08T14:09:02Z" level=fatal msg="validate service connection: CRI v1 runtime API is not implemented for endpoint "unix:///var/run/containerd/containerd.sock": rpc error: code = Unimplemented desc = unknown service runtime.v1.RuntimeService"
解决方法</li>
</ol>
<pre><code>sudo rm /etc/containerd/config.toml
sudo systemctl restart containerd
</code></pre>
<ol start="2">
<li>[WARNING Swap]: swap is enabled; production deployments should disable swap unless testing the NodeSwap feature gate of the kubelet
ubuntu系统</li>
</ol>
<pre><code>swapoff -a
</code></pre>
<ol start="3">
<li>查看pod出现错误,要是出现类似 pod cidr not assigned,如果是flannel网络插件,那么就是没有设置pod-network-cidr</li>
<li>排查kubelet 日志</li>
</ol>
<pre><code>sudo systemctl status kubelet.service
sudo journalctl -xu kubelet.service
</code></pre>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>k8s</category>
            <category>docker</category>
            <category>flannel</category>
            <category>kubeadmin</category>
        </item>
        <item>
            <title><![CDATA[golang 结合 cobra 使用 chatgpt  qdrant 实现 ai知识库 cli]]></title>
            <link>https://blog.taoluyuan.com/blog/kbai</link>
            <guid>https://blog.taoluyuan.com/blog/kbai</guid>
            <pubDate>Tue, 21 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[
          
          <h2>流程</h2>
<p><img src="/static/blog/kbai.png" alt=""></p>
<ol>
<li>将数据集 通过 openai embedding 得到向量+组装payload,存入 qdrant</li>
<li>用户进行问题搜索,通过 openai embedding 得到向量,从 qdrant 中搜索相似度大于0.8的数据</li>
<li>从 qdrant 中取出数据得到参考答案</li>
<li>将问题标题+参考答案,组装成promot 向gpt进行提问,得到偏向于 已有知识库设定的扩展知识回答</li>
</ol>
<h2>kabi 知识库的导入和搜索</h2>
<p>仓库地址:<a href="https://github.com/webws/embedding-knowledge-base">https://github.com/webws/embedding-knowledge-base</a></p>
<p>kabi 是使用 golang 基于 openai chatgpt embedding + qdrant 实现知识库的导入和问答</p>
<pre><code>❯ kabi -h
a local knowledge base, based on chatgpt and qdrant

usage:
  kbai [flags]
  kbai [command]

available commands:
  completion  generate the autocompletion script for the specified shell
  help        help about any command
  import      import data to vector database
  search      ask the knowledge base example: kbai ask --msg 'first, the chicken or the egg'

flags:
      --apikey string       openai apikey:default from env apikey
      --collection string   qdrant collection name default: kubernetes (default "kubernetes")
  -h, --help                help for kbai
      --proxy string        http client proxy default:socks5://127.0.0.1:1080  (default "socks5://127.0.0.1:1080")
      --qdrant string       qdrant address default: 127.0.0.1:6334 (default "127.0.0.1:6334")
      --vectorsize uint     qdrant vector size default: 1536 (default 1536)

use "kbai [command] --help" for more information about a command.
</code></pre>
<h5>启动向量数据库</h5>
<p>qdrant 是一个开源的向量搜索引擎,支持多种向量距离计算方式</p>
<p>docker 运行 qdrant</p>
<pre><code>docker run --rm -p 6334:6334 qdrant/qdrant
</code></pre>
<h5>kbai库导入数据到知识库</h5>
<p>clone 源码运行(后续提供二进制文件)</p>
<pre><code>git clone https://github.com/webws/embedding-knowledge-base.git

cd ./embedding-knowledge-base
</code></pre>
<p>这里使用的测试数据是k8s相关的知识库,真实数据需自己准备</p>
<p>1.设置 openai apikey</p>
<pre><code>export apikey=xxx
</code></pre>
<p>2.导入知识库(源码运行)</p>
<pre><code>go run ./ import --datafile ./example/data.json
</code></pre>
<p>data.json 数据格式如下,为 真实数据需自己准备</p>
<pre><code>[
    {
        "questions": "这是问题",
        "answers": "这是答案"
    },
]
</code></pre>
<p>说明:</p>
<pre><code class="language-text">默认的 代理 是 "socks5://127.0.0.1:1080" 自定义 可使用 --proxy 指定
</code></pre>
<h5>kbai 搜索数据</h5>
<p>搜索问题(源码执行)</p>
<pre><code> go run ./ search --msg "网关是什么"
</code></pre>
<p>回答</p>
<pre><code>the answer to the knowledge base:
在kubernetes中，网关通常指的是ingress（入 口）资源对象。ingress是一种kubernetes api对象，用于配置和管理集群中的http和https流量入口。它充当了从集群外部访问集群内部服务的入口点

results of chatgpt answers  with reference answers:
，同时提供负载均衡、ssl/tls终止和基于域名的路由等功能。ingress资源对象定义了一组规则，这些规则指定了通过特定http路径或主机名将请求路由到后端服务的方式。可以使用不同的ingress控制器实现这些规则，如nginx、traefik等。这样就可以在集群中创建多个ingress资源对象来管理不同的流量入口。

only chatgpt answers:
网关是一种网络设备，用于连接两个或多个不同类型的网络，以便实现数据以不同协议进行传递和转换。网关起到了连接不同网络之间的桥梁作用，将两个或多个网络互相连接起来，并负责数据的路由和转发。网关可以是硬件设备，如路由器，也可以是软件程序，如互联网网关。网关通常用于连接本地网络与互联网，使得局域网中的计算机能够访问互联网上的资源。除了连接不同网络的功能，网关还可以实现安全性、负载均衡、数据过滤等功能。
</code></pre>
<ol>
<li>第一个是知识库的回答(the answer to the knowledge base):</li>
<li>第二个 是结合知识库 chatgpt 的回答(results of chatgpt answers with reference answers)</li>
<li>第三个 仅chatgpt 回答</li>
</ol>
<p>可以看出 直接问chatgpt,得到的答案可能跟k8s无关,结合k8s本地知识库,可以让回答偏向 数据集设定的主题</p>
<p>如果直接搜索 与知识库无关或违规问题,将搜索不到任务数据</p>
<pre><code>go run ./ search --msg "苹果不洗能吃吗"
rearch term violation or exceeding category
</code></pre>
<h2>kabi golang 实现 ai知识库导入原理</h2>
<h4>导入</h4>
<ol>
<li>接入 qdrant 和 openai cleint</li>
<li>解释原始知识库数据 为 q(问) a(答)</li>
<li>将 问题 经过 openai embedding 得到向量+答案存入 qdrant</li>
</ol>
<p>以下是 <a href="%22https://github.com/webws/embedding-knowledge-base%22">kbai</a> go 导入逻辑代码</p>
<pre><code>            qdrantclient := qdrant.newqdrantclient(configflags.qdrant, configflags.collection, configflags.vectorsize)
			defer qdrantclient.close()
			aiclient, err := ai.newaiclient(configflags.proxy, configflags.apikey)
			if err != nil {
				return err
			}
			if err = qdrantclient.createcollection(configflags.collection, configflags.vectorsize); err != nil {
				return err
			}
			qas, err := converttoqas(datafile)
			if err != nil {
				return err
			}
			points := []*pb.pointstruct{}
			logger.infow("import", "data", qas)
			qpslenth := len(qas)
			for i, qa := range qas {
				embedding, err := aiclient.simplegetvec(qa.questions)
				if err != nil {
					logger.errorw("simplegetvec", "err", err, "question", qa.questions, "index", i, "total", qpslenth)
					return err
				}
				point := buildpoint(qa.questions, qa.answers, embedding)
				points = append(points, point)
			}
</code></pre>
<h3>搜索</h3>
<ol>
<li>问题搜索,通过 openai embedding 得到向量</li>
<li>根据向量 从 qdrant 中搜索相似度大于0.8的数据</li>
<li>根据 qdrant 里的知识库答案(参考答案) + 从 chatgpt 提问 得到扩展知识</li>
</ol>
<p>以下是 <a href="%22https://github.com/webws/embedding-knowledge-base%22">kbai</a> go 搜索代码逻辑</p>
<pre><code class="language-shell">            qdrantclient := qdrant.newqdrantclient(configflags.qdrant, configflags.collection, configflags.vectorsize)
			defer qdrantclient.close()

			aiclient, err := ai.newaiclient(configflags.proxy, configflags.apikey)
			if err != nil {
				return err
			}
			vector, err := aiclient.simplegetvec(msg)
			if err != nil {
				return err
			}
			points, err := qdrantclient.search(vector)
			if err != nil {
				logger.errorw("qdrant search fail", "err", err)
				return err
			}
			if len(points) == 0 {
				fmt.println("rearch term violation or exceeding category")
				return nil
				// return errors.new("rearch term violation or exceeding category")
			}
			// score less than 0.8, rearch term violation or exceeding category
			if points[0].score &#x3C; 0.8 {
				fmt.println("rearch term violation or exceeding category")
				return nil
				// return errors.new("rearch term violation or exceeding category")
			}

</code></pre>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>chatgpt</category>
            <category>cobra</category>
            <category>qdrant</category>
            <category>Ai</category>
        </item>
        <item>
            <title><![CDATA[golang 使用 OpenTelemetry 实现跨服务 全链路追踪]]></title>
            <link>https://blog.taoluyuan.com/blog/opentelmetry</link>
            <guid>https://blog.taoluyuan.com/blog/opentelmetry</guid>
            <pubDate>Tue, 21 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[
          <p><strong> 用户请求了一个接口,接口会调用其他grpc,http接口,或者内部的方法,这样的调用链路,如果出现了问题,我们需要快速的利用工具定位问题,OpenTelemetry就是这样一个工具</strong></p>
          <p>文章博客地址: <a href="https://blog.taoluyuan.com/posts/opentelmetry/">https://blog.taoluyuan.com/blog/opentelmetry/</a></p>
<h2>使用 OpenTelemetry 链路追踪说明</h2>
<ol>
<li>工作中常常会遇到需要查看服务调用关系,比如用户请求了一个接口</li>
<li>接口会调用其他grpc,http接口,或者内部的方法</li>
<li>这样的调用链路,如果出现了问题,我们需要快速的定位问题,这时候就需要一个工具来帮助我们查看调用链路</li>
<li>OpenTelemetry就是这样一个工具</li>
<li>本文大概以:main 函数初始化 OpenTelemetry、启动 http server、配置httpclient 请求服务 来进行说明</li>
<li>完整可执行源码在:<a href="https://github.com/webws/go-moda/tree/main/example/tracing/moda_tracing">opentelemetry-go 示例</a></li>
<li>示例代码已增加 grpc的链路追踪</li>
</ol>
<h2>服务链路关系</h2>
<h4>关系图</h4>
<p><img src="/static/blog/blog20230516224243.png?imageMogr2/auto-orient/interlace/1/blur/1x0/quality/70%7Cwatermark/2/text/YmxvZy50YW9sdXl1YW4uY29t/font/5a6L5L2T/fontsize/500/fill/I0E4QTBBMA==/dissolve/100/gravity/NorthWest/dx/10/dy/10" alt=""></p>
<h4>说明:</h4>
<ol>
<li>用户 请求 api1(echo server) 服务的 api1/bar</li>
<li>api1 调用 Grpc 服务</li>
<li>api1 调用 api2 (gin server) 服务的 api2/bar</li>
<li>api2 调用 api3 (echo server )服务的 api3/bar</li>
<li>api3 调用 内部 调用方法 bar->bar2->bar3</li>
</ol>
<h2>安装jaeger</h2>
<ol>
<li>下载jaeger:我使用的是 jaeger-all-in-one</li>
<li>启动 jaeger: ~/tool/jaeger-1.31.0-linux-amd64/jaeger-all-in-one</li>
<li>默认查看面板 地址 http://localhost:16686/</li>
<li>tracer Batcher的地址,下面代码会体现: http://localhost:14268/api/traces</li>
</ol>
<h2>初始化 全局的 OpenTelemetry</h2>
<p>这里openTelemetry 的exporter 以 jaeger 为例</p>
<pre><code class="language-go">var tracer = otel.Tracer("go-moda")
func InitJaegerProvider(jaegerUrl string, serviceName string) (func(ctx context.Context) error, error) {
	if jaegerUrl == "" {
		logger.Errorw("jaeger url is empty")
		return nil, nil
	}
	tracer = otel.Tracer(serviceName)
	exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(jaegerUrl)))
	if err != nil {
		return nil, err
	}
	tp := tracesdk.NewTracerProvider(
		tracesdk.WithBatcher(exp),
		tracesdk.WithResource(resource.NewSchemaless(
			semconv.ServiceNameKey.String(serviceName),
		)),
	)
	otel.SetTracerProvider(tp)
	// otel.SetTextMapPropagator(propagation.TraceContext{})
	b3Propagator := b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader))
	propagator := propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}, b3Propagator)
	otel.SetTextMapPropagator(propagator)
	return tp.Shutdown, nil
}
</code></pre>
<h4>说明</h4>
<ol>
<li>jaegerUrl ,如果安装的是 jaeger-all-in-one,则地址默认为 http://localhost:14268/api/traces</li>
<li>serviceName 是服务名称,这里我使用的是 api1,api2,api3</li>
<li>增加 span 可以使用 tracer.Start(ctx, "spanName")</li>
</ol>
<h2>http服务链路追踪</h2>
<p>初始化了全局的 OpenTelemetry后,在当前服务就可以使用 OpenTelemetry 的 tracer 进行链路追踪 比如</p>
<pre><code>ctx, span := tracing.Start(ctx, "service.bar")
defer span.End()

</code></pre>
<p>但如果是跨服务进行调用,比如 http server之间的调用,需要:</p>
<ol>
<li>对于 http client: 请求server的时候,将ctx(上下文) 注入到 请求头中(req header) 中</li>
<li>对于 http server: 在获取http请求时,解析 出请求头 中的 parent trace 信息 这样就可以实现跨服务链路追踪</li>
</ol>
<h3>启动 http服务开启链路追踪</h3>
<p>http服务,解析请求头中的trace信息:echo 和 gin 都有成熟的的中间件,我们在初始化的时候,将中间件加入到服务中即可,下面是 echo 和 gin启动服务的演示:</p>
<h4>echo server 示例</h4>
<pre><code class="language-go">import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho"
e := echo.New()
e.Server.Use(otelecho.Middleware("moda"))
</code></pre>
<h4>gin 举例</h4>
<pre><code class="language-go">import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
ginEngine := gin.Default()
g.GetServer().Use(otelgin.Middleware("my-server"))
</code></pre>
<h3>http client 链路追踪</h3>
<p>httpserver 启动时 通过解析 请求头 中的 parent trace 来进行链路追踪
那么在调用服务时,就需要将上下文注入到 req header 中 下面是我个人封装的 httpclient,可以参考:</p>
<pre><code class="language-go">package tracing

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"

	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

// 新增 options  http.Transport
type ClientOption struct {
	Transport *http.Transport
}

type ClientOptionFunc func(*ClientOption)

func WithClientTransport(transport *http.Transport) ClientOptionFunc {
	return func(option *ClientOption) {
		option.Transport = transport
	}
}

// CallAPI 为 http client 封装,默认使用 otelhttp.NewTransport(http.DefaultTransport)
func CallAPI(ctx context.Context, url string, method string, reqBody interface{}, option ...ClientOptionFunc) ([]byte, error) {
	clientOption := &#x26;ClientOption{}
	for _, o := range option {
		o(clientOption)
	}

	client := http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)}
	if clientOption.Transport != nil {
		client.Transport = otelhttp.NewTransport(clientOption.Transport)
	}
	var requestBody io.Reader
	if reqBody != nil {
		payload, err := json.Marshal(reqBody)
		if err != nil {
			return nil, err
		}
		requestBody = bytes.NewReader(payload)
	}
	req, err := http.NewRequestWithContext(ctx, method, url, requestBody)
	if err != nil {
		return nil, err
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	resBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return resBody, nil
}

</code></pre>
<h4>说明</h4>
<ol>
<li>上面代码中,主要是使用了 otelhttp.NewTransport(http.DefaultTransport) 将上下文注入到 req header 中</li>
<li>调用服务时,需要将上下文(ctx)传入到 CallAPI 方法</li>
</ol>
<h2>调用服务,查看链路关系</h2>
<h3>实战代码演示</h3>
<p>跨服务 链路追踪 大概说完 下面是运行实战代码,分为普通运行和docker 一键运行
查看源码位置:<a href="https://github.com/webws/go-moda/tree/main/example/tracing/moda_tracing">opentelemetry-go 示例</a></p>
<h4>普通运行</h4>
<ol>
<li>示例文件:moda_tracing下 有四个目录,分别是 api1_http,api2_http,api3_http,grpc 分别对应三个api服务 一个grpc服务</li>
<li>分别启动三个服务,进入目录 go run ./ -c ./conf.toml 即可启动服务</li>
</ol>
<h4>docker 运行</h4>
<ol>
<li>进入moda_tracing目录</li>
<li>执行 make deploy,会同时启动 jaeger,api1,api2,api3,grpc(mac 和 linux经过试验可行,win如不行可使用第一种)</li>
</ol>
<h4>查看jaeger 链路</h4>
<ol>
<li>根据上面链路关系,调用api1 等待调用完成: curl localhost:8081/api1/bar</li>
<li>打开 jaeger 面板,查看链路关系图,http://localhost:16686/</li>
</ol>
<p><img src="/static/blog/blog20230516224511.png?imageMogr2/auto-orient/interlace/1/blur/1x0/quality/70%7Cwatermark/2/text/YmxvZy50YW9sdXl1YW4uY29t/font/5a6L5L2T/fontsize/500/fill/I0E4QTBBMA==/dissolve/100/gravity/NorthWest/dx/10/dy/10" alt=""></p>
<p>可以看到对应的链路,在bar,bar2,bar3 刻意sleep 加了耗时也体现了出来</p>
        ]]></description>
            <author>address@yoursite.com (套路猿)</author>
            <category>tracing</category>
        </item>
    </channel>
</rss>