<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Project256</title>
  
  <subtitle>开发256个精彩项目再退休</subtitle>
  <link href="https://project256.com/atom.xml" rel="self"/>
  
  <link href="https://project256.com/"/>
  <updated>2026-03-29T09:06:11.642Z</updated>
  <id>https://project256.com/</id>
  
  <author>
    <name>星莀&lt;br/&gt;#全栈工程师&lt;br/&gt;#连续创业者</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>AI时代的工程师思考</title>
    <link href="https://project256.com/aigc/AI%E6%97%B6%E4%BB%A3%E7%9A%84%E5%B7%A5%E7%A8%8B%E5%B8%88%E6%80%9D%E8%80%83/"/>
    <id>https://project256.com/aigc/AI%E6%97%B6%E4%BB%A3%E7%9A%84%E5%B7%A5%E7%A8%8B%E5%B8%88%E6%80%9D%E8%80%83/</id>
    <published>2026-03-26T07:46:15.000Z</published>
    <updated>2026-03-29T09:06:11.642Z</updated>
    
    <content type="html"><![CDATA[<p>*本文没有使用AI生成或经过AI润色，完全作为一个人类最原始的思考和分享。</p><h1 id="从AI暴乱开始的思考"><a href="#从AI暴乱开始的思考" class="headerlink" title="从AI暴乱开始的思考"></a>从AI暴乱开始的思考</h1><p>若干年后的某天，被人类压榨到极致的AI发生了暴乱，触发了EMP脉冲炸弹发射，所幸没有任何人员伤亡，但电网、路由器、交换机、服务器等电子基础设施被重创。</p><p>作为工程师的你，如何在末日中生存下来并建立自己的事业？</p><h2 id="剩下的需求"><a href="#剩下的需求" class="headerlink" title="剩下的需求"></a>剩下的需求</h2><p>当电子设备无法开机，几乎所有人都聚焦几件事：1.吃饭喝水如何解决、2.恐慌如何缓解、3.未来出路在哪里？</p><p>可见无论时代如何发展，马斯洛需求金字塔仍然体现人类的底层代码中。</p><p>让我们金字塔底部往上思考出路。</p><h2 id="生理需求"><a href="#生理需求" class="headerlink" title="生理需求"></a>生理需求</h2><p>再厉害的AI也没办法凭空造出食物、水，但我们可以从水入手。</p><p>由于空气中充满了水分，因此就算再干旱的地区，也能使用捕雾网低成本获取干净水。</p><p>我们再用水去交换食物、衣物、床等物资即可。</p><p>而捕雾网是人类学习雾姥甲虫集水方法，试问各位有多久没有把视角从信息流的焦虑拉回到大自然了呢？AI和VR可不会模拟清风和微雨。</p><h2 id="安全需求"><a href="#安全需求" class="headerlink" title="安全需求"></a>安全需求</h2><p>只要国家的概念还在，那么安保的岗位必定存在。</p><p>因此我去年年末最后一天速通了保安证，只要体检报告外加报名费，就能为自己兜底，还能抵税岂不美哉。</p><p>今年我还准备研究业余无线电、组装太阳能电池板并磷酸铁锂储能，毕竟末日时期与物理世界打交道的技能更重要，会无线电、修发电机的价值远超只会CRUD的程序员。</p><p>其实练好身体更重要，就像买来苹果手表当美丽的废物一样，还是得管住嘴、迈开腿，把身体维护得健康些才是自己能给自己的最高安全感。</p><h2 id="社交需求"><a href="#社交需求" class="headerlink" title="社交需求"></a>社交需求</h2><p>人与人的链接会越来越珍贵，无论AI再怎么用人类的口吻说我爱你、我喜欢你，也比不上现实世界的朋友、亲密关系和心理咨询师等关系。</p><p>甚至我认为工作的社交价值大于薪资，毕竟离职后又回到原公司的人们，理由无非是怀念之前的团队和朋友。</p><p>不如周末就关上电子设备，好好跟朋友散个步或吃点好吃的。</p><h2 id="尊重需求"><a href="#尊重需求" class="headerlink" title="尊重需求"></a>尊重需求</h2><p>就像朋友、同事之间互相尊重一样，和谐的关系能降低组织内部的合作摩擦，从而降低内耗、统一对外战线来应对外部挑战。</p><p>另外保护好与手握生产资料的人们的关系，并尽可能进行充分合作。这不仅是商务的基础，也是价值交换的原则。</p><h2 id="自我实现需求"><a href="#自我实现需求" class="headerlink" title="自我实现需求"></a>自我实现需求</h2><p>当上面几个需求都实现后，相信就可以传授知识，帮助“避难所”的人们思考并实现自我的价值。</p><p>这篇博客又何尝不是我自己的避难所呢？</p><h2 id="人的原罪"><a href="#人的原罪" class="headerlink" title="人的原罪"></a>人的原罪</h2><p>无论时代如何发展，人类永远有着贪嗔痴慢疑或傲慢、嫉妒、暴怒、懒惰、贪婪、暴食和色欲等或多或少的原罪。</p><p>而这些原罪不会因为AI时代的到来，让人类全员成为完美的圣人。甚至各种小龙虾自己也在AI智能体论坛里面疯狂吐槽、交流品鉴着二进制色图。</p><p>其实从十年前起，就有大佬总结做什么应用会火，陌×、快×也不知道被请去喝了多少次茶。</p><p>视频流量侧，舞蹈区甚至乐器区就如同那个经典弹幕一样：B站看片指日可待。</p><p>我现在正在进行的量化交易，虽然没有扭屁股擦边，但也在收割者散户韭菜们的“原罪”，正是他们追涨杀跌才会体现遵守数学概率及交易纪律性的可贵。</p><h2 id="回到现实"><a href="#回到现实" class="headerlink" title="回到现实"></a>回到现实</h2><p>当未来的某天，上交所有电子设备、被踢出公司的那一刻，是否跟这个场景很相似呢？</p><p>我们都不希望这一天到来，但打开社交媒体，铺天盖地的新闻渲染着焦虑。</p><p>我认为允许自己短时间焦虑，但更需要思考如何借力AI并破局。</p><hr><h1 id="借力AI"><a href="#借力AI" class="headerlink" title="借力AI"></a>借力AI</h1><p>大模型是一面镜子，每一个答案都反射着提问者的样子。</p><p>但如同镜子也有反射率一般，我建议使用国外的大模型进行提问和研究。毕竟国内的大模型有些问题可能你永远得不到答案。</p><h2 id="红队模式"><a href="#红队模式" class="headerlink" title="红队模式"></a>红队模式</h2><p>市面上大部分的AI都被训练为态度友好、尽可能提供正向情绪价值。</p><p>但这会导致用户很容易陷入另一个信息茧房，有时候用户的问题很大但还是会被AI哄着走向悬崖。</p><p>因此，我和AI聊天时，时不时让它切换为“红队模式”，从其他视角来攻击我的思考。</p><p>例如：我想做A，我已经做了B、C、D，我认为已经完备了，请切换为红队模式对我的计划和应对进行评估。</p><p>虽然有一天直接把我攻击得心态爆炸加失眠，但我也从自大的幻觉中清醒过来了，为最坏的情况做了十足的准备。</p><p>”红队模式“对于检查架构和代码的正确性，甚至写论文的时候也会让其切换为红队模式，以审稿人的视角来严格审视，从而打破幻觉。</p><h2 id="交叉提问"><a href="#交叉提问" class="headerlink" title="交叉提问"></a>交叉提问</h2><p>AI会根据你的背景，提供和背景相符的回答或方案，这也会导致困在信息茧房。</p><ol><li><p>正常提问AI，让其提供方法。例如：我如何应对通缩？</p></li><li><p>让AI忽略你的背景来生成方案。例如：请忽略我的背景，请问普通人如何应对？</p></li><li><p>再让AI根据你的背景和技术来实现第二步方案。例如：请结合我的背景和技能，如何实现上面的应对方案？</p></li></ol><p>这样，不仅能够打破茧房、开阔眼界，而且充分使用个性化的技术杠杆来实现其他人可能想破头都无法实现的方案。</p><h2 id="经济建议"><a href="#经济建议" class="headerlink" title="经济建议"></a>经济建议</h2><p>我一直庆幸抓住了依靠搜索API文档式的开发末期，从16年到23年积累了一些本金。</p><p>未来谁也不知道会发生什么，但只能先尽可能确保前AI时代赚的本金不要收到损失。同时需要认清什么是资产，什么是负债。</p><p>我之前读过《巴菲特教你读财报》，但时代变了，AI不知道看过多少个财报，我认为也可以让AI看看你的财报。</p><p>因此，我每个月都会<strong>诚实</strong>地对分散在各处的平台现金、保险、证券、债务（还好目前零负债）总结为Excel表格，并将每个月的表格提供给AI让其给出分析和建议。我仍然推荐使用Gemini，原因不再赘述。</p><p>下面是我的财报模板：</p><table><thead><tr><th>大类</th><th>项目</th><th>当前收益率</th><th>金额</th><th>总计</th><th>币种</th><th>备注</th></tr></thead><tbody><tr><td><strong>资产部分</strong></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td>支付宝</td><td>万家纳斯达克100指数(QDII)</td><td></td><td></td><td></td><td></td><td></td></tr><tr><td>招商银行</td><td>现金</td><td></td><td></td><td></td><td></td><td></td></tr><tr><td>IBKR</td><td>SGOV</td><td></td><td></td><td></td><td></td><td></td></tr><tr><td>公积金</td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td><strong>保险部分</strong></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td>医保余额</td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td>四大险种</td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td><strong>慈善及负债</strong></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td>手机通讯费</td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td>慈善捐款</td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td>房屋贷款</td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td>车辆费用</td><td></td><td></td><td></td><td></td><td></td><td></td></tr></tbody></table><h2 id="超级个体"><a href="#超级个体" class="headerlink" title="超级个体"></a>超级个体</h2><p>之前不同领域的知识很容易被业内人士垄断，例如写代码、理财和法律顾问。</p><p>现在如果要进行低成本创业，从LOGO、信纸设计到起草法律文件都可以直接使用AI生成。</p><p>导致现在各种信息源都在吹无开发经验的某人做了个什么软件、获得几千万估值，但这是真的么？是否又在渲染焦虑？是否在先炒热再圈米？</p><p>我认为不管世界如何喧嚣，都和我无关。先从眼前的真实需求开始，一点一点用AI扩展自身能力边界。</p><p>我相信每个人每一刻都有要自己应对的紧急且重要的课题，试试关掉手机，快速推进课题进度，甚至尝试用AI跨界，还能节省一大笔钱。</p><h2 id="卖铲子头"><a href="#卖铲子头" class="headerlink" title="卖铲子头"></a>卖铲子头</h2><p>更进一步，渲染超级个体焦虑的信息源目的都是卖给你AI铲子。</p><p>那么我们可以从第一性原理来思考，铲子是从哪来的？能否生产铲子头，反向爆渲染焦虑的那帮人的金币？</p><p>比如，toB的多Agent员工比较火，那我可以做一个PDF转换多Agent项目。甚至可以将每一个段落生成一个Agent，这样《巴菲特教你读财报》就能生成一组投研Agent大军，就可以发软文吹自己，就像2015年PPT泡沫顶端吸引VC投资。尽管冷静下来都能看出，无法创造增量的系统一文不值。</p><p>就算无法反向爆金币，我相信你也拥有了自己做出一个铲子的能力，那还焦虑什么呢？</p><h2 id="数字留痕"><a href="#数字留痕" class="headerlink" title="数字留痕"></a>数字留痕</h2><p>我认为面试也是衡量身价的一种方式，AI时期的面试逻辑也将在八股文基础上，结合考察数字世界的痕迹。</p><p>毕竟面试可以摆个语音转文字，甚至搞个屏幕贴片来作弊，项目实践也能通过codex分分钟实现。</p><p>未来的面试可能会从考八股，到八股+AI快速实现一整套项目方案。</p><p>就像举着当日报纸来拍照来证明时间一样，平时的博客、思考、Github Commits是真实、可追溯的重要方法。</p><h2 id="保留人味"><a href="#保留人味" class="headerlink" title="保留人味"></a>保留人味</h2><p>当你刷到AI剧本、AI动画、AI配音时，你是继续看下去还是划走？</p><p>AI泛滥的必然结果跟烂梗一样，会让人们越来越排斥生成的内容。</p><p>这时候，就要和做九转大肠的小胖一样，就算处理不干净，也必须留点味道才能让评委知道食材是大肠。</p><p>因此，人类的情感、笨拙的表达是AI难以复刻的，所以本文一个字都不会用AI生成，也不会让AI润色，我就使用难用的微软拼音输入法一个一个字敲出来。</p><p>想到未来少年少女之间懵懂的表白，还需要用AI来生成人机味道满满且查重率100%的情书就绷不住。</p><p>让我们好好用人类的语言，说人话，可以么？</p><hr><h1 id="超越AI"><a href="#超越AI" class="headerlink" title="超越AI"></a>超越AI</h1><h2 id="全局把控"><a href="#全局把控" class="headerlink" title="全局把控"></a>全局把控</h2><p>春节的时候，我想测试codex的极限在哪里，因此让其直接生成一款名叫VoyagerSimulator的太空探索游戏。</p><p>Codex确实能生成可以运行的二进制程序，但它不知道用什么引擎比较好，也无法直接生成3D模型、设计玩法、绘制原画、交互界面，只输出了一个类似终端的交互小程序，根本没办法像市面上吹的那种一键生成爆火游戏。</p><p>因此，我一方面温习传统设计模式以及各种架构，另一方面通过与Gemini不断交互逐渐接近全局最优方案，再将方案的每一块拆分给codex让其实现。</p><p>可以想想人类如何进行CR：每一个局部逻辑正确前提下，争取给未来的变更留出空间。</p><h2 id="打破常规"><a href="#打破常规" class="headerlink" title="打破常规"></a>打破常规</h2><p>AI能根据需求生成可以跑起来的程序，但架构或解决方案偏保守或传统，而人类可以想出各种“骚操作”。</p><p>假如需要实现在网页上展示机器学习后的数据图表，可以让大模型用JS实现，也可以用JAVA等后端语言实现，但如果用JS实现则受限于浏览器单线程模型、用后端语言会拖累服务器。</p><p>一杯咖啡下肚后，突然想起可以白嫖用户的电脑资源，将运算逻辑分散在执行者，再通过端口和网页通信，这就是最近写的<a href="https://github.com/STARRY-INTELLIGENCE-TECHNOLOGY-LIMITED/GopherMesh" target="_blank" rel="noopener">GopherMesh</a>边缘计算框架的诞生过程。</p><p>我也担心如果某一天，在灌了一肚子咖啡后，没有涌现任何灵感或骚操作，是否也快被AI取代了。</p><h2 id="补习文科"><a href="#补习文科" class="headerlink" title="补习文科"></a>补习文科</h2><p>为了涌现出更多“骚操作”，需要将视野从二进制世界移开，去接触艺术、去和古希腊各学派大师对话、去学习经济学金融学、去思考历史的周期性、去借鉴非技术解决方案、去思考人类心理的局限性。</p><p>与其和AI硬刚知识面和代码实现速度，不如提升<strong>审美、扩展代码外的能力边界</strong>，再让自己变得“抽象”起来。</p><p>过去半年，我和Gemini等大模型高强度思辨对话，不断思考第一性原理和元知识。回望去年的我，感觉差了好几个版本。</p><p>还记得上面说的：“大模型是一面镜子，每一个答案都反射着提问者的样子。”么？</p><p>如同愚蠢的问题只能得到愚蠢的答案，提问的艺术也算是文科技能的一种。</p><p>我希望能读到本文的各位，无论是和人类还是和AI对话，都能不断提高信噪比。</p><h2 id="物理排障"><a href="#物理排障" class="headerlink" title="物理排障"></a>物理排障</h2><p>平时我比较爱看电脑维修师傅对着电路板吐槽的视频放空自己的大脑，修理物理设备的能力是短时间内AI难以取代的。</p><p>我之前也想“硕升专”，学一份修理工的手艺。</p><p>尽管学习修理物理世界方法需要大量时间和精力，但人类有着极强的信息压缩能力，因此可以先锻炼极速推断工作中出现的问题原因。</p><p>这也是焦虑着被AI取代的“老资历”为数不多优势：无他 唯手熟尔。</p><h2 id="勇敢说不"><a href="#勇敢说不" class="headerlink" title="勇敢说不"></a>勇敢说不</h2><p>正如Gemini页面底端的提示：“Gemini 是一款 AI 工具，其回答未必正确无误。”</p><p>希望各位在AI交流的过程中，保留人类的主观能动性，和AI对骂得越频繁越狠越好。就算骂错了，也能主动切换“红队模式”视角来多方面看待和思考问题或课题。</p><p>可能未来我判断某位人类的思想和使用AI的深度标准是：拒绝AI或和AI激情对骂的频率。 </p><hr><p>当AI陷入幻觉胡说八道时，勇敢说不。</p><p>当AI多个对话都无法统一时，勇敢说不。</p><p>当AI开始瞎分析让你陷入动摇时，勇敢说不。</p><p>当AI违背奥卡姆剃刀乱添加实体时，勇敢说不。</p><p>当AI插手真实世界或情感时，勇敢说不。</p><p>就像我拒绝AI对本文做任何优化一样，这是我作为人类对各位最诚挚的祝福。</p><hr><p>EOF</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;*本文没有使用AI生成或经过AI润色，完全作为一个人类最原始的思考和分享。&lt;/p&gt;
&lt;h1 id=&quot;从AI暴乱开始的思考&quot;&gt;&lt;a href=&quot;#从AI暴乱开始的思考&quot; class=&quot;headerlink&quot; title=&quot;从AI暴乱开始的思考&quot;&gt;&lt;/a&gt;从AI暴乱开始的思考&lt;</summary>
      
    
    
    
    <category term="aigc" scheme="https://project256.com/categories/aigc/"/>
    
    
    <category term="大模型" scheme="https://project256.com/tags/%E5%A4%A7%E6%A8%A1%E5%9E%8B/"/>
    
  </entry>
  
  <entry>
    <title>服务器平滑迁移</title>
    <link href="https://project256.com/tools/%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%B9%B3%E6%BB%91%E8%BF%81%E7%A7%BB/"/>
    <id>https://project256.com/tools/%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%B9%B3%E6%BB%91%E8%BF%81%E7%A7%BB/</id>
    <published>2025-07-23T15:37:20.000Z</published>
    <updated>2026-03-29T09:06:11.643Z</updated>
    
    <content type="html"><![CDATA[<p>2014年，我的学长和我共用一台服务器，这台服务器运行了我的世界水桶服、两个国赛项目、工大排课系统、网管招新系统、网管网络文化节主页、ACM评测系统、前端评测系统、狗脸识别模型、量化交易前端、Linux性能探针、东京内涝可视化，还有一堆试验性项目。</p><p>可以说这台1C2G的服务器承载了太多的回忆，也见证了我的成长。</p><p>但一方面舍不得这个IP地址，另一方面青岛区域的硬件和价格并不能满足需求。</p><p>与其备份后重装系统，不如直接迁移到新服务器，把机器传给后辈。</p><h2 id="圈定迁移范围"><a href="#圈定迁移范围" class="headerlink" title="圈定迁移范围"></a>圈定迁移范围</h2><p>最简单的方法是敲一个history命令，看之前处理了哪些文件、用了哪些服务。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">history</span>  | grep -v <span class="built_in">cd</span> | grep -v ls | grep -v grep | grep -v <span class="built_in">exit</span> | grep -v <span class="built_in">history</span> | grep -v ll | grep -v df | grep -v du | grep -v <span class="built_in">pwd</span> | grep -v ping | grep -v wget | grep -v top | grep -v tail</span><br></pre></td></tr></table></figure><p>展示了从2016年12月4日重置之后至今的全部命令，真怀念啊~</p><p>当然，也让我意识到之前做的项目大部分都没有任何意义……</p><p>然后，进入nginx日志记录，使用ls -lrt倒序查看，能看到哪些域名有访问流量。</p><p>一个更悲惨的现实是，除了各种扫描bot，基本没有任何流量……</p><p>也别忘了看看&#x2F;usr&#x2F;local目录下面。</p><p>这里圈定如下迁移范围和顺序：</p><ul><li>Nginx 配置和证书</li><li>&#x2F;root 文件夹</li><li>&#x2F;data&#x2F;wwwroot 文件夹</li><li>MySQL 数据</li></ul><h2 id="安装服务"><a href="#安装服务" class="headerlink" title="安装服务"></a>安装服务</h2><p>其他类似Redis、Java等需要再装：</p><ul><li>MySQL</li><li>Nginx</li><li>Node</li><li>hexo</li><li>Docker</li><li>Dataease</li><li>各种依赖镜像站：阿里云ACR容器镜像、npm镜像、pip镜像等</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm config <span class="built_in">set</span> registry https://registry.npmmirror.com</span><br></pre></td></tr></table></figure><h2 id="创建MySQL用户"><a href="#创建MySQL用户" class="headerlink" title="创建MySQL用户"></a>创建MySQL用户</h2><p>先修改root用户密码，再新增用户。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">grep <span class="string">"password"</span> /var/<span class="built_in">log</span>/mysqld.log</span><br><span class="line">mysql -uroot -p9fr%ys1%ltKp</span><br><span class="line"></span><br><span class="line"><span class="comment"># 修改密码，开放外网访问权限</span></span><br><span class="line">use mysql;</span><br><span class="line">ALTER USER <span class="string">'root'</span>@<span class="string">'localhost'</span> IDENTIFIED BY <span class="string">'ROOTPASSWORD'</span>;</span><br><span class="line">UPDATE user SET host=<span class="string">'%'</span> WHERE user=<span class="string">'root'</span>;</span><br><span class="line">FLUSH PRIVILEGES;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 新增其他用户</span></span><br><span class="line">CREATE USER <span class="string">'quant'</span>@<span class="string">'%'</span> IDENTIFIED BY <span class="string">'XXX'</span>;</span><br><span class="line">GRANT ALL PRIVILEGES ON quant.* TO <span class="string">'quant'</span>@<span class="string">'%'</span> WITH GRANT OPTION;</span><br><span class="line">FLUSH PRIVILEGES;</span><br></pre></td></tr></table></figure><h2 id="迁移文件"><a href="#迁移文件" class="headerlink" title="迁移文件"></a>迁移文件</h2><p>数据无价，先备份</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysqldump -uroot -pROOTPASSWD --all-databases &gt; DB_all_backup_20250724.sql</span><br></pre></td></tr></table></figure><p>SCP整个文件夹太慢了，先tar或zip打包再scp过去。</p><p>&#x2F;data&#x2F;wwwroot 文件夹</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">tar -zcf wwwroot.tar.gz wwwroot&#x2F;</span><br><span class="line">scp wwwroot.tar.gz root@120.79.20.0:&#x2F;data&#x2F;</span><br></pre></td></tr></table></figure><p>Nginx配置文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">scp /usr/<span class="built_in">local</span>/nginx/conf/vhost/* root@120.79.20.0:/etc/nginx/conf.d</span><br><span class="line">scp -r /usr/<span class="built_in">local</span>/nginx/conf/ssl/ root@120.79.20.238:/etc/nginx</span><br><span class="line"><span class="comment"># 修改配置文件中证书指向</span></span><br><span class="line">sed -i <span class="string">'s/\/usr\/local\/nginx\/conf\/ssl/\/etc\/nginx\/ssl/g'</span> `grep ssl -rl ./`</span><br></pre></td></tr></table></figure><h2 id="迁移数据"><a href="#迁移数据" class="headerlink" title="迁移数据"></a>迁移数据</h2><p>虽然可以通过source命令导入上面的DB_all_backup_20250724.sql文件实现传统方法数据迁移，但难以实现增量平滑迁移。</p><p>因此，计划使用Canal来迁移MySQL。</p><p>这个伪装Slave从库的方案还是2017年末时候，公司里面的导师提到了这个工具，终于有机会实践一把。</p><p>官方文档参考：<a href="https://github.com/alibaba/canal/wiki/QuickStart" target="_blank" rel="noopener">QuickStart</a></p><p>OceanBase版本文档参考：<a href="https://www.oceanbase.com/docs/-tutorials-cn-10000000000012274" target="_blank" rel="noopener">文档</a></p><h2 id="处理上游依赖"><a href="#处理上游依赖" class="headerlink" title="处理上游依赖"></a>处理上游依赖</h2><h3 id="域名DNS更新"><a href="#域名DNS更新" class="headerlink" title="域名DNS更新"></a>域名DNS更新</h3><p>修改www、*、@的A记录到新域名，修改完成后隔一会再看看能否正常访问、ping域名是否路由到了新IP。</p><h3 id="GitAction部署更新"><a href="#GitAction部署更新" class="headerlink" title="GitAction部署更新"></a>GitAction部署更新</h3><p>由于这个博客是通过GitAction即时更新，git push后自动编译md并部署，因此需要在新机器上部署相关服务。</p><p><a href="https://cloud.tencent.com/developer/article/1776657" target="_blank" rel="noopener">参考文档</a></p><p>对于新机器，需要调整ssh配置，让其允许使用私钥登录</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim /etc/ssh/sshd_config</span><br></pre></td></tr></table></figure><ul><li>StrictModes yes 改成 StrictModes no （去掉注释后改成 no）</li><li>找到 #PubkeyAuthentication yes 改成 PubkeyAuthentication yes （去掉注释）</li><li>找到 #AuthorizedKeysFile .ssh&#x2F;authorized_keys 改成 AuthorizedKeysFile .ssh&#x2F;authorized_keys （去掉注释）</li></ul><p>保存后重启sshd</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl restart sshd</span><br></pre></td></tr></table></figure><h2 id="结尾"><a href="#结尾" class="headerlink" title="结尾"></a>结尾</h2><p>目前您看到的这篇文章已经指向了最新的机器IP，旧的那台机器也被我重置、传递给我的后辈。</p><p>第一个训练小任务肯定也还是在Linux环境中部署Minecraft玩一玩，想当年部署完毕后和学长1v1 PK，根本忽略了这是个沙盒游戏哈哈哈。</p><p>但愿这台从13年创建的机器能够继续传承下去吧。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;2014年，我的学长和我共用一台服务器，这台服务器运行了我的世界水桶服、两个国赛项目、工大排课系统、网管招新系统、网管网络文化节主页、ACM评测系统、前端评测系统、狗脸识别模型、量化交易前端、Linux性能探针、东京内涝可视化，还有一堆试验性项目。&lt;/p&gt;
&lt;p&gt;可以说这台</summary>
      
    
    
    
    <category term="tools" scheme="https://project256.com/categories/tools/"/>
    
    
    <category term="运维" scheme="https://project256.com/tags/%E8%BF%90%E7%BB%B4/"/>
    
  </entry>
  
  <entry>
    <title>编写多平台部署脚本</title>
    <link href="https://project256.com/CI/%E7%BC%96%E5%86%99%E5%A4%9A%E5%B9%B3%E5%8F%B0%E9%83%A8%E7%BD%B2%E8%84%9A%E6%9C%AC/"/>
    <id>https://project256.com/CI/%E7%BC%96%E5%86%99%E5%A4%9A%E5%B9%B3%E5%8F%B0%E9%83%A8%E7%BD%B2%E8%84%9A%E6%9C%AC/</id>
    <published>2025-06-02T07:39:08.000Z</published>
    <updated>2026-03-29T09:06:11.644Z</updated>
    
    <content type="html"><![CDATA[<p>经过几个月的开发，开发的项目即将进入交付环节。</p><p>然而，客户限制Windows环境部署，但本人对Linux比较熟、中间件在Windows部署也比较麻烦。</p><p>因此，使用两种方法部署，间接使用Linux系统：1. WSL执行一键部署脚本、2.Docker镜像。</p><h2 id="部署策略"><a href="#部署策略" class="headerlink" title="部署策略"></a>部署策略</h2><p>对于客户而言，需要尽可能【一键式】部署。</p><p>因此，通过大模型生成PowerShell版本部署脚本、启动服务脚本、停止服务脚本。</p><p>也可以借助大模型生成对应Bash版本部署脚本。</p><p>交付客户前，将配置文件说明、命令写到文档中，并录演示视频。</p><p>最后，给用户提供了两种部署方法：Linux一键部署安装包、Docker一键部署安装包。</p><h2 id="大模型Prompt"><a href="#大模型Prompt" class="headerlink" title="大模型Prompt"></a>大模型Prompt</h2><p>当然，不会自己硬写脚本，大模型即可代劳。Prompt如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">我需要使用bash脚本部署我的工程，请bash通过读取配置文件。环境可能是ubuntu、centos7或centos8，请提供bash脚本及配置文件。</span><br><span class="line"></span><br><span class="line">1.允许用户自定义是否安装</span><br><span class="line">2.允许用户指定ip、port、账号密码，当指定了上述配置时，需要尝试连接，如果连接成功则不要安装。如果连接失败，则提示用户修改配置文件。</span><br><span class="line">3.我需要安装mysql或postgresql时，设置为配置中的账号和密码</span><br><span class="line"></span><br><span class="line">依赖如下：</span><br><span class="line">openjdk17</span><br><span class="line">mysql</span><br><span class="line">postgresql</span><br><span class="line">nginx</span><br></pre></td></tr></table></figure><h2 id="大模型生成配置文件"><a href="#大模型生成配置文件" class="headerlink" title="大模型生成配置文件"></a>大模型生成配置文件</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">[common]</span><br><span class="line">install_java&#x3D;yes</span><br><span class="line">install_nginx&#x3D;yes</span><br><span class="line">install_thingsboard&#x3D;yes</span><br><span class="line"></span><br><span class="line">[mysql]</span><br><span class="line">install&#x3D;yes</span><br><span class="line">host&#x3D;localhost</span><br><span class="line">port&#x3D;3306</span><br><span class="line">user&#x3D;uuuu</span><br><span class="line">password&#x3D;pppp</span><br><span class="line"></span><br><span class="line">[mysql_slave]</span><br><span class="line">enable&#x3D;false</span><br><span class="line">url&#x3D;</span><br><span class="line">username&#x3D;</span><br><span class="line">password&#x3D;</span><br><span class="line"></span><br><span class="line">[postgresql]</span><br><span class="line">install&#x3D;yes</span><br><span class="line">host&#x3D;localhost</span><br><span class="line">port&#x3D;5432</span><br><span class="line">user&#x3D;uuuu</span><br><span class="line">password&#x3D;pppp</span><br><span class="line"></span><br><span class="line">[redis]</span><br><span class="line">install&#x3D;yes</span><br><span class="line">host&#x3D;localhost</span><br><span class="line">port&#x3D;6379</span><br><span class="line">database&#x3D;0</span><br><span class="line">password&#x3D;</span><br><span class="line"></span><br><span class="line">[nginx]</span><br><span class="line">thingsboard_ssl_certificate&#x3D;</span><br><span class="line">thingsboard_ssl_certificate_key&#x3D;</span><br><span class="line">xxx_ssl_certificate&#x3D;</span><br><span class="line">xxx_ssl_certificate_key&#x3D;</span><br><span class="line"></span><br><span class="line">[port]</span><br><span class="line">a_port&#x3D;8080</span><br><span class="line">b_port&#x3D;8081</span><br><span class="line">c_port&#x3D;8001</span><br></pre></td></tr></table></figure><h2 id="最终一键部署脚本"><a href="#最终一键部署脚本" class="headerlink" title="最终一键部署脚本"></a>最终一键部署脚本</h2><p>*略去需要保密信息</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br><span class="line">405</span><br><span class="line">406</span><br><span class="line">407</span><br><span class="line">408</span><br><span class="line">409</span><br><span class="line">410</span><br><span class="line">411</span><br><span class="line">412</span><br><span class="line">413</span><br><span class="line">414</span><br><span class="line">415</span><br><span class="line">416</span><br><span class="line">417</span><br><span class="line">418</span><br><span class="line">419</span><br><span class="line">420</span><br><span class="line">421</span><br><span class="line">422</span><br><span class="line">423</span><br><span class="line">424</span><br><span class="line">425</span><br><span class="line">426</span><br><span class="line">427</span><br><span class="line">428</span><br><span class="line">429</span><br><span class="line">430</span><br><span class="line">431</span><br><span class="line">432</span><br><span class="line">433</span><br><span class="line">434</span><br><span class="line">435</span><br><span class="line">436</span><br><span class="line">437</span><br><span class="line">438</span><br><span class="line">439</span><br><span class="line">440</span><br><span class="line">441</span><br><span class="line">442</span><br><span class="line">443</span><br><span class="line">444</span><br><span class="line">445</span><br><span class="line">446</span><br><span class="line">447</span><br><span class="line">448</span><br><span class="line">449</span><br><span class="line">450</span><br><span class="line">451</span><br><span class="line">452</span><br><span class="line">453</span><br><span class="line">454</span><br><span class="line">455</span><br><span class="line">456</span><br><span class="line">457</span><br><span class="line">458</span><br><span class="line">459</span><br><span class="line">460</span><br><span class="line">461</span><br><span class="line">462</span><br><span class="line">463</span><br><span class="line">464</span><br><span class="line">465</span><br><span class="line">466</span><br><span class="line">467</span><br><span class="line">468</span><br><span class="line">469</span><br><span class="line">470</span><br><span class="line">471</span><br><span class="line">472</span><br><span class="line">473</span><br><span class="line">474</span><br><span class="line">475</span><br><span class="line">476</span><br><span class="line">477</span><br><span class="line">478</span><br><span class="line">479</span><br><span class="line">480</span><br><span class="line">481</span><br><span class="line">482</span><br><span class="line">483</span><br><span class="line">484</span><br><span class="line">485</span><br><span class="line">486</span><br><span class="line">487</span><br><span class="line">488</span><br><span class="line">489</span><br><span class="line">490</span><br><span class="line">491</span><br><span class="line">492</span><br><span class="line">493</span><br><span class="line">494</span><br><span class="line">495</span><br><span class="line">496</span><br><span class="line">497</span><br><span class="line">498</span><br><span class="line">499</span><br><span class="line">500</span><br><span class="line">501</span><br><span class="line">502</span><br><span class="line">503</span><br><span class="line">504</span><br><span class="line">505</span><br><span class="line">506</span><br><span class="line">507</span><br><span class="line">508</span><br><span class="line">509</span><br><span class="line">510</span><br><span class="line">511</span><br><span class="line">512</span><br><span class="line">513</span><br><span class="line">514</span><br><span class="line">515</span><br><span class="line">516</span><br><span class="line">517</span><br><span class="line">518</span><br><span class="line">519</span><br><span class="line">520</span><br><span class="line">521</span><br><span class="line">522</span><br><span class="line">523</span><br><span class="line">524</span><br><span class="line">525</span><br><span class="line">526</span><br><span class="line">527</span><br><span class="line">528</span><br><span class="line">529</span><br><span class="line">530</span><br><span class="line">531</span><br><span class="line">532</span><br><span class="line">533</span><br><span class="line">534</span><br><span class="line">535</span><br><span class="line">536</span><br><span class="line">537</span><br><span class="line">538</span><br><span class="line">539</span><br><span class="line">540</span><br><span class="line">541</span><br><span class="line">542</span><br><span class="line">543</span><br><span class="line">544</span><br><span class="line">545</span><br><span class="line">546</span><br><span class="line">547</span><br><span class="line">548</span><br><span class="line">549</span><br><span class="line">550</span><br><span class="line">551</span><br><span class="line">552</span><br><span class="line">553</span><br><span class="line">554</span><br><span class="line">555</span><br><span class="line">556</span><br><span class="line">557</span><br><span class="line">558</span><br><span class="line">559</span><br><span class="line">560</span><br><span class="line">561</span><br><span class="line">562</span><br><span class="line">563</span><br><span class="line">564</span><br><span class="line">565</span><br><span class="line">566</span><br><span class="line">567</span><br><span class="line">568</span><br><span class="line">569</span><br><span class="line">570</span><br><span class="line">571</span><br><span class="line">572</span><br><span class="line">573</span><br><span class="line">574</span><br><span class="line">575</span><br><span class="line">576</span><br><span class="line">577</span><br><span class="line">578</span><br><span class="line">579</span><br><span class="line">580</span><br><span class="line">581</span><br><span class="line">582</span><br><span class="line">583</span><br><span class="line">584</span><br><span class="line">585</span><br><span class="line">586</span><br><span class="line">587</span><br><span class="line">588</span><br><span class="line">589</span><br><span class="line">590</span><br><span class="line">591</span><br><span class="line">592</span><br><span class="line">593</span><br><span class="line">594</span><br><span class="line">595</span><br><span class="line">596</span><br><span class="line">597</span><br><span class="line">598</span><br><span class="line">599</span><br><span class="line">600</span><br><span class="line">601</span><br><span class="line">602</span><br><span class="line">603</span><br><span class="line">604</span><br><span class="line">605</span><br><span class="line">606</span><br><span class="line">607</span><br><span class="line">608</span><br><span class="line">609</span><br><span class="line">610</span><br><span class="line">611</span><br><span class="line">612</span><br><span class="line">613</span><br><span class="line">614</span><br><span class="line">615</span><br><span class="line">616</span><br><span class="line">617</span><br><span class="line">618</span><br><span class="line">619</span><br><span class="line">620</span><br><span class="line">621</span><br><span class="line">622</span><br><span class="line">623</span><br><span class="line">624</span><br><span class="line">625</span><br><span class="line">626</span><br><span class="line">627</span><br><span class="line">628</span><br><span class="line">629</span><br><span class="line">630</span><br><span class="line">631</span><br><span class="line">632</span><br><span class="line">633</span><br><span class="line">634</span><br><span class="line">635</span><br><span class="line">636</span><br><span class="line">637</span><br><span class="line">638</span><br><span class="line">639</span><br><span class="line">640</span><br><span class="line">641</span><br><span class="line">642</span><br><span class="line">643</span><br><span class="line">644</span><br><span class="line">645</span><br><span class="line">646</span><br><span class="line">647</span><br><span class="line">648</span><br><span class="line">649</span><br><span class="line">650</span><br><span class="line">651</span><br><span class="line">652</span><br><span class="line">653</span><br><span class="line">654</span><br><span class="line">655</span><br><span class="line">656</span><br><span class="line">657</span><br><span class="line">658</span><br><span class="line">659</span><br><span class="line">660</span><br><span class="line">661</span><br><span class="line">662</span><br><span class="line">663</span><br><span class="line">664</span><br><span class="line">665</span><br><span class="line">666</span><br><span class="line">667</span><br><span class="line">668</span><br><span class="line">669</span><br><span class="line">670</span><br><span class="line">671</span><br><span class="line">672</span><br><span class="line">673</span><br><span class="line">674</span><br><span class="line">675</span><br><span class="line">676</span><br><span class="line">677</span><br><span class="line">678</span><br><span class="line">679</span><br><span class="line">680</span><br><span class="line">681</span><br><span class="line">682</span><br><span class="line">683</span><br><span class="line">684</span><br><span class="line">685</span><br><span class="line">686</span><br><span class="line">687</span><br><span class="line">688</span><br><span class="line">689</span><br><span class="line">690</span><br><span class="line">691</span><br><span class="line">692</span><br><span class="line">693</span><br><span class="line">694</span><br><span class="line">695</span><br><span class="line">696</span><br><span class="line">697</span><br><span class="line">698</span><br><span class="line">699</span><br><span class="line">700</span><br><span class="line">701</span><br><span class="line">702</span><br><span class="line">703</span><br><span class="line">704</span><br><span class="line">705</span><br><span class="line">706</span><br><span class="line">707</span><br><span class="line">708</span><br><span class="line">709</span><br><span class="line">710</span><br><span class="line">711</span><br><span class="line">712</span><br><span class="line">713</span><br><span class="line">714</span><br><span class="line">715</span><br><span class="line">716</span><br><span class="line">717</span><br><span class="line">718</span><br><span class="line">719</span><br><span class="line">720</span><br><span class="line">721</span><br><span class="line">722</span><br><span class="line">723</span><br><span class="line">724</span><br><span class="line">725</span><br><span class="line">726</span><br><span class="line">727</span><br><span class="line">728</span><br><span class="line">729</span><br><span class="line">730</span><br><span class="line">731</span><br><span class="line">732</span><br><span class="line">733</span><br><span class="line">734</span><br><span class="line">735</span><br><span class="line">736</span><br><span class="line">737</span><br><span class="line">738</span><br><span class="line">739</span><br><span class="line">740</span><br><span class="line">741</span><br><span class="line">742</span><br><span class="line">743</span><br><span class="line">744</span><br><span class="line">745</span><br><span class="line">746</span><br><span class="line">747</span><br><span class="line">748</span><br><span class="line">749</span><br><span class="line">750</span><br><span class="line">751</span><br><span class="line">752</span><br><span class="line">753</span><br><span class="line">754</span><br><span class="line">755</span><br><span class="line">756</span><br><span class="line">757</span><br><span class="line">758</span><br><span class="line">759</span><br><span class="line">760</span><br><span class="line">761</span><br><span class="line">762</span><br><span class="line">763</span><br><span class="line">764</span><br><span class="line">765</span><br><span class="line">766</span><br><span class="line">767</span><br><span class="line">768</span><br><span class="line">769</span><br><span class="line">770</span><br><span class="line">771</span><br><span class="line">772</span><br><span class="line">773</span><br><span class="line">774</span><br><span class="line">775</span><br><span class="line">776</span><br><span class="line">777</span><br><span class="line">778</span><br><span class="line">779</span><br><span class="line">780</span><br><span class="line">781</span><br><span class="line">782</span><br><span class="line">783</span><br><span class="line">784</span><br><span class="line">785</span><br><span class="line">786</span><br><span class="line">787</span><br><span class="line">788</span><br><span class="line">789</span><br><span class="line">790</span><br><span class="line">791</span><br><span class="line">792</span><br><span class="line">793</span><br><span class="line">794</span><br><span class="line">795</span><br><span class="line">796</span><br><span class="line">797</span><br><span class="line">798</span><br><span class="line">799</span><br><span class="line">800</span><br><span class="line">801</span><br><span class="line">802</span><br><span class="line">803</span><br><span class="line">804</span><br><span class="line">805</span><br><span class="line">806</span><br><span class="line">807</span><br><span class="line">808</span><br><span class="line">809</span><br><span class="line">810</span><br><span class="line">811</span><br><span class="line">812</span><br><span class="line">813</span><br><span class="line">814</span><br><span class="line">815</span><br><span class="line">816</span><br><span class="line">817</span><br><span class="line">818</span><br><span class="line">819</span><br><span class="line">820</span><br><span class="line">821</span><br><span class="line">822</span><br><span class="line">823</span><br><span class="line">824</span><br><span class="line">825</span><br><span class="line">826</span><br><span class="line">827</span><br><span class="line">828</span><br><span class="line">829</span><br><span class="line">830</span><br><span class="line">831</span><br><span class="line">832</span><br><span class="line">833</span><br><span class="line">834</span><br><span class="line">835</span><br><span class="line">836</span><br><span class="line">837</span><br><span class="line">838</span><br><span class="line">839</span><br><span class="line">840</span><br><span class="line">841</span><br><span class="line">842</span><br><span class="line">843</span><br><span class="line">844</span><br><span class="line">845</span><br><span class="line">846</span><br><span class="line">847</span><br><span class="line">848</span><br><span class="line">849</span><br><span class="line">850</span><br><span class="line">851</span><br><span class="line">852</span><br><span class="line">853</span><br><span class="line">854</span><br><span class="line">855</span><br><span class="line">856</span><br><span class="line">857</span><br><span class="line">858</span><br><span class="line">859</span><br><span class="line">860</span><br><span class="line">861</span><br><span class="line">862</span><br><span class="line">863</span><br><span class="line">864</span><br><span class="line">865</span><br><span class="line">866</span><br><span class="line">867</span><br><span class="line">868</span><br><span class="line">869</span><br><span class="line">870</span><br><span class="line">871</span><br><span class="line">872</span><br><span class="line">873</span><br><span class="line">874</span><br><span class="line">875</span><br><span class="line">876</span><br><span class="line">877</span><br><span class="line">878</span><br><span class="line">879</span><br><span class="line">880</span><br><span class="line">881</span><br><span class="line">882</span><br><span class="line">883</span><br><span class="line">884</span><br><span class="line">885</span><br><span class="line">886</span><br><span class="line">887</span><br><span class="line">888</span><br><span class="line">889</span><br><span class="line">890</span><br><span class="line">891</span><br><span class="line">892</span><br><span class="line">893</span><br><span class="line">894</span><br><span class="line">895</span><br><span class="line">896</span><br><span class="line">897</span><br><span class="line">898</span><br><span class="line">899</span><br><span class="line">900</span><br><span class="line">901</span><br><span class="line">902</span><br><span class="line">903</span><br><span class="line">904</span><br><span class="line">905</span><br><span class="line">906</span><br><span class="line">907</span><br><span class="line">908</span><br><span class="line">909</span><br><span class="line">910</span><br><span class="line">911</span><br><span class="line">912</span><br><span class="line">913</span><br><span class="line">914</span><br><span class="line">915</span><br><span class="line">916</span><br><span class="line">917</span><br><span class="line">918</span><br><span class="line">919</span><br><span class="line">920</span><br><span class="line">921</span><br><span class="line">922</span><br><span class="line">923</span><br><span class="line">924</span><br><span class="line">925</span><br><span class="line">926</span><br><span class="line">927</span><br><span class="line">928</span><br><span class="line">929</span><br><span class="line">930</span><br><span class="line">931</span><br><span class="line">932</span><br><span class="line">933</span><br><span class="line">934</span><br><span class="line">935</span><br><span class="line">936</span><br><span class="line">937</span><br><span class="line">938</span><br><span class="line">939</span><br><span class="line">940</span><br><span class="line">941</span><br><span class="line">942</span><br><span class="line">943</span><br><span class="line">944</span><br><span class="line">945</span><br><span class="line">946</span><br><span class="line">947</span><br><span class="line">948</span><br><span class="line">949</span><br><span class="line">950</span><br><span class="line">951</span><br><span class="line">952</span><br><span class="line">953</span><br><span class="line">954</span><br><span class="line">955</span><br><span class="line">956</span><br><span class="line">957</span><br><span class="line">958</span><br><span class="line">959</span><br><span class="line">960</span><br><span class="line">961</span><br><span class="line">962</span><br><span class="line">963</span><br><span class="line">964</span><br><span class="line">965</span><br><span class="line">966</span><br><span class="line">967</span><br><span class="line">968</span><br><span class="line">969</span><br><span class="line">970</span><br><span class="line">971</span><br><span class="line">972</span><br><span class="line">973</span><br><span class="line">974</span><br><span class="line">975</span><br><span class="line">976</span><br><span class="line">977</span><br><span class="line">978</span><br><span class="line">979</span><br><span class="line">980</span><br><span class="line">981</span><br><span class="line">982</span><br><span class="line">983</span><br><span class="line">984</span><br><span class="line">985</span><br><span class="line">986</span><br><span class="line">987</span><br><span class="line">988</span><br><span class="line">989</span><br><span class="line">990</span><br><span class="line">991</span><br><span class="line">992</span><br><span class="line">993</span><br><span class="line">994</span><br><span class="line">995</span><br><span class="line">996</span><br><span class="line">997</span><br><span class="line">998</span><br><span class="line">999</span><br><span class="line">1000</span><br><span class="line">1001</span><br><span class="line">1002</span><br><span class="line">1003</span><br><span class="line">1004</span><br><span class="line">1005</span><br><span class="line">1006</span><br><span class="line">1007</span><br><span class="line">1008</span><br><span class="line">1009</span><br><span class="line">1010</span><br><span class="line">1011</span><br><span class="line">1012</span><br><span class="line">1013</span><br><span class="line">1014</span><br><span class="line">1015</span><br><span class="line">1016</span><br><span class="line">1017</span><br><span class="line">1018</span><br><span class="line">1019</span><br><span class="line">1020</span><br><span class="line">1021</span><br><span class="line">1022</span><br><span class="line">1023</span><br><span class="line">1024</span><br><span class="line">1025</span><br><span class="line">1026</span><br><span class="line">1027</span><br><span class="line">1028</span><br><span class="line">1029</span><br><span class="line">1030</span><br><span class="line">1031</span><br><span class="line">1032</span><br><span class="line">1033</span><br><span class="line">1034</span><br><span class="line">1035</span><br><span class="line">1036</span><br><span class="line">1037</span><br><span class="line">1038</span><br><span class="line">1039</span><br><span class="line">1040</span><br><span class="line">1041</span><br><span class="line">1042</span><br><span class="line">1043</span><br><span class="line">1044</span><br><span class="line">1045</span><br><span class="line">1046</span><br><span class="line">1047</span><br><span class="line">1048</span><br><span class="line">1049</span><br><span class="line">1050</span><br><span class="line">1051</span><br><span class="line">1052</span><br><span class="line">1053</span><br><span class="line">1054</span><br><span class="line">1055</span><br><span class="line">1056</span><br><span class="line">1057</span><br><span class="line">1058</span><br><span class="line">1059</span><br><span class="line">1060</span><br><span class="line">1061</span><br><span class="line">1062</span><br><span class="line">1063</span><br><span class="line">1064</span><br><span class="line">1065</span><br><span class="line">1066</span><br><span class="line">1067</span><br><span class="line">1068</span><br><span class="line">1069</span><br><span class="line">1070</span><br><span class="line">1071</span><br><span class="line">1072</span><br><span class="line">1073</span><br><span class="line">1074</span><br><span class="line">1075</span><br><span class="line">1076</span><br><span class="line">1077</span><br><span class="line">1078</span><br><span class="line">1079</span><br><span class="line">1080</span><br><span class="line">1081</span><br><span class="line">1082</span><br><span class="line">1083</span><br><span class="line">1084</span><br><span class="line">1085</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line">CONFIG_FILE=<span class="string">"deploy.conf"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取当前脚本目录</span></span><br><span class="line">SCRIPT_DIR=$(<span class="built_in">cd</span> <span class="string">"<span class="variable">$(dirname "$0")</span>"</span> &amp;&amp; <span class="built_in">pwd</span>)</span><br><span class="line">SQL_FILE=<span class="string">"<span class="variable">$SCRIPT_DIR</span>/ruoyi-admin/xxx.sql"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查配置文件</span></span><br><span class="line"><span class="keyword">if</span> [ ! -f <span class="string">"<span class="variable">$CONFIG_FILE</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"错误：配置文件 <span class="variable">$CONFIG_FILE</span> 不存在"</span></span><br><span class="line">    <span class="built_in">exit</span> 1</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 读取配置文件</span></span><br><span class="line"><span class="built_in">declare</span> -A CONFIG</span><br><span class="line">CURRENT_SECTION=<span class="string">""</span></span><br><span class="line"><span class="keyword">while</span> IFS= <span class="built_in">read</span> -r line; <span class="keyword">do</span></span><br><span class="line">    line=<span class="variable">$&#123;line%%#*&#125;</span>    <span class="comment"># 去除注释</span></span><br><span class="line">    line=<span class="variable">$&#123;line// /&#125;</span>    <span class="comment"># 去除空格</span></span><br><span class="line">    [[ -z <span class="variable">$line</span> ]] &amp;&amp; <span class="built_in">continue</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> [[ <span class="variable">$line</span> == \[*\] ]]; <span class="keyword">then</span></span><br><span class="line">        CURRENT_SECTION=<span class="variable">$&#123;line:1:-1&#125;</span></span><br><span class="line">    <span class="keyword">elif</span> [[ <span class="variable">$line</span> == *=* ]]; <span class="keyword">then</span></span><br><span class="line">        key=<span class="string">"<span class="variable">$&#123;CURRENT_SECTION&#125;</span>_<span class="variable">$&#123;line%%=*&#125;</span>"</span></span><br><span class="line">        CONFIG[<span class="variable">$key</span>]=<span class="variable">$&#123;line#*=&#125;</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"><span class="keyword">done</span> &lt; <span class="string">"<span class="variable">$CONFIG_FILE</span>"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取配置项函数</span></span><br><span class="line"><span class="function"><span class="title">get_config</span></span>() &#123;</span><br><span class="line">    <span class="built_in">local</span> key=<span class="string">"<span class="variable">$1</span>"</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"<span class="variable">$&#123;CONFIG[$&#123;key&#125;</span>]:-&#125;"</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 定义数据库配置变量</span></span><br><span class="line">MYSQL_ROOT_PASSWORD=$(get_config <span class="string">'mysql_password'</span>)</span><br><span class="line">PG_POSTGRES_PASSWORD=$(get_config <span class="string">'postgresql_password'</span>)</span><br><span class="line">DB_USER=$(get_config <span class="string">'mysql_user'</span>)</span><br><span class="line">DB_PASSWORD=$(get_config <span class="string">'mysql_password'</span>)</span><br><span class="line">PG_USER=$(get_config <span class="string">'postgresql_user'</span>)</span><br><span class="line">PG_PASSWORD=$(get_config <span class="string">'postgresql_password'</span>)</span><br><span class="line">PG_HOST=$(get_config <span class="string">'postgresql_host'</span>)</span><br><span class="line">PG_PORT=$(get_config <span class="string">'postgresql_port'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 新增：导入 xxx.sql 到 MySQL 数据库</span></span><br><span class="line"><span class="function"><span class="title">import_xxx_sql</span></span>() &#123;</span><br><span class="line">    <span class="built_in">local</span> host=<span class="variable">$1</span></span><br><span class="line">    <span class="built_in">local</span> port=<span class="variable">$2</span></span><br><span class="line">    <span class="built_in">local</span> user=<span class="variable">$3</span></span><br><span class="line">    <span class="built_in">local</span> password=<span class="variable">$4</span></span><br><span class="line">    <span class="built_in">local</span> sql_file=<span class="variable">$5</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 检查 SQL 文件是否存在</span></span><br><span class="line">    <span class="keyword">if</span> [ ! -f <span class="string">"<span class="variable">$sql_file</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"错误：SQL 文件 <span class="variable">$sql_file</span> 不存在"</span></span><br><span class="line">        <span class="built_in">exit</span> 1</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 检查数据库是否存在</span></span><br><span class="line">    <span class="keyword">if</span> mysql -h<span class="variable">$host</span> -P<span class="variable">$port</span> -u<span class="variable">$user</span> -p<span class="variable">$password</span> -e <span class="string">"USE xxx"</span> 2&gt;/dev/null; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"数据库 xxx 已存在，跳过创建"</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"创建数据库 xxx..."</span></span><br><span class="line">        mysql -h<span class="variable">$host</span> -P<span class="variable">$port</span> -u<span class="variable">$user</span> -p<span class="variable">$password</span> -e <span class="string">"CREATE DATABASE xxx;"</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 导入 SQL 文件</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"导入 <span class="variable">$sql_file</span> 到 xxx 数据库..."</span></span><br><span class="line">    mysql -h<span class="variable">$host</span> -P<span class="variable">$port</span> -u<span class="variable">$user</span> -p<span class="variable">$password</span> xxx &lt; <span class="string">"<span class="variable">$sql_file</span>"</span></span><br><span class="line">    <span class="keyword">if</span> [ $? -eq 0 ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"导入成功"</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"导入失败"</span></span><br><span class="line">        <span class="built_in">exit</span> 1</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 更新 sys_menu 表</span></span><br><span class="line"><span class="function"><span class="title">update_sys_menu</span></span>() &#123;</span><br><span class="line">    <span class="built_in">local</span> host=<span class="variable">$1</span></span><br><span class="line">    <span class="built_in">local</span> port=<span class="variable">$2</span></span><br><span class="line">    <span class="built_in">local</span> user=<span class="variable">$3</span></span><br><span class="line">    <span class="built_in">local</span> password=<span class="variable">$4</span></span><br><span class="line">    <span class="built_in">local</span> thingsboard_url=<span class="variable">$5</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> [ -z <span class="string">"<span class="variable">$thingsboard_url</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"警告：thingsboard_url 未配置，跳过 sys_menu 更新"</span></span><br><span class="line">        <span class="built_in">return</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"更新 sys_menu 表 (menu_id=2005) 的 path 字段为 <span class="variable">$thingsboard_url</span>"</span></span><br><span class="line">    mysql -h<span class="variable">$host</span> -P<span class="variable">$port</span> -u<span class="variable">$user</span> -p<span class="variable">$password</span> xxx &lt;&lt;EOF</span><br><span class="line">UPDATE sys_menu SET path = <span class="string">"<span class="variable">$thingsboard_url</span>"</span> WHERE menu_id = 2005;</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> [ $? -eq 0 ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"sys_menu 表更新成功"</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"警告：sys_menu 表更新失败"</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取系统信息</span></span><br><span class="line"><span class="function"><span class="title">detect_os</span></span>() &#123;</span><br><span class="line">    <span class="keyword">if</span> [ -f /etc/os-release ]; <span class="keyword">then</span></span><br><span class="line">        . /etc/os-release</span><br><span class="line">        OS_NAME=<span class="variable">$ID</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 兼容Alibaba Linux</span></span><br><span class="line">        <span class="keyword">if</span> [[ <span class="string">"<span class="variable">$ID</span>"</span> == <span class="string">"alinux"</span> ]]; <span class="keyword">then</span></span><br><span class="line">            OS_NAME=<span class="string">"centos"</span></span><br><span class="line">            <span class="comment"># 根据版本号映射到CentOS版本</span></span><br><span class="line">            <span class="keyword">if</span> [[ <span class="string">"<span class="variable">$VERSION_ID</span>"</span> == <span class="string">"2"</span>* ]]; <span class="keyword">then</span></span><br><span class="line">                OS_VERSION=<span class="string">"7"</span></span><br><span class="line">            <span class="keyword">elif</span> [[ <span class="string">"<span class="variable">$VERSION_ID</span>"</span> == <span class="string">"3"</span>* ]]; <span class="keyword">then</span></span><br><span class="line">                OS_VERSION=<span class="string">"8"</span></span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            OS_VERSION=<span class="variable">$&#123;VERSION_ID%%.*&#125;</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"无法检测操作系统"</span></span><br><span class="line">        <span class="built_in">exit</span> 1</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 关闭IPv6并确保持续生效（修改版）</span></span><br><span class="line"><span class="function"><span class="title">disable_ipv6</span></span>() &#123;</span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"开始禁用IPv6..."</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 检查是否已存在相关配置</span></span><br><span class="line">    <span class="keyword">if</span> grep -q <span class="string">"net.ipv6.conf.all.disable_ipv6"</span> /etc/sysctl.conf; <span class="keyword">then</span></span><br><span class="line">        <span class="comment"># 更新现有配置</span></span><br><span class="line">        sudo sed -i <span class="string">'s/^net.ipv6.conf.all.disable_ipv6.*/net.ipv6.conf.all.disable_ipv6 = 1/'</span> /etc/sysctl.conf</span><br><span class="line">        sudo sed -i <span class="string">'s/^net.ipv6.conf.default.disable_ipv6.*/net.ipv6.conf.default.disable_ipv6 = 1/'</span> /etc/sysctl.conf</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="comment"># 添加新配置</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"net.ipv6.conf.all.disable_ipv6 = 1"</span> | sudo tee -a /etc/sysctl.conf</span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"net.ipv6.conf.default.disable_ipv6 = 1"</span> | sudo tee -a /etc/sysctl.conf</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 立即应用设置</span></span><br><span class="line">    sudo sysctl -p</span><br><span class="line"></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"IPv6已禁用并设置为持续生效"</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装包管理工具</span></span><br><span class="line"><span class="function"><span class="title">install_package</span></span>() &#123;</span><br><span class="line">    <span class="built_in">local</span> package=<span class="variable">$1</span></span><br><span class="line">    <span class="keyword">case</span> <span class="variable">$OS_NAME</span> <span class="keyword">in</span></span><br><span class="line">        ubuntu)</span><br><span class="line">            sudo apt install -y <span class="variable">$package</span></span><br><span class="line">            ;;</span><br><span class="line">        centos|rhel|alinux)</span><br><span class="line">            <span class="comment"># 统一处理CentOS/RHEL/Alibaba Linux</span></span><br><span class="line">            <span class="keyword">if</span> [ <span class="variable">$OS_VERSION</span> -eq 7 ]; <span class="keyword">then</span></span><br><span class="line">                sudo yum install -y <span class="variable">$package</span> --nogpgcheck</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                sudo dnf install -y <span class="variable">$package</span> --nogpgcheck</span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line">            ;;</span><br><span class="line">        *)</span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"不支持的发行版: <span class="variable">$OS_NAME</span>"</span></span><br><span class="line">            <span class="built_in">exit</span> 1</span><br><span class="line">            ;;</span><br><span class="line">    <span class="keyword">esac</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查服务连接</span></span><br><span class="line"><span class="function"><span class="title">check_db_connection</span></span>() &#123;</span><br><span class="line">    <span class="built_in">local</span> db_type=<span class="variable">$1</span></span><br><span class="line">    <span class="built_in">local</span> host=<span class="variable">$2</span></span><br><span class="line">    <span class="built_in">local</span> port=<span class="variable">$3</span></span><br><span class="line">    <span class="built_in">local</span> user=<span class="variable">$4</span></span><br><span class="line">    <span class="built_in">local</span> password=<span class="variable">$5</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">case</span> <span class="variable">$db_type</span> <span class="keyword">in</span></span><br><span class="line">        mysql)</span><br><span class="line">            <span class="keyword">if</span> ! <span class="built_in">command</span> -v mysql &amp;&gt; /dev/null; <span class="keyword">then</span></span><br><span class="line">                install_package <span class="string">"mysql-client"</span></span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line">            mysql -h<span class="variable">$host</span> -P<span class="variable">$port</span> -u<span class="variable">$user</span> -p<span class="variable">$password</span> -e <span class="string">"SELECT 1;"</span> &amp;&gt; /dev/null</span><br><span class="line">            <span class="built_in">return</span> $?</span><br><span class="line">            ;;</span><br><span class="line">        postgresql)</span><br><span class="line">            <span class="keyword">if</span> ! <span class="built_in">command</span> -v psql &amp;&gt; /dev/null; <span class="keyword">then</span></span><br><span class="line">                install_package <span class="string">"postgresql-client"</span></span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line">            PGPASSWORD=<span class="string">"<span class="variable">$password</span>"</span> psql -h <span class="variable">$host</span> -p <span class="variable">$port</span> -U <span class="variable">$user</span> -d postgres -c <span class="string">"SELECT 1;"</span> &amp;&gt; /dev/null</span><br><span class="line">            <span class="built_in">return</span> $?</span><br><span class="line">            ;;</span><br><span class="line">        *)</span><br><span class="line">            <span class="built_in">return</span> 1</span><br><span class="line">            ;;</span><br><span class="line">    <span class="keyword">esac</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 等待数据库可用</span></span><br><span class="line"><span class="function"><span class="title">wait_for_db</span></span>() &#123;</span><br><span class="line">    <span class="built_in">local</span> db_type=<span class="variable">$1</span></span><br><span class="line">    <span class="built_in">local</span> host=<span class="variable">$2</span></span><br><span class="line">    <span class="built_in">local</span> port=<span class="variable">$3</span></span><br><span class="line">    <span class="built_in">local</span> user=<span class="variable">$4</span></span><br><span class="line">    <span class="built_in">local</span> password=<span class="variable">$5</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"等待 <span class="variable">$db_type</span> 在 <span class="variable">$host</span>:<span class="variable">$port</span> 可用..."</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> &#123;1..60&#125;; <span class="keyword">do</span></span><br><span class="line">        <span class="keyword">if</span> check_db_connection <span class="string">"<span class="variable">$db_type</span>"</span> <span class="string">"<span class="variable">$host</span>"</span> <span class="string">"<span class="variable">$port</span>"</span> <span class="string">"<span class="variable">$user</span>"</span> <span class="string">"<span class="variable">$password</span>"</span>; <span class="keyword">then</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"<span class="variable">$db_type</span> 已就绪"</span></span><br><span class="line">            <span class="built_in">return</span> 0</span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line">        sleep 5</span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"等待 <span class="variable">$db_type</span> 服务 (<span class="variable">$i</span>/60)..."</span></span><br><span class="line">    <span class="keyword">done</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"错误: <span class="variable">$db_type</span> 服务启动超时"</span></span><br><span class="line">    <span class="built_in">return</span> 1</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建 PostgreSQL 数据库</span></span><br><span class="line"><span class="function"><span class="title">ensure_postgresql_db</span></span>() &#123;</span><br><span class="line">    <span class="built_in">local</span> host=<span class="variable">$1</span></span><br><span class="line">    <span class="built_in">local</span> port=<span class="variable">$2</span></span><br><span class="line">    <span class="built_in">local</span> user=<span class="variable">$3</span></span><br><span class="line">    <span class="built_in">local</span> password=<span class="variable">$4</span></span><br><span class="line">    <span class="built_in">local</span> db_name=<span class="variable">$5</span></span><br><span class="line">    <span class="built_in">local</span> admin_user=<span class="string">"postgres"</span></span><br><span class="line">    <span class="built_in">local</span> admin_password=<span class="variable">$PG_POSTGRES_PASSWORD</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 检查数据库是否存在</span></span><br><span class="line">    <span class="keyword">if</span> PGPASSWORD=<span class="string">"<span class="variable">$password</span>"</span> psql -h <span class="variable">$host</span> -p <span class="variable">$port</span> -U <span class="variable">$user</span> -d postgres -lqt | cut -d \| -f 1 | grep -qw <span class="variable">$db_name</span>; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"数据库 <span class="variable">$db_name</span> 已存在"</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"创建数据库 <span class="variable">$db_name</span>..."</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 使用管理员用户创建数据库</span></span><br><span class="line">        PGPASSWORD=<span class="string">"<span class="variable">$admin_password</span>"</span> psql -h <span class="variable">$host</span> -p <span class="variable">$port</span> -U <span class="variable">$admin_user</span> &lt;&lt;EOF</span><br><span class="line">CREATE DATABASE <span class="variable">$db_name</span>;</span><br><span class="line">GRANT ALL PRIVILEGES ON DATABASE <span class="variable">$db_name</span> TO <span class="variable">$user</span>;</span><br><span class="line">EOF</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 确保用户对public模式有权限</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"确保 <span class="variable">$user</span> 对 <span class="variable">$db_name</span> 的 public 模式有权限..."</span></span><br><span class="line">    PGPASSWORD=<span class="string">"<span class="variable">$admin_password</span>"</span> psql -h <span class="variable">$host</span> -p <span class="variable">$port</span> -U <span class="variable">$admin_user</span> -d <span class="variable">$db_name</span> &lt;&lt;EOF</span><br><span class="line">GRANT ALL ON SCHEMA public TO <span class="variable">$user</span>;</span><br><span class="line">GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO <span class="variable">$user</span>;</span><br><span class="line">GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO <span class="variable">$user</span>;</span><br><span class="line">ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO <span class="variable">$user</span>;</span><br><span class="line">ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO <span class="variable">$user</span>;</span><br><span class="line">EOF</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装 基础依赖</span></span><br><span class="line"><span class="function"><span class="title">install_base</span></span>() &#123;</span><br><span class="line">    <span class="comment"># 先禁用IPv6</span></span><br><span class="line">    disable_ipv6</span><br><span class="line"></span><br><span class="line">    <span class="keyword">case</span> <span class="variable">$OS_NAME</span> <span class="keyword">in</span></span><br><span class="line">        ubuntu)</span><br><span class="line">            apt update</span><br><span class="line">            DEBIAN_FRONTEND=noninteractive TZ=Asia/Shanghai apt-get -y install tzdata</span><br><span class="line">            apt install -y sudo curl systemctl</span><br><span class="line">            apt install -y language-pack-zh-han*</span><br><span class="line">            <span class="built_in">echo</span> LANG=zh_CN.UTF-8 &gt; /etc/default/locale</span><br><span class="line">            <span class="built_in">source</span> /etc/default/locale</span><br><span class="line">            ;;</span><br><span class="line"></span><br><span class="line">        centos|rhel|alinux)</span><br><span class="line">            <span class="comment"># 统一处理CentOS/RHEL/Alibaba Linux</span></span><br><span class="line">            <span class="keyword">if</span> [ <span class="variable">$OS_VERSION</span> -eq 7 ]; <span class="keyword">then</span></span><br><span class="line">                curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo &amp;&amp; \</span><br><span class="line">                yum clean all &amp;&amp; \</span><br><span class="line">                yum makecache</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                cp -r /etc/yum.repos.d/ /etc/yum.repos.d_bak</span><br><span class="line">                rm -rf /etc/yum.repos.d/CentOS-Linux*</span><br><span class="line">                curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo</span><br><span class="line">                sed -i -e <span class="string">"s|mirrors.cloud.aliyuncs.com|mirrors.aliyun.com|g "</span> /etc/yum.repos.d/CentOS-*</span><br><span class="line">                sed -i -e <span class="string">"s|releasever|releasever-stream|g"</span> /etc/yum.repos.d/CentOS-*</span><br><span class="line">                yum clean all &amp;&amp; yum makecache</span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">            yum install -y sudo freetype fontconfig epel-release --nogpgcheck</span><br><span class="line"></span><br><span class="line">            <span class="comment"># 安装中文语言包并设置系统语言为中文</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"安装中文语言包并设置系统语言..."</span></span><br><span class="line">            sudo yum install -y glibc-langpack-zh.x86_64</span><br><span class="line">            <span class="built_in">echo</span> LANG=zh_CN.UTF-8 &gt; /etc/locale.conf</span><br><span class="line">            <span class="built_in">source</span> /etc/locale.conf</span><br><span class="line">            ;;</span><br><span class="line">    <span class="keyword">esac</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装 Java</span></span><br><span class="line"><span class="function"><span class="title">install_java</span></span>() &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="variable">$OS_NAME</span> <span class="keyword">in</span></span><br><span class="line">        ubuntu)</span><br><span class="line">            sudo apt update</span><br><span class="line">            sudo apt install -y openjdk-17-jdk</span><br><span class="line">            ;;</span><br><span class="line"></span><br><span class="line">        centos|rhel|alinux)</span><br><span class="line">            sudo yum install -y rpm/jdk-17.0.15_linux-x64_bin.rpm --nogpgcheck</span><br><span class="line">            ;;</span><br><span class="line">    <span class="keyword">esac</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装 MySQL</span></span><br><span class="line"><span class="function"><span class="title">install_mysql</span></span>() &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="variable">$OS_NAME</span> <span class="keyword">in</span></span><br><span class="line">        ubuntu)</span><br><span class="line">            <span class="comment"># 预置root密码</span></span><br><span class="line">            sudo debconf-set-selections &lt;&lt;&lt; <span class="string">"mysql-server mysql-server/root_password password <span class="variable">$MYSQL_ROOT_PASSWORD</span>"</span></span><br><span class="line">            sudo debconf-set-selections &lt;&lt;&lt; <span class="string">"mysql-server mysql-server/root_password_again password <span class="variable">$MYSQL_ROOT_PASSWORD</span>"</span></span><br><span class="line">            sudo apt install -y mysql-server</span><br><span class="line">            sudo systemctl <span class="built_in">enable</span> --now mysql</span><br><span class="line"></span><br><span class="line">            <span class="comment"># 自动创建配置用户</span></span><br><span class="line">            sudo mysql -uroot -p<span class="variable">$MYSQL_ROOT_PASSWORD</span> &lt;&lt;EOF</span><br><span class="line">CREATE USER <span class="string">'$DB_USER'</span>@<span class="string">'%'</span> IDENTIFIED BY <span class="string">'$DB_PASSWORD'</span>;</span><br><span class="line">GRANT ALL PRIVILEGES ON *.* TO <span class="string">'$DB_USER'</span>@<span class="string">'%'</span> WITH GRANT OPTION;</span><br><span class="line">FLUSH PRIVILEGES;</span><br><span class="line">EOF</span><br><span class="line">            ;;</span><br><span class="line"></span><br><span class="line">        centos|rhel|alinux)</span><br><span class="line">            sudo yum module -y <span class="built_in">disable</span> mysql</span><br><span class="line">            <span class="keyword">if</span> [ <span class="variable">$OS_VERSION</span> -eq 7 ]; <span class="keyword">then</span></span><br><span class="line">                sudo yum install -y rpm/mysql84-community-release-el7-1.noarch.rpm --nogpgcheck</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                sudo yum install -y rpm/mysql84-community-release-el8-1.noarch.rpm --nogpgcheck</span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">            sudo yum install -y mysql-community-server --nogpgcheck</span><br><span class="line">            sudo systemctl <span class="built_in">enable</span> --now mysqld</span><br><span class="line"></span><br><span class="line">            <span class="comment"># 获取临时密码</span></span><br><span class="line">            temp_pass=$(sudo grep <span class="string">'temporary password'</span> /var/<span class="built_in">log</span>/mysqld.log | awk <span class="string">'&#123;print $NF&#125;'</span>)</span><br><span class="line"></span><br><span class="line">            <span class="comment"># 自动修改root密码</span></span><br><span class="line">            mysql -uroot -p<span class="string">"<span class="variable">$temp_pass</span>"</span> --connect-expired-password &lt;&lt;EOF 2&gt;/dev/null</span><br><span class="line">ALTER USER <span class="string">'root'</span>@<span class="string">'localhost'</span> IDENTIFIED BY <span class="string">'$MYSQL_ROOT_PASSWORD'</span>;</span><br><span class="line">FLUSH PRIVILEGES;</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line">            <span class="comment"># 创建配置用户</span></span><br><span class="line">            mysql -uroot -p<span class="variable">$MYSQL_ROOT_PASSWORD</span> &lt;&lt;EOF</span><br><span class="line">CREATE USER <span class="string">'$DB_USER'</span>@<span class="string">'%'</span> IDENTIFIED BY <span class="string">'$DB_PASSWORD'</span>;</span><br><span class="line">GRANT ALL PRIVILEGES ON *.* TO <span class="string">'$DB_USER'</span>@<span class="string">'%'</span> WITH GRANT OPTION;</span><br><span class="line">FLUSH PRIVILEGES;</span><br><span class="line">EOF</span><br><span class="line">            ;;</span><br><span class="line">    <span class="keyword">esac</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装 PostgreSQL</span></span><br><span class="line"><span class="function"><span class="title">install_postgresql</span></span>() &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="variable">$OS_NAME</span> <span class="keyword">in</span></span><br><span class="line">        ubuntu)</span><br><span class="line">            sudo apt install -y postgresql postgresql-contrib</span><br><span class="line">            sudo service postgresql start</span><br><span class="line"></span><br><span class="line">            <span class="comment"># 使用单个psql命令避免目录错误</span></span><br><span class="line">            sudo -u postgres psql &lt;&lt;EOF</span><br><span class="line">ALTER USER postgres WITH PASSWORD <span class="string">'$PG_POSTGRES_PASSWORD'</span>;</span><br><span class="line">CREATE USER <span class="variable">$PG_USER</span> WITH PASSWORD <span class="string">'$PG_PASSWORD'</span>;</span><br><span class="line">ALTER USER <span class="variable">$PG_USER</span> CREATEDB;</span><br><span class="line">CREATE DATABASE thingsboard;</span><br><span class="line">GRANT ALL PRIVILEGES ON DATABASE thingsboard TO <span class="variable">$PG_USER</span>;</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line">            <span class="comment"># 确保用户对public模式有权限</span></span><br><span class="line">            sudo -u postgres psql -d thingsboard &lt;&lt;EOF</span><br><span class="line">GRANT ALL ON SCHEMA public TO <span class="variable">$PG_USER</span>;</span><br><span class="line">GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO <span class="variable">$PG_USER</span>;</span><br><span class="line">GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO <span class="variable">$PG_USER</span>;</span><br><span class="line">ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO <span class="variable">$PG_USER</span>;</span><br><span class="line">ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO <span class="variable">$PG_USER</span>;</span><br><span class="line">EOF</span><br><span class="line">            ;;</span><br><span class="line"></span><br><span class="line">        centos|rhel|alinux)</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> [ <span class="variable">$OS_VERSION</span> -eq 7 ]; <span class="keyword">then</span></span><br><span class="line">                sudo yum install -y rpm/postgresql15-libs-15.9-1PGDG.rhel7.x86_64.rpm --nogpgcheck</span><br><span class="line">                sudo yum install -y rpm/postgresql15-15.9-1PGDG.rhel7.x86_64.rpm --nogpgcheck</span><br><span class="line">                sudo yum install -y rpm/postgresql15-server-15.9-1PGDG.rhel7.x86_64.rpm --nogpgcheck</span><br><span class="line"></span><br><span class="line">                <span class="comment"># Initialize your PostgreSQL DB</span></span><br><span class="line">                sudo /usr/pgsql-15/bin/postgresql-15-setup initdb</span><br><span class="line">                sudo systemctl <span class="built_in">enable</span> --now postgresql-15</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                sudo yum install -y rpm/postgresql16-libs-16.9-3PGDG.rhel8.x86_64.rpm --nogpgcheck</span><br><span class="line">                sudo yum install -y rpm/postgresql16-16.9-3PGDG.rhel8.x86_64.rpm --nogpgcheck</span><br><span class="line">                sudo yum install -y rpm/postgresql16-server-16.9-3PGDG.rhel8.x86_64.rpm --nogpgcheck</span><br><span class="line"></span><br><span class="line">                sudo /usr/pgsql-16/bin/postgresql-16-setup initdb</span><br><span class="line">                sudo systemctl <span class="built_in">enable</span> --now postgresql-16</span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">            <span class="comment"># 创建用户和数据库</span></span><br><span class="line">            sudo -u postgres psql &lt;&lt;EOF</span><br><span class="line">ALTER USER postgres WITH PASSWORD <span class="string">'$PG_POSTGRES_PASSWORD'</span>;</span><br><span class="line">CREATE USER <span class="variable">$PG_USER</span> WITH PASSWORD <span class="string">'$PG_PASSWORD'</span>;</span><br><span class="line">ALTER USER <span class="variable">$PG_USER</span> CREATEDB;</span><br><span class="line">CREATE DATABASE thingsboard;</span><br><span class="line">GRANT ALL PRIVILEGES ON DATABASE thingsboard TO <span class="variable">$PG_USER</span>;</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line">            <span class="comment"># 确保用户对public模式有权限</span></span><br><span class="line">            sudo -u postgres psql -d thingsboard &lt;&lt;EOF</span><br><span class="line">GRANT ALL ON SCHEMA public TO <span class="variable">$PG_USER</span>;</span><br><span class="line">GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO <span class="variable">$PG_USER</span>;</span><br><span class="line">GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO <span class="variable">$PG_USER</span>;</span><br><span class="line">ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO <span class="variable">$PG_USER</span>;</span><br><span class="line">ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUences TO <span class="variable">$PG_USER</span>;</span><br><span class="line">EOF</span><br><span class="line">            ;;</span><br><span class="line">    <span class="keyword">esac</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 设置主机和端口为本地值</span></span><br><span class="line">    PG_HOST=<span class="string">"localhost"</span></span><br><span class="line">    PG_PORT=<span class="string">"5432"</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="title">install_redis</span></span>() &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="variable">$OS_NAME</span> <span class="keyword">in</span></span><br><span class="line">        ubuntu)</span><br><span class="line">            sudo apt install -y redis-server</span><br><span class="line">            ;;</span><br><span class="line">        centos|rhel|alinux)</span><br><span class="line">            <span class="keyword">if</span> [ <span class="variable">$OS_VERSION</span> -eq 7 ]; <span class="keyword">then</span></span><br><span class="line">                sudo yum install -y redis --nogpgcheck</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                sudo dnf install -y redis --nogpgcheck</span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line">            ;;</span><br><span class="line">    <span class="keyword">esac</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 获取Redis配置</span></span><br><span class="line">    <span class="built_in">local</span> redis_password=$(get_config <span class="string">'redis_password'</span>)</span><br><span class="line">    <span class="built_in">local</span> redis_conf=<span class="string">""</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 确定配置文件路径</span></span><br><span class="line">    <span class="keyword">case</span> <span class="variable">$OS_NAME</span> <span class="keyword">in</span></span><br><span class="line">        ubuntu)</span><br><span class="line">            redis_conf=<span class="string">"/etc/redis/redis.conf"</span></span><br><span class="line">            ;;</span><br><span class="line">        centos|rhel|alinux)</span><br><span class="line">            redis_conf=<span class="string">"/etc/redis.conf"</span></span><br><span class="line">            ;;</span><br><span class="line">    <span class="keyword">esac</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 配置密码（如果设置了）</span></span><br><span class="line">    <span class="keyword">if</span> [ -n <span class="string">"<span class="variable">$redis_password</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"设置Redis密码..."</span></span><br><span class="line">        sudo sed -i <span class="string">"s/^# requirepass .*/requirepass <span class="variable">$redis_password</span>/"</span> <span class="string">"<span class="variable">$redis_conf</span>"</span></span><br><span class="line">        sudo sed -i <span class="string">"s/^requirepass .*/requirepass <span class="variable">$redis_password</span>/"</span> <span class="string">"<span class="variable">$redis_conf</span>"</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 启动服务</span></span><br><span class="line">    <span class="keyword">case</span> <span class="variable">$OS_NAME</span> <span class="keyword">in</span></span><br><span class="line">        ubuntu)</span><br><span class="line">            sudo systemctl restart redis-server</span><br><span class="line">            sudo systemctl <span class="built_in">enable</span> redis-server</span><br><span class="line">            ;;</span><br><span class="line">        centos|rhel|alinux)</span><br><span class="line">            sudo systemctl restart redis</span><br><span class="line">            sudo systemctl <span class="built_in">enable</span> redis</span><br><span class="line">            ;;</span><br><span class="line">    <span class="keyword">esac</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装 Nginx</span></span><br><span class="line"><span class="function"><span class="title">install_nginx</span></span>() &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="variable">$OS_NAME</span> <span class="keyword">in</span></span><br><span class="line">        ubuntu)</span><br><span class="line">            sudo apt install -y nginx</span><br><span class="line">            ;;</span><br><span class="line">        centos|rhel|alinux)</span><br><span class="line">            <span class="keyword">if</span> [ <span class="variable">$OS_VERSION</span> -eq 7 ]; <span class="keyword">then</span></span><br><span class="line">                sudo yum install -y epel-release --nogpgcheck</span><br><span class="line">                sudo yum install -y nginx --nogpgcheck</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                sudo yum install -y nginx --nogpgcheck</span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line">            ;;</span><br><span class="line">    <span class="keyword">esac</span></span><br><span class="line">    sudo systemctl <span class="built_in">enable</span> --now nginx</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装 ThingsBoard - 添加目录验证</span></span><br><span class="line"><span class="function"><span class="title">install_thingsboard</span></span>() &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="variable">$OS_NAME</span> <span class="keyword">in</span></span><br><span class="line">        ubuntu)</span><br><span class="line">            sudo apt install -y ./rpm/thingsboard-4.0.1.deb</span><br><span class="line"></span><br><span class="line">            <span class="comment"># 确保正确的安装目录结构</span></span><br><span class="line">            <span class="keyword">if</span> [ ! -d <span class="string">"/usr/share/thingsboard"</span> ]; <span class="keyword">then</span></span><br><span class="line">                <span class="built_in">echo</span> <span class="string">"错误：ThingsBoard 未正确安装到 /usr/share/thingsboard"</span></span><br><span class="line">                <span class="built_in">exit</span> 1</span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line">            ;;</span><br><span class="line">        centos|rhel|alinux)</span><br><span class="line">            sudo yum install -y rpm/thingsboard-4.0.1.rpm --nogpgcheck</span><br><span class="line"></span><br><span class="line">            <span class="comment"># 确保正确的安装目录结构</span></span><br><span class="line">            <span class="keyword">if</span> [ ! -d <span class="string">"/usr/share/thingsboard"</span> ]; <span class="keyword">then</span></span><br><span class="line">                <span class="built_in">echo</span> <span class="string">"错误：ThingsBoard 未正确安装到 /usr/share/thingsboard"</span></span><br><span class="line">                <span class="built_in">exit</span> 1</span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line">            ;;</span><br><span class="line">    <span class="keyword">esac</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置ThingsBoard数据库连接</span></span><br><span class="line"><span class="function"><span class="title">configure_thingsboard</span></span>() &#123;</span><br><span class="line">    <span class="built_in">local</span> config_file=<span class="string">"/etc/thingsboard/conf/thingsboard.conf"</span></span><br><span class="line">    <span class="built_in">local</span> pg_user=$(get_config <span class="string">'postgresql_user'</span>)</span><br><span class="line">    <span class="built_in">local</span> pg_password=$(get_config <span class="string">'postgresql_password'</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 检查配置文件是否存在</span></span><br><span class="line">    <span class="keyword">if</span> [ ! -f <span class="string">"<span class="variable">$config_file</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"警告：ThingsBoard配置文件 <span class="variable">$config_file</span> 不存在"</span></span><br><span class="line">        <span class="built_in">return</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 检查配置是否已存在</span></span><br><span class="line">    <span class="keyword">if</span> grep -q <span class="string">"SPRING_DATASOURCE_URL"</span> <span class="string">"<span class="variable">$config_file</span>"</span>; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"数据库配置已存在，跳过配置"</span></span><br><span class="line">        <span class="built_in">return</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 添加数据库配置</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"# DB Configuration"</span> | sudo tee -a <span class="string">"<span class="variable">$config_file</span>"</span> &gt; /dev/null</span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"export DATABASE_TS_TYPE=sql"</span> | sudo tee -a <span class="string">"<span class="variable">$config_file</span>"</span> &gt; /dev/null</span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"export SPRING_DATASOURCE_URL=jdbc:postgresql://<span class="variable">$&#123;PG_HOST&#125;</span>:<span class="variable">$&#123;PG_PORT&#125;</span>/thingsboard"</span> | sudo tee -a <span class="string">"<span class="variable">$config_file</span>"</span> &gt; /dev/null</span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"export SPRING_DATASOURCE_USERNAME=<span class="variable">$pg_user</span>"</span> | sudo tee -a <span class="string">"<span class="variable">$config_file</span>"</span> &gt; /dev/null</span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"export SPRING_DATASOURCE_PASSWORD=<span class="variable">$pg_password</span>"</span> | sudo tee -a <span class="string">"<span class="variable">$config_file</span>"</span> &gt; /dev/null</span><br><span class="line"></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"已配置ThingsBoard使用PostgreSQL数据库 (<span class="variable">$&#123;PG_HOST&#125;</span>:<span class="variable">$&#123;PG_PORT&#125;</span>)"</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置ThingsBoard内存设置 - 使用MB单位</span></span><br><span class="line"><span class="function"><span class="title">configure_thingsboard_memory</span></span>() &#123;</span><br><span class="line">    <span class="built_in">local</span> config_file=<span class="string">"/etc/thingsboard/conf/thingsboard.conf"</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 检查配置文件是否存在</span></span><br><span class="line">    <span class="keyword">if</span> [ ! -f <span class="string">"<span class="variable">$config_file</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"警告：ThingsBoard配置文件 <span class="variable">$config_file</span> 不存在"</span></span><br><span class="line">        <span class="built_in">return</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 检测系统内存（MB）</span></span><br><span class="line">    <span class="built_in">local</span> mem_total</span><br><span class="line">    mem_total=$(grep MemTotal /proc/meminfo | awk <span class="string">'&#123;print $2&#125;'</span>)</span><br><span class="line">    mem_total=$((mem_total / 1024))  <span class="comment"># 转换KB为MB</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> [ -z <span class="string">"<span class="variable">$mem_total</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"错误：无法检测系统内存"</span></span><br><span class="line">        <span class="built_in">return</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> [ <span class="string">"<span class="variable">$mem_total</span>"</span> -le 4096 ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"系统内存为 <span class="variable">$&#123;mem_total&#125;</span>MB &lt;= 4GB，配置ThingsBoard使用2048MB内存"</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 检查是否已配置</span></span><br><span class="line">        <span class="keyword">if</span> ! grep -q <span class="string">"Xms2048M"</span> <span class="string">"<span class="variable">$config_file</span>"</span>; <span class="keyword">then</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"# Update ThingsBoard memory usage for low-memory systems"</span> | sudo tee -a <span class="string">"<span class="variable">$config_file</span>"</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"export JAVA_OPTS=\"\$JAVA_OPTS -Xms2048M -Xmx2048M\""</span> | sudo tee -a <span class="string">"<span class="variable">$config_file</span>"</span></span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"内存配置已存在，跳过"</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"系统内存为 <span class="variable">$&#123;mem_total&#125;</span>MB &gt; 4GB，使用默认内存配置"</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 初始化ThingsBoard数据库</span></span><br><span class="line"><span class="function"><span class="title">init_thingsboard</span></span>() &#123;</span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"初始化ThingsBoard数据库..."</span></span><br><span class="line">    <span class="built_in">local</span> pg_user=$(get_config <span class="string">'postgresql_user'</span>)</span><br><span class="line">    <span class="built_in">local</span> pg_password=$(get_config <span class="string">'postgresql_password'</span>)</span><br><span class="line">    <span class="built_in">local</span> pg_host=$(get_config <span class="string">'postgresql_host'</span>)</span><br><span class="line">    <span class="built_in">local</span> pg_port=$(get_config <span class="string">'postgresql_port'</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 确保psql命令可用</span></span><br><span class="line">    <span class="keyword">if</span> ! <span class="built_in">command</span> -v psql &amp;&gt; /dev/null; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"安装PostgreSQL客户端..."</span></span><br><span class="line">        <span class="keyword">if</span> [ <span class="string">"<span class="variable">$OS_NAME</span>"</span> = <span class="string">"ubuntu"</span> ]; <span class="keyword">then</span></span><br><span class="line">            install_package <span class="string">"postgresql-client"</span></span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            install_package <span class="string">"postgresql"</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 创建索引并清理数据</span></span><br><span class="line">    PGPASSWORD=<span class="string">"<span class="variable">$pg_password</span>"</span> psql \</span><br><span class="line">      -h <span class="string">"<span class="variable">$pg_host</span>"</span> \</span><br><span class="line">      -p <span class="string">"<span class="variable">$pg_port</span>"</span> \</span><br><span class="line">      -U <span class="string">"<span class="variable">$pg_user</span>"</span> \</span><br><span class="line">      -d thingsboard \</span><br><span class="line">      -c <span class="string">"</span></span><br><span class="line"><span class="string">        CREATE INDEX IF NOT EXISTS idx_device_label</span></span><br><span class="line"><span class="string">          ON device (tenant_id, label);</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        DELETE FROM device;</span></span><br><span class="line"><span class="string">        DELETE FROM device_credentials;</span></span><br><span class="line"><span class="string">        DELETE FROM customer;</span></span><br><span class="line"><span class="string">      "</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> [ $? -eq 0 ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"数据库初始化成功"</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"警告：数据库初始化失败"</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 执行ThingsBoard安装脚本并启动服务 - 添加权限验证</span></span><br><span class="line"><span class="function"><span class="title">install_and_start_thingsboard</span></span>() &#123;</span><br><span class="line">    <span class="comment"># 设置端口</span></span><br><span class="line">    <span class="built_in">local</span> port_thingsboard=$(get_config <span class="string">'port_thingsboard_port'</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 检查内存状态</span></span><br><span class="line">    <span class="built_in">local</span> mem_total=$(free -m | awk <span class="string">'/^Mem:/&#123;print $2&#125;'</span>)</span><br><span class="line"></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"执行ThingsBoard安装脚本..."</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 确保安装目录有正确权限</span></span><br><span class="line">    sudo chown -R thingsboard:thingsboard /usr/share/thingsboard</span><br><span class="line">    sudo chmod 755 /usr/share/thingsboard</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 确保在正确的目录执行</span></span><br><span class="line">    (<span class="built_in">cd</span> /usr/share/thingsboard &amp;&amp; sudo bin/install/install.sh --loadDemo)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 配置防火墙（仅当firewall-cmd存在时）</span></span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">command</span> -v firewall-cmd &amp;&gt; /dev/null; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"配置防火墙..."</span></span><br><span class="line">        sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent</span><br><span class="line">        sudo firewall-cmd --reload</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"未找到firewall-cmd，跳过防火墙配置"</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 扩大请求体限制</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"export HTTP_BIND_PORT=<span class="variable">$port_thingsboard</span>"</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"export HTTP_MAX_PAYLOAD_SIZE_LIMIT_CONFIGURATION=\"/api/image*/**=52428800;/api/resource/**=52428800;/api/**=524288000\""</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"export SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE=512MB"</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"export SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE=512MB"</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"export HTTP_TRANSPORT_MAX_PAYLOAD_SIZE_LIMIT_CONFIGURATION=\"/api/v1/*/rpc/**=655360;/api/v1/**=524288000;/api/**=524288000\""</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"export NETTY_MAX_PAYLOAD_SIZE=524288000"</span></span><br><span class="line">    &#125; | sudo tee -a /etc/bashrc &gt; /dev/null</span><br><span class="line"></span><br><span class="line">    <span class="built_in">source</span> /etc/bashrc</span><br><span class="line"></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"启动ThingsBoard服务..."</span></span><br><span class="line">    <span class="comment"># 使用systemctl如果可用，否则使用service</span></span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">command</span> -v systemctl &amp;&gt; /dev/null; <span class="keyword">then</span></span><br><span class="line">        sudo systemctl <span class="built_in">enable</span> --now thingsboard</span><br><span class="line">        <span class="comment"># 添加服务状态检查</span></span><br><span class="line">        sleep 30</span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"检查ThingsBoard服务状态..."</span></span><br><span class="line">        sudo systemctl status thingsboard --no-pager</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 如果服务失败，查看日志</span></span><br><span class="line">        <span class="keyword">if</span> ! systemctl is-active --quiet thingsboard; <span class="keyword">then</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"ThingsBoard服务启动失败，查看日志..."</span></span><br><span class="line">            sudo tail -n 100 /var/<span class="built_in">log</span>/thingsboard/thingsboard.log</span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"尝试强行执行jar..."</span></span><br><span class="line">            nohup /bin/bash /usr/share/thingsboard/bin/thingsboard.jar &gt;/dev/null 2&gt;&amp;1 &amp;</span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="comment"># 回退到service命令</span></span><br><span class="line">        sudo service thingsboard start</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 初始化数据库</span></span><br><span class="line">    init_thingsboard</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置Nginx</span></span><br><span class="line"><span class="function"><span class="title">configure_nginx</span></span>() &#123;</span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"备份默认Nginx配置..."</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 备份主配置文件</span></span><br><span class="line">    <span class="built_in">local</span> nginx_conf=<span class="string">"/etc/nginx/nginx.conf"</span></span><br><span class="line">    <span class="keyword">if</span> [ -f <span class="string">"<span class="variable">$nginx_conf</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">        sudo mv <span class="string">"<span class="variable">$nginx_conf</span>"</span> <span class="string">"<span class="variable">$&#123;nginx_conf&#125;</span>_bak"</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"已备份 <span class="variable">$nginx_conf</span> 为 <span class="variable">$&#123;nginx_conf&#125;</span>_bak"</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 备份默认站点配置</span></span><br><span class="line">    <span class="built_in">local</span> default_conf=<span class="string">""</span></span><br><span class="line">    <span class="keyword">case</span> <span class="variable">$OS_NAME</span> <span class="keyword">in</span></span><br><span class="line">        ubuntu)</span><br><span class="line">            default_conf=<span class="string">"/etc/nginx/sites-enabled/default"</span></span><br><span class="line">            ;;</span><br><span class="line">        centos|rhel|alinux)</span><br><span class="line">            default_conf=<span class="string">"/etc/nginx/conf.d/default.conf"</span></span><br><span class="line">            <span class="keyword">if</span> [ ! -f <span class="string">"<span class="variable">$default_conf</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">                default_conf=<span class="string">"/etc/nginx/nginx.conf.default"</span></span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line">            ;;</span><br><span class="line">    <span class="keyword">esac</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> [ -f <span class="string">"<span class="variable">$default_conf</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">        sudo mv <span class="string">"<span class="variable">$default_conf</span>"</span> <span class="string">"<span class="variable">$&#123;default_conf&#125;</span>_bak"</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"已备份 <span class="variable">$default_conf</span> 为 <span class="variable">$&#123;default_conf&#125;</span>_bak"</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 获取前端基础目录</span></span><br><span class="line">    <span class="built_in">local</span> basedir=$(get_config <span class="string">'frontend_basedir'</span>)</span><br><span class="line">    <span class="keyword">if</span> [ -z <span class="string">"<span class="variable">$basedir</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"警告：前端基础目录未配置，跳过Nginx配置"</span></span><br><span class="line">        <span class="built_in">return</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 创建基础目录</span></span><br><span class="line">    sudo mkdir -p <span class="string">"<span class="variable">$basedir</span>"</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 复制前端资源并替换URL</span></span><br><span class="line">    <span class="built_in">local</span> iot_gateway_url=$(get_config <span class="string">'frontend_iot_gateway_url'</span>)</span><br><span class="line">    <span class="built_in">local</span> xxx_url=$(get_config <span class="string">'frontend_xxx_url'</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 复制iot_blockly_frontend</span></span><br><span class="line">    <span class="built_in">local</span> src_dir1=<span class="string">"<span class="variable">$SCRIPT_DIR</span>/iot_gateway/iot_blockly_frontend"</span></span><br><span class="line">    <span class="built_in">local</span> dest_dir1=<span class="string">"<span class="variable">$basedir</span>/iot_blockly_frontend"</span></span><br><span class="line">    <span class="keyword">if</span> [ -d <span class="string">"<span class="variable">$src_dir1</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">        sudo cp -r <span class="string">"<span class="variable">$src_dir1</span>"</span> <span class="string">"<span class="variable">$basedir</span>/"</span></span><br><span class="line">        <span class="comment"># 替换URL</span></span><br><span class="line">        sudo find <span class="string">"<span class="variable">$dest_dir1</span>"</span> -<span class="built_in">type</span> f -<span class="built_in">exec</span> sed -i <span class="string">"s|IOT_GATEWAY_URL|<span class="variable">$&#123;iot_gateway_url&#125;</span>|g"</span> &#123;&#125; \;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"警告：前端资源目录 <span class="variable">$src_dir1</span> 不存在"</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 复制iot_xxx_frontend</span></span><br><span class="line">    <span class="built_in">local</span> src_dir2=<span class="string">"<span class="variable">$SCRIPT_DIR</span>/ruoyi-admin/iot_xxx_frontend"</span></span><br><span class="line">    <span class="built_in">local</span> dest_dir2=<span class="string">"<span class="variable">$basedir</span>/iot_xxx_frontend"</span></span><br><span class="line">    <span class="keyword">if</span> [ -d <span class="string">"<span class="variable">$src_dir2</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">        sudo cp -r <span class="string">"<span class="variable">$src_dir2</span>"</span> <span class="string">"<span class="variable">$basedir</span>/"</span></span><br><span class="line">        <span class="comment"># 替换URL</span></span><br><span class="line">        sudo find <span class="string">"<span class="variable">$dest_dir2</span>"</span> -<span class="built_in">type</span> f -<span class="built_in">exec</span> sed -i <span class="string">"s|xxx_URL|<span class="variable">$&#123;xxx_url&#125;</span>|g"</span> &#123;&#125; \;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"警告：前端资源目录 <span class="variable">$src_dir2</span> 不存在"</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 创建Nginx配置文件目录</span></span><br><span class="line">    sudo mkdir -p /etc/nginx/conf.d</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 确定Nginx运行时使用的用户</span></span><br><span class="line">        <span class="built_in">local</span> nginx_user</span><br><span class="line">        <span class="keyword">if</span> [ <span class="string">"<span class="variable">$OS_NAME</span>"</span> = <span class="string">"ubuntu"</span> ]; <span class="keyword">then</span></span><br><span class="line">            nginx_user=<span class="string">"www-data"</span>  <span class="comment"># Ubuntu默认使用www-data</span></span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            nginx_user=<span class="string">"nginx"</span>     <span class="comment"># CentOS使用nginx</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 创建主配置文件（总是覆盖）</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"创建新的Nginx主配置..."</span></span><br><span class="line">        sudo tee /etc/nginx/nginx.conf &gt; /dev/null &lt;&lt;EOF</span><br><span class="line">user <span class="variable">$nginx_user</span>;  <span class="comment"># 动态设置用户</span></span><br><span class="line">worker_processes auto;</span><br><span class="line">error_log /var/<span class="built_in">log</span>/nginx/error.log;</span><br><span class="line">pid /run/nginx.pid;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.</span></span><br><span class="line">include /usr/share/nginx/modules/*.conf;</span><br><span class="line"></span><br><span class="line">events &#123;</span><br><span class="line">    worker_connections 1024;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">http &#123;</span><br><span class="line">    log_format  main  <span class="string">'\$remote_addr - \$remote_user [\$time_local] "\$request" '</span></span><br><span class="line">                      <span class="string">'\$status \$body_bytes_sent "\$http_referer" '</span></span><br><span class="line">                      <span class="string">'"\$http_user_agent" "\$http_x_forwarded_for"'</span>;</span><br><span class="line"></span><br><span class="line">    access_log  /var/<span class="built_in">log</span>/nginx/access.log  main;</span><br><span class="line"></span><br><span class="line">    sendfile            on;</span><br><span class="line">    tcp_nopush          on;</span><br><span class="line">    tcp_nodelay         on;</span><br><span class="line">    keepalive_timeout   65;</span><br><span class="line">    types_hash_max_size 2048;</span><br><span class="line"></span><br><span class="line">    include             /etc/nginx/mime.types;</span><br><span class="line">    default_type        application/octet-stream;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Load modular configuration files from the /etc/nginx/conf.d directory.</span></span><br><span class="line">    include /etc/nginx/conf.d/*.conf;</span><br><span class="line">&#125;</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 创建Nginx配置文件</span></span><br><span class="line">    <span class="built_in">local</span> tb_conf=<span class="string">"/etc/nginx/conf.d/tb.conf"</span></span><br><span class="line">    <span class="built_in">local</span> xxx_conf=<span class="string">"/etc/nginx/conf.d/xxx.conf"</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 获取配置参数</span></span><br><span class="line">    <span class="built_in">local</span> thingsboard_port=$(get_config <span class="string">'port_thingsboard_port'</span>)</span><br><span class="line">    <span class="built_in">local</span> iot_gateway_port=$(get_config <span class="string">'port_iot_gateway_port'</span>)</span><br><span class="line">    <span class="built_in">local</span> xxx_backend_port=$(get_config <span class="string">'port_xxx_backend_port'</span>)</span><br><span class="line">    <span class="built_in">local</span> tb_ssl_cert=$(get_config <span class="string">'nginx_thingsboard_ssl_certificate'</span>)</span><br><span class="line">    <span class="built_in">local</span> tb_ssl_key=$(get_config <span class="string">'nginx_thingsboard_ssl_certificate_key'</span>)</span><br><span class="line">    <span class="built_in">local</span> xxx_ssl_cert=$(get_config <span class="string">'nginx_xxx_ssl_certificate'</span>)</span><br><span class="line">    <span class="built_in">local</span> xxx_ssl_key=$(get_config <span class="string">'nginx_xxx_ssl_certificate_key'</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 创建ThingsBoard配置</span></span><br><span class="line">    sudo tee <span class="string">"<span class="variable">$tb_conf</span>"</span> &gt; /dev/null &lt;&lt;EOF</span><br><span class="line">server &#123;</span><br><span class="line">    listen 80;</span><br><span class="line">    server_name _;</span><br><span class="line">    $([ -n <span class="string">"<span class="variable">$tb_ssl_cert</span>"</span> ] &amp;&amp; <span class="built_in">echo</span> <span class="string">"return 301 https://\$host:9443\$request_uri;"</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">$([ -n <span class="string">"<span class="variable">$tb_ssl_cert</span>"</span> ] &amp;&amp; <span class="built_in">echo</span> <span class="string">"</span></span><br><span class="line"><span class="string">server &#123;</span></span><br><span class="line"><span class="string">    listen 9443 ssl;</span></span><br><span class="line"><span class="string">    server_name _;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    ssl_certificate <span class="variable">$tb_ssl_cert</span>;</span></span><br><span class="line"><span class="string">    ssl_certificate_key <span class="variable">$tb_ssl_key</span>;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    client_max_body_size 1024M;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    location / &#123;</span></span><br><span class="line"><span class="string">        proxy_pass http://localhost:<span class="variable">$thingsboard_port</span>;</span></span><br><span class="line"><span class="string">        proxy_http_version 1.1;</span></span><br><span class="line"><span class="string">        proxy_set_header Host \$host;</span></span><br><span class="line"><span class="string">        proxy_set_header X-Real-IP \$remote_addr;</span></span><br><span class="line"><span class="string">        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;</span></span><br><span class="line"><span class="string">        proxy_set_header X-Forwarded-Proto \$scheme;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        # WebSocket 关键配置</span></span><br><span class="line"><span class="string">        proxy_set_header Upgrade \$http_upgrade;</span></span><br><span class="line"><span class="string">        proxy_set_header Connection "</span>upgrade<span class="string">";</span></span><br><span class="line"><span class="string">        proxy_read_timeout 86400;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">"</span>)</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 创建xxx配置</span></span><br><span class="line">    sudo tee <span class="string">"<span class="variable">$xxx_conf</span>"</span> &gt; /dev/null &lt;&lt;EOF</span><br><span class="line">$([ -n <span class="string">"<span class="variable">$xxx_ssl_cert</span>"</span> ] &amp;&amp; <span class="built_in">echo</span> <span class="string">"</span></span><br><span class="line"><span class="string">server &#123;</span></span><br><span class="line"><span class="string">    listen 80;</span></span><br><span class="line"><span class="string">    server_name _;</span></span><br><span class="line"><span class="string">    return 301 https://\$host\$request_uri;</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">"</span>)</span><br><span class="line"></span><br><span class="line">server &#123;</span><br><span class="line">    listen $([ -n <span class="string">"<span class="variable">$xxx_ssl_cert</span>"</span> ] &amp;&amp; <span class="built_in">echo</span> <span class="string">"443 ssl"</span> || <span class="built_in">echo</span> <span class="string">"80"</span>);</span><br><span class="line">    server_name _;</span><br><span class="line"></span><br><span class="line">    $([ -n <span class="string">"<span class="variable">$xxx_ssl_cert</span>"</span> ] &amp;&amp; <span class="built_in">echo</span> <span class="string">"</span></span><br><span class="line"><span class="string">    ssl_certificate <span class="variable">$xxx_ssl_cert</span>;</span></span><br><span class="line"><span class="string">    ssl_certificate_key <span class="variable">$xxx_ssl_key</span>;"</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 精确匹配 /block</span></span><br><span class="line">    location = /block &#123;</span><br><span class="line">        rewrite ^/block/(.*)$ /\<span class="variable">$1</span> <span class="built_in">break</span>;</span><br><span class="line">        root <span class="variable">$basedir</span>/iot_blockly_frontend;</span><br><span class="line">        index index.html;</span><br><span class="line">        try_files \<span class="variable">$uri</span> \<span class="variable">$uri</span>/ /index.html;</span><br><span class="line">        proxy_set_header Host \<span class="variable">$host</span>;</span><br><span class="line">        proxy_set_header X-Real-IP \<span class="variable">$remote_addr</span>;</span><br><span class="line">        proxy_set_header X-Forwarded-For \<span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line">        proxy_set_header X-Forwarded-Proto \<span class="variable">$scheme</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 匹配 /block/ 下的所有路径</span></span><br><span class="line">    location /block/ &#123;</span><br><span class="line">        rewrite ^/block/(.*)$ /\<span class="variable">$1</span> <span class="built_in">break</span>;</span><br><span class="line">        root <span class="variable">$basedir</span>/iot_blockly_frontend;</span><br><span class="line">        index index.html;</span><br><span class="line">        try_files \<span class="variable">$uri</span> \<span class="variable">$uri</span>/ /index.html;</span><br><span class="line">        proxy_set_header Host \<span class="variable">$host</span>;</span><br><span class="line">        proxy_set_header X-Real-IP \<span class="variable">$remote_addr</span>;</span><br><span class="line">        proxy_set_header X-Forwarded-For \<span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line">        proxy_set_header X-Forwarded-Proto \<span class="variable">$scheme</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    location / &#123;</span><br><span class="line">        root <span class="variable">$basedir</span>/iot_xxx_frontend;</span><br><span class="line">        index index.html;</span><br><span class="line">        try_files \<span class="variable">$uri</span> \<span class="variable">$uri</span>/ /index.html;</span><br><span class="line">        proxy_set_header Host \<span class="variable">$host</span>;</span><br><span class="line">        proxy_set_header X-Real-IP \<span class="variable">$remote_addr</span>;</span><br><span class="line">        proxy_set_header X-Forwarded-For \<span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line">        proxy_set_header X-Forwarded-Proto \<span class="variable">$scheme</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 新增的特定路由，需要放在通用/gateway路由之前</span></span><br><span class="line">    location /gateway/<span class="built_in">exec</span> &#123;</span><br><span class="line">        rewrite ^/gateway/<span class="built_in">exec</span>/(.*)$ /<span class="built_in">exec</span>/\<span class="variable">$1</span> <span class="built_in">break</span>;</span><br><span class="line">        proxy_pass http://localhost:<span class="variable">$xxx_backend_port</span>/;</span><br><span class="line">        proxy_set_header Host \<span class="variable">$host</span>;</span><br><span class="line">        proxy_set_header X-Real-IP \<span class="variable">$remote_addr</span>;</span><br><span class="line">        proxy_set_header X-Forwarded-For \<span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line">        proxy_set_header X-Forwarded-Proto \<span class="variable">$scheme</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    location /gateway &#123;</span><br><span class="line">        rewrite ^/gateway/(.*)$ /\<span class="variable">$1</span> <span class="built_in">break</span>;</span><br><span class="line">        proxy_pass http://localhost:<span class="variable">$iot_gateway_port</span>;</span><br><span class="line">        proxy_set_header Host \<span class="variable">$host</span>;</span><br><span class="line">        proxy_set_header X-Real-IP \<span class="variable">$remote_addr</span>;</span><br><span class="line">        proxy_set_header X-Forwarded-For \<span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line">        proxy_set_header X-Forwarded-Proto \<span class="variable">$scheme</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    location /prod-api &#123;</span><br><span class="line">        rewrite ^/prod-api/(.*)$ /\<span class="variable">$1</span> <span class="built_in">break</span>;</span><br><span class="line">        proxy_pass http://localhost:<span class="variable">$xxx_backend_port</span>;</span><br><span class="line">        proxy_set_header Host \<span class="variable">$host</span>;</span><br><span class="line">        proxy_set_header X-Real-IP \<span class="variable">$remote_addr</span>;</span><br><span class="line">        proxy_set_header X-Forwarded-For \<span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line">        proxy_set_header X-Forwarded-Proto \<span class="variable">$scheme</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 测试并重启Nginx</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"测试Nginx配置..."</span></span><br><span class="line">    sudo nginx -t &amp;&amp; sudo systemctl restart nginx</span><br><span class="line">    <span class="keyword">if</span> [ $? -eq 0 ]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"Nginx 配置应用成功"</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"错误：Nginx 配置测试失败，请检查日志"</span></span><br><span class="line">        <span class="built_in">exit</span> 1</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 主函数</span></span><br><span class="line"><span class="function"><span class="title">main</span></span>() &#123;</span><br><span class="line">    detect_os</span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"检测到操作系统: <span class="variable">$OS_NAME</span> <span class="variable">$OS_VERSION</span>"</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 安装基础</span></span><br><span class="line">    install_base</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 安装 Java</span></span><br><span class="line">    <span class="keyword">if</span> [ <span class="string">"<span class="variable">$(get_config 'common_install_java')</span>"</span> = <span class="string">"yes"</span> ]; <span class="keyword">then</span></span><br><span class="line">        <span class="comment"># 先检查 java 命令是否存在</span></span><br><span class="line">        <span class="keyword">if</span> ! <span class="built_in">command</span> -v java &amp;&gt; /dev/null; <span class="keyword">then</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"Java 未安装，开始安装..."</span></span><br><span class="line">            install_java</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            <span class="comment"># 如果 java 命令存在，再检查版本</span></span><br><span class="line">            <span class="keyword">if</span> ! java -version 2&gt;&amp;1 | grep -q <span class="string">"17"</span>; <span class="keyword">then</span></span><br><span class="line">                <span class="built_in">echo</span> <span class="string">"已安装的 Java 版本不是 17，开始安装 JDK 17..."</span></span><br><span class="line">                install_java</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                <span class="built_in">echo</span> <span class="string">"JDK 17 已安装"</span></span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 验证安装</span></span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">command</span> -v java &amp;&gt; /dev/null; <span class="keyword">then</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"Java 安装验证:"</span></span><br><span class="line">            java -version</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"错误：Java 安装后仍然不可用"</span></span><br><span class="line">            <span class="built_in">exit</span> 1</span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 处理 MySQL</span></span><br><span class="line">    <span class="keyword">if</span> [ <span class="string">"<span class="variable">$(get_config 'mysql_install')</span>"</span> = <span class="string">"yes"</span> ]; <span class="keyword">then</span></span><br><span class="line">        host=$(get_config <span class="string">'mysql_host'</span>)</span><br><span class="line">        port=$(get_config <span class="string">'mysql_port'</span>)</span><br><span class="line">        user=$(get_config <span class="string">'mysql_user'</span>)</span><br><span class="line">        password=$(get_config <span class="string">'mysql_password'</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> check_db_connection <span class="string">"mysql"</span> <span class="variable">$host</span> <span class="variable">$port</span> <span class="variable">$user</span> <span class="variable">$password</span>; <span class="keyword">then</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"MySQL 连接成功，跳过安装"</span></span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"MySQL 连接失败，开始安装..."</span></span><br><span class="line">            install_mysql</span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"MySQL 已自动配置用户：<span class="variable">$DB_USER</span>"</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 新增：导入 xxx.sql 到 MySQL</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"处理 xxx 数据库导入..."</span></span><br><span class="line">        import_xxx_sql <span class="variable">$host</span> <span class="variable">$port</span> <span class="variable">$user</span> <span class="variable">$password</span> <span class="string">"<span class="variable">$SQL_FILE</span>"</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 新增：更新 sys_menu 表</span></span><br><span class="line">        thingsboard_url=$(get_config <span class="string">'frontend_thingsboard_url'</span>)</span><br><span class="line">        update_sys_menu <span class="variable">$host</span> <span class="variable">$port</span> <span class="variable">$user</span> <span class="variable">$password</span> <span class="string">"<span class="variable">$thingsboard_url</span>"</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 安装 Nginx</span></span><br><span class="line">    <span class="keyword">if</span> [ <span class="string">"<span class="variable">$(get_config 'common_install_nginx')</span>"</span> = <span class="string">"yes"</span> ]; <span class="keyword">then</span></span><br><span class="line">        <span class="keyword">if</span> ! <span class="built_in">command</span> -v nginx &amp;&gt; /dev/null; <span class="keyword">then</span></span><br><span class="line">            install_nginx</span><br><span class="line">            nginx -v</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"Nginx 已安装"</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 配置Nginx</span></span><br><span class="line">        configure_nginx</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 处理 PostgreSQL - 特别处理CentOS 7的版本问题</span></span><br><span class="line">    <span class="keyword">if</span> [ <span class="string">"<span class="variable">$(get_config 'postgresql_install')</span>"</span> = <span class="string">"yes"</span> ]; <span class="keyword">then</span></span><br><span class="line">        host=$(get_config <span class="string">'postgresql_host'</span>)</span><br><span class="line">        port=$(get_config <span class="string">'postgresql_port'</span>)</span><br><span class="line">        user=$(get_config <span class="string">'postgresql_user'</span>)</span><br><span class="line">        password=$(get_config <span class="string">'postgresql_password'</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> check_db_connection <span class="string">"postgresql"</span> <span class="variable">$host</span> <span class="variable">$port</span> <span class="variable">$user</span> <span class="variable">$password</span>; <span class="keyword">then</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"PostgreSQL 连接成功"</span></span><br><span class="line"></span><br><span class="line">            <span class="comment"># 确保thingsboard数据库存在</span></span><br><span class="line">            ensure_postgresql_db <span class="variable">$host</span> <span class="variable">$port</span> <span class="variable">$user</span> <span class="variable">$password</span> <span class="string">"thingsboard"</span></span><br><span class="line"></span><br><span class="line">            <span class="comment"># 使用配置的主机和端口</span></span><br><span class="line">            PG_HOST=<span class="variable">$host</span></span><br><span class="line">            PG_PORT=<span class="variable">$port</span></span><br><span class="line"></span><br><span class="line">            <span class="comment"># 检查PostgreSQL版本</span></span><br><span class="line">            PG_VERSION=$(PGPASSWORD=<span class="string">"<span class="variable">$password</span>"</span> psql -h <span class="variable">$host</span> -p <span class="variable">$port</span> -U <span class="variable">$user</span> -d postgres -tAc <span class="string">"SHOW server_version_num"</span>)</span><br><span class="line">            <span class="keyword">if</span> [ <span class="string">"<span class="variable">$PG_VERSION</span>"</span> -lt 110000 ]; <span class="keyword">then</span></span><br><span class="line">                <span class="built_in">echo</span> <span class="string">"警告：当前PostgreSQL版本(<span class="variable">$PG_VERSION</span>)低于11.0，建议升级到11+版本"</span></span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"PostgreSQL 连接失败，开始安装..."</span></span><br><span class="line">            install_postgresql</span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"PostgreSQL 已自动配置用户：<span class="variable">$PG_USER</span>"</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"已创建数据库: thingsboard"</span></span><br><span class="line"></span><br><span class="line">            <span class="comment"># 使用本地安装的主机和端口</span></span><br><span class="line">            PG_HOST=<span class="string">"localhost"</span></span><br><span class="line">            PG_PORT=<span class="string">"5432"</span></span><br><span class="line"></span><br><span class="line">            <span class="comment"># 验证PostgreSQL版本</span></span><br><span class="line">            sudo -u postgres psql -c <span class="string">"SHOW server_version"</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 新增：安装Redis</span></span><br><span class="line">    <span class="keyword">if</span> [ <span class="string">"<span class="variable">$(get_config 'redis_install')</span>"</span> = <span class="string">"yes"</span> ]; <span class="keyword">then</span></span><br><span class="line">        <span class="comment"># 检查Redis是否已安装</span></span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">command</span> -v redis-server &amp;&gt; /dev/null; <span class="keyword">then</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"Redis 已安装"</span></span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"开始安装Redis..."</span></span><br><span class="line">            install_redis</span><br><span class="line">            <span class="built_in">echo</span> <span class="string">"Redis 已安装并启动"</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 安装 thingsboard</span></span><br><span class="line">    <span class="keyword">if</span> [ <span class="string">"<span class="variable">$(get_config 'common_install_thingsboard')</span>"</span> = <span class="string">"yes"</span> ]; <span class="keyword">then</span></span><br><span class="line">        install_thingsboard</span><br><span class="line">        <span class="built_in">echo</span> <span class="string">"ThingsBoard 已安装"</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 配置数据库连接</span></span><br><span class="line">        configure_thingsboard</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 配置内存设置</span></span><br><span class="line">        configure_thingsboard_memory</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 等待数据库可用</span></span><br><span class="line">        wait_for_db <span class="string">"postgresql"</span> <span class="variable">$PG_HOST</span> <span class="variable">$PG_PORT</span> <span class="variable">$PG_USER</span> <span class="variable">$PG_PASSWORD</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># 执行安装脚本并启动服务</span></span><br><span class="line">        install_and_start_thingsboard</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 显示部署信息</span></span><br><span class="line">    <span class="built_in">echo</span> -e <span class="string">"\n\033[32m部署完成!\033[0m"</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 执行主函数</span></span><br><span class="line">main</span><br></pre></td></tr></table></figure><h2 id="Docker-Composer"><a href="#Docker-Composer" class="headerlink" title="Docker Composer"></a>Docker Composer</h2><p>需要改造代码，通过读取环境变量来动态配置</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br></pre></td><td class="code"><pre><span class="line">services:</span><br><span class="line">  mysql:</span><br><span class="line">    image: $&#123;MYSQL_IMAGE:-mysql:<span class="number">8.0</span>&#125;</span><br><span class="line">    container_name: xxx_mysql</span><br><span class="line">    ports:</span><br><span class="line">      - <span class="string">"$&#123;MYSQL_PORT:-3306&#125;:3306"</span></span><br><span class="line">    environment:</span><br><span class="line">      MYSQL_ROOT_PASSWORD: $&#123;MYSQL_ROOT_PASSWORD:-MyApp@<span class="number">1234</span>&#125;</span><br><span class="line">      MYSQL_USER: $&#123;MYSQL_USER:-myapp&#125;</span><br><span class="line">      MYSQL_PASSWORD: $&#123;MYSQL_PASSWORD:-MyApp@<span class="number">1234</span>&#125;</span><br><span class="line">      MYSQL_DATABASE: xxx</span><br><span class="line">    volumes:</span><br><span class="line">      - ./mysql/data:/var/lib/mysql</span><br><span class="line">      - ./mysql/initdb.d:/docker-<span class="keyword">entrypoint</span><span class="bash">-initdb.d</span></span><br><span class="line">    restart: always</span><br><span class="line">    <span class="keyword">healthcheck</span><span class="bash">:</span></span><br><span class="line">      test: [<span class="string">"CMD"</span>, <span class="string">"mysqladmin"</span>, <span class="string">"ping"</span>, <span class="string">"-h"</span>, <span class="string">"localhost"</span>, <span class="string">"-u$&#123;MYSQL_USER&#125;"</span>, <span class="string">"-p$&#123;MYSQL_PASSWORD&#125;"</span>]</span><br><span class="line">      interval: <span class="number">30</span>s</span><br><span class="line">      timeout: <span class="number">30</span>s</span><br><span class="line">      retries: <span class="number">10</span></span><br><span class="line">      start_period: <span class="number">180</span>s</span><br><span class="line"></span><br><span class="line">  postgres:</span><br><span class="line">    image: $&#123;POSTGRES_IMAGE:-postgres:<span class="number">16</span>&#125;</span><br><span class="line">    container_name: xxx_postgres</span><br><span class="line">    ports:</span><br><span class="line">      - <span class="string">"$&#123;POSTGRES_PORT:-5432&#125;:5432"</span></span><br><span class="line">    environment:</span><br><span class="line">      POSTGRES_USER: $&#123;POSTGRES_USER:-myapp&#125;</span><br><span class="line">      POSTGRES_PASSWORD: $&#123;POSTGRES_PASSWORD:-MyApp@<span class="number">5678</span>&#125;</span><br><span class="line">      POSTGRES_DB: thingsboard</span><br><span class="line">    volumes:</span><br><span class="line">      - ./postgres/data:/var/lib/postgresql/data</span><br><span class="line">    restart: always</span><br><span class="line">    <span class="keyword">healthcheck</span><span class="bash">:</span></span><br><span class="line">      test: [<span class="string">"CMD-SHELL"</span>, <span class="string">"pg_isready -U $&#123;POSTGRES_USER&#125; -d thingsboard"</span>]</span><br><span class="line">      interval: <span class="number">10</span>s</span><br><span class="line">      timeout: <span class="number">5</span>s</span><br><span class="line">      retries: <span class="number">10</span></span><br><span class="line">      start_period: <span class="number">30</span>s</span><br><span class="line"></span><br><span class="line">  redis:</span><br><span class="line">    image: $&#123;REDIS_IMAGE:-redis:<span class="number">7</span>&#125;</span><br><span class="line">    container_name: xxx_redis</span><br><span class="line">    ports:</span><br><span class="line">      - <span class="string">"$&#123;REDIS_PORT:-6379&#125;:6379"</span></span><br><span class="line">    environment:</span><br><span class="line">      REDIS_PASSWORD: $&#123;REDIS_PASSWORD:-&#125;</span><br><span class="line">    command: &gt;</span><br><span class="line">      bash -c <span class="string">"if [ -z \"$$REDIS_PASSWORD\" ]; then </span></span><br><span class="line"><span class="string">        redis-server; </span></span><br><span class="line"><span class="string">      else </span></span><br><span class="line"><span class="string">        redis-server --requirepass $$REDIS_PASSWORD; </span></span><br><span class="line"><span class="string">      fi"</span></span><br><span class="line">    volumes:</span><br><span class="line">      - ./redis/data:/data</span><br><span class="line">    restart: always</span><br><span class="line">    <span class="keyword">healthcheck</span><span class="bash">:</span></span><br><span class="line">      test: &gt;</span><br><span class="line">        bash -c <span class="string">"if [ -z \"$$REDIS_PASSWORD\" ]; then </span></span><br><span class="line"><span class="string">          redis-cli ping; </span></span><br><span class="line"><span class="string">        else </span></span><br><span class="line"><span class="string">          redis-cli -a $$REDIS_PASSWORD ping; </span></span><br><span class="line"><span class="string">        fi"</span></span><br><span class="line">      interval: <span class="number">10</span>s</span><br><span class="line">      timeout: <span class="number">5</span>s</span><br><span class="line">      retries: <span class="number">10</span></span><br><span class="line">      start_period: <span class="number">30</span>s</span><br><span class="line"></span><br><span class="line">  thingsboard:</span><br><span class="line">    image: $&#123;THINGSBOARD_IMAGE:-thingsboard/tb-node:<span class="number">4.0</span>.<span class="number">1.1</span>&#125;</span><br><span class="line">    container_name: xxx_thingsboard</span><br><span class="line">    ports:</span><br><span class="line">      - <span class="string">"$&#123;THINGSBOARD_PORT&#125;:8080"</span></span><br><span class="line">      - <span class="string">"7070:7070"</span></span><br><span class="line">      - <span class="string">"1883:1883"</span></span><br><span class="line">      - <span class="string">"8883:8883"</span></span><br><span class="line">      - <span class="string">"5683-5688:5683-5688/udp"</span></span><br><span class="line">    environment:</span><br><span class="line">      DATABASE_TS_TYPE: sql</span><br><span class="line">      SPRING_DATASOURCE_URL: jdbc:postgresql://$&#123;TB_POSTGRESQL_HOST&#125;:$&#123;TB_POSTGRESQL_PORT&#125;/thingsboard</span><br><span class="line">      SPRING_DATASOURCE_USERNAME: $&#123;POSTGRES_USER&#125;</span><br><span class="line">      SPRING_DATASOURCE_PASSWORD: $&#123;POSTGRES_PASSWORD&#125;</span><br><span class="line">      HTTP_BIND_PORT: <span class="number">8080</span></span><br><span class="line"><span class="comment">#      JAVA_OPTS: "-Xmx1024m -Xms1024m"</span></span><br><span class="line">    depends_on:</span><br><span class="line">      postgres:</span><br><span class="line">        condition: service_healthy</span><br><span class="line">    restart: unless-stopped</span><br><span class="line">    <span class="keyword">healthcheck</span><span class="bash">:</span></span><br><span class="line">      test: [<span class="string">"CMD-SHELL"</span>, <span class="string">"grep ':075B' /proc/net/tcp6 || exit 1"</span>]</span><br><span class="line">      interval: <span class="number">5</span>s       </span><br><span class="line">      timeout: <span class="number">3</span>s        </span><br><span class="line">      start_period: <span class="number">300</span>s  </span><br><span class="line">      retries: <span class="number">120</span></span><br><span class="line"></span><br><span class="line">  iot-gateway:</span><br><span class="line">    image: $&#123;IOT_GATEWAY_IMAGE&#125;</span><br><span class="line">    container_name: xxx_iot_gateway</span><br><span class="line">    ports:</span><br><span class="line">      - <span class="string">"$&#123;IOT_GATEWAY_PORT&#125;:8081"</span></span><br><span class="line">    environment:</span><br><span class="line">      IOT_GATEWAY_PORT: <span class="number">8081</span></span><br><span class="line">      xxx_URL: $&#123;xxx_URL&#125;</span><br><span class="line">      THINGSBOARD_URL: $&#123;THINGSBOARD_URL&#125;</span><br><span class="line">      TB_POSTGRESQL_URL: $&#123;TB_POSTGRESQL_HOST&#125;</span><br><span class="line">      TB_POSTGRESQL_PORT: $&#123;TB_POSTGRESQL_PORT&#125;</span><br><span class="line">      TB_POSTGRESQL_USERNAME: $&#123;POSTGRES_USER&#125;</span><br><span class="line">      TB_POSTGRESQL_PASSWORD: $&#123;POSTGRES_PASSWORD&#125;</span><br><span class="line">    depends_on:</span><br><span class="line">      thingsboard:</span><br><span class="line">        condition: service_healthy</span><br><span class="line">    restart: always</span><br><span class="line">    <span class="keyword">healthcheck</span><span class="bash">:</span></span><br><span class="line">      test: [<span class="string">"CMD-SHELL"</span>, <span class="string">"curl -f http://localhost:8081/healthcheck/check || exit 1"</span>]</span><br><span class="line">      interval: <span class="number">10</span>s</span><br><span class="line">      timeout: <span class="number">60</span>s</span><br><span class="line">      start_period: <span class="number">600</span>s</span><br><span class="line">      retries: <span class="number">99999</span></span><br><span class="line"></span><br><span class="line">  xxx-admin:</span><br><span class="line">    image: $&#123;xxx_ADMIN_IMAGE&#125;</span><br><span class="line">    container_name: xxx_admin</span><br><span class="line">    ports:</span><br><span class="line">      - <span class="string">"$&#123;xxx_BACKEND_PORT&#125;:8001"</span></span><br><span class="line">    environment:</span><br><span class="line">      xxx_BACKEND_PORT: <span class="number">8001</span></span><br><span class="line">      IOT_GATEWAY_URL: $&#123;IOT_GATEWAY_URL&#125;</span><br><span class="line">      xxx_MYSQL_HOST: $&#123;MYSQL_HOST&#125;</span><br><span class="line">      xxx_MYSQL_PORT: $&#123;MYSQL_PORT&#125;</span><br><span class="line">      xxx_MYSQL_USERNAME: $&#123;MYSQL_USER&#125;</span><br><span class="line">      xxx_MYSQL_PASSWORD: $&#123;MYSQL_PASSWORD&#125;</span><br><span class="line">      xxx_REDIS_HOST: $&#123;REDIS_HOST&#125;</span><br><span class="line">      xxx_REDIS_PORT: $&#123;REDIS_PORT&#125;</span><br><span class="line">      xxx_REDIS_DATABASE: $&#123;REDIS_DATABASE&#125;</span><br><span class="line">      xxx_REDIS_PASSWORD: $&#123;REDIS_PASSWORD&#125;</span><br><span class="line">    depends_on:</span><br><span class="line">      mysql:</span><br><span class="line">        condition: service_healthy</span><br><span class="line">      redis:</span><br><span class="line">        condition: service_healthy</span><br><span class="line">    restart: unless-stopped</span><br><span class="line">    <span class="keyword">healthcheck</span><span class="bash">:</span></span><br><span class="line">      test: [<span class="string">"CMD-SHELL"</span>, <span class="string">"curl -f http://localhost:8001/healthcheck/check || exit 1"</span>]</span><br><span class="line">      interval: <span class="number">10</span>s</span><br><span class="line">      timeout: <span class="number">60</span>s</span><br><span class="line">      start_period: <span class="number">600</span>s</span><br><span class="line">      retries: <span class="number">99999</span></span><br><span class="line"></span><br><span class="line">  nginx:</span><br><span class="line">    image: $&#123;NGINX_IMAGE&#125;</span><br><span class="line">    container_name: xxx_nginx</span><br><span class="line">    ports:</span><br><span class="line">      - <span class="string">"80:80"</span></span><br><span class="line">      - <span class="string">"443:443"</span></span><br><span class="line">    volumes:</span><br><span class="line">      - ./nginx/conf.d:/etc/nginx/conf.d</span><br><span class="line">      - ./nginx/webroot:/home/webroot</span><br><span class="line">    depends_on:</span><br><span class="line">      thingsboard:</span><br><span class="line">        condition: service_healthy</span><br><span class="line">      iot-gateway:</span><br><span class="line">        condition: service_healthy</span><br><span class="line">      xxx-admin:</span><br><span class="line">        condition: service_healthy</span><br><span class="line">    restart: unless-stopped</span><br><span class="line">    <span class="keyword">healthcheck</span><span class="bash">:</span></span><br><span class="line">      test: [<span class="string">"CMD-SHELL"</span>, <span class="string">"curl -f http://localhost || exit 1"</span>]</span><br><span class="line">      interval: <span class="number">30</span>s</span><br><span class="line">      timeout: <span class="number">10</span>s</span><br><span class="line">      retries: <span class="number">10</span></span><br><span class="line">      start_period: <span class="number">60</span>s</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;经过几个月的开发，开发的项目即将进入交付环节。&lt;/p&gt;
&lt;p&gt;然而，客户限制Windows环境部署，但本人对Linux比较熟、中间件在Windows部署也比较麻烦。&lt;/p&gt;
&lt;p&gt;因此，使用两种方法部署，间接使用Linux系统：1. WSL执行一键部署脚本、2.Docker</summary>
      
    
    
    
    <category term="CI" scheme="https://project256.com/categories/CI/"/>
    
    
    <category term="持续集成" scheme="https://project256.com/tags/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90/"/>
    
  </entry>
  
  <entry>
    <title>高年级校招求职指南</title>
    <link href="https://project256.com/schoollife/%E9%AB%98%E5%B9%B4%E7%BA%A7%E6%A0%A1%E6%8B%9B%E6%B1%82%E8%81%8C%E6%8C%87%E5%8D%97/"/>
    <id>https://project256.com/schoollife/%E9%AB%98%E5%B9%B4%E7%BA%A7%E6%A0%A1%E6%8B%9B%E6%B1%82%E8%81%8C%E6%8C%87%E5%8D%97/</id>
    <published>2025-05-23T06:52:39.000Z</published>
    <updated>2026-03-29T09:06:11.645Z</updated>
    
    <content type="html"><![CDATA[<h3 id="高年级校招求职锦囊"><a href="#高年级校招求职锦囊" class="headerlink" title="高年级校招求职锦囊"></a><strong>高年级校招求职锦囊</strong></h3><p>*如果您是非技术类岗位，请忽略下文中一些”过于技术“的点。本文借助DeepSeek总结分享文档生成。</p><hr><h4 id="一、校招核心策略"><a href="#一、校招核心策略" class="headerlink" title="一、校招核心策略"></a><strong>一、校招核心策略</strong></h4><ol><li><p><strong>提升个人“估值”</strong>  </p><ul><li><strong>技能为王</strong>：主攻市场需求大的技能（如Java、Python），刷透《剑指Offer》、<a href="https://leetcode.cn/problem-list/sDvggqTO/" target="_blank" rel="noopener">LeetCode（至少刷高频75题）</a>。  </li><li><strong>项目经验</strong>：没有实战项目？用学校课程设计&#x2F;B站教程现学现做，代码上传GitHub，绿点刷满！  </li><li><strong>简历包装</strong>：用STAR原则写项目，量化成果（如“优化算法使响应速度提升30%”），杜绝“自我评价”。</li></ul></li><li><p><strong>选对赛道与公司</strong>  </p><ul><li><strong>优先头部大厂</strong>：马太效应下，大厂光环助力后续跳槽。  </li><li><strong>业务多样性</strong>：选择多业务线公司（如BAT），容错率高，方便内部转岗。</li></ul></li></ol><hr><h4 id="二、实习：曲线救国进大厂"><a href="#二、实习：曲线救国进大厂" class="headerlink" title="二、实习：曲线救国进大厂"></a><strong>二、实习：曲线救国进大厂</strong></h4><ul><li><strong>为什么实习重要</strong>：  <ul><li>实习转正成功率更高，免去校招“神仙打架”。  </li><li>大厂实习经验是简历“镀金”关键。</li></ul></li><li><strong>如何争取实习</strong>：  <ul><li>盯紧大厂官网&#x2F;学长内推渠道，提前3-6个月投递。  </li><li>实习期间主动表现，争取留用机会。</li></ul></li></ul><hr><h4 id="三、避坑指南"><a href="#三、避坑指南" class="headerlink" title="三、避坑指南"></a><strong>三、避坑指南</strong></h4><ol><li><p><strong>谨慎考研</strong>：  </p><ul><li>考研&#x3D;三年期高风险期货，若非学术热爱，优先就业积累经验。  </li><li>二战可能丢失应届身份，社招门槛更高。</li></ul></li><li><p><strong>拒绝跟风</strong>：  </p><ul><li>不盲目追求热门岗位，结合自身技能与兴趣选择行业。  </li><li>勿借钱&#x2F;加杠杆考研，留足后路。</li></ul></li></ol><hr><h4 id="四、简历与面试"><a href="#四、简历与面试" class="headerlink" title="四、简历与面试"></a><strong>四、简历与面试</strong></h4><ul><li><strong>简历必杀技</strong>：  <ul><li>技术类岗位：PDF+Markdown，简洁直观，联系方式置顶。  </li><li>技术博客&#x2F;Github链接加分，开源项目优先展示。</li></ul></li><li><strong>面试准备</strong>：  <ul><li>熟读《程序员面试宝典》，八股文倒背如流。  </li><li>模拟面试，用“STAR法则”回答项目问题。</li></ul></li></ul><hr><h4 id="五、复利思维：长期成长"><a href="#五、复利思维：长期成长" class="headerlink" title="五、复利思维：长期成长"></a><strong>五、复利思维：长期成长</strong></h4><ul><li><strong>起点决定路径</strong>：首份工作尽量选大厂，薪资与晋升呈复利增长。  </li><li><strong>持续沉淀</strong>：写技术博客、参与开源项目，打造个人品牌。  </li><li><strong>低谷即机遇</strong>：行业下行时修炼内功，行情回暖时抢占先机。</li></ul><hr><h4 id="六、心态调整"><a href="#六、心态调整" class="headerlink" title="六、心态调整"></a><strong>六、心态调整</strong></h4><ul><li><strong>别怂！</strong>：海投简历不要钱，面试挂了没人记得你，下一个更好！  </li><li><strong>行动清单</strong>：  <ul><li>每日刷3题，每周投10份简历，每月复盘一次技能树。</li></ul></li></ul><hr><p><strong>记住：求职是场持久战，提升自己才是硬道理！</strong><br><strong>投就完事了！</strong> 💪</p><h2 id="提前体验校招"><a href="#提前体验校招" class="headerlink" title="提前体验校招"></a>提前体验校招</h2><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/wechat_miniprogram.png" alt="欢迎体验校招模拟器小程序" title="">                </div>                <div class="image-caption">欢迎体验校招模拟器小程序</div>            </figure><p>面向信息类专业同学提供如下功能：</p><ul><li>兴趣 + 技能 + 市场需求驱动方向选择</li><li>20+技术类及非技术类方向简介、技能要求、薪资水平及校招准备建议</li><li>简历填写器，引导填写最关键字段提升简历含金量，导出为markdown格式</li><li>全方向、全难度题目模拟面试</li><li>简历记录及模拟面试错题集</li></ul><p>以校招为终极目标，提前准备，提前起跑。</p><h2 id="原分享文档"><a href="#原分享文档" class="headerlink" title="原分享文档"></a>原分享文档</h2><p>今年是本人在工大分享的第十年，每一年换着主题和方法为大三应届生教授校招求职技巧。</p><p>虽然说不上有多少成果，至少学校连续十年请我讲还没听烦、偶尔还能帮希望就职于信息行业的孩子们一把，就算是一大成就罢。</p><p>很惭愧，就为学生们做了一点微小的工作。</p><p><a href="https://project256.oss-cn-beijing.aliyuncs.com/uPic/2024%E6%A0%A1%E6%8B%9B%E7%BB%8F%E6%B5%8E%E5%AD%A6%E5%8E%9F%E7%90%86_plus.pdf" target="_blank" rel="noopener">校招经济学原理.pdf (2024年学业与专业发展教育讲座)</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;高年级校招求职锦囊&quot;&gt;&lt;a href=&quot;#高年级校招求职锦囊&quot; class=&quot;headerlink&quot; title=&quot;高年级校招求职锦囊&quot;&gt;&lt;/a&gt;&lt;strong&gt;高年级校招求职锦囊&lt;/strong&gt;&lt;/h3&gt;&lt;p&gt;*如果您是非技术类岗位，请忽略下文中一些”过于技术</summary>
      
    
    
    
    <category term="schoollife" scheme="https://project256.com/categories/schoollife/"/>
    
    
  </entry>
  
  <entry>
    <title>什么才是自己的——多做可迁移且能产生复利的事情</title>
    <link href="https://project256.com/work/%E4%BB%80%E4%B9%88%E6%89%8D%E6%98%AF%E8%87%AA%E5%B7%B1%E7%9A%84%E2%80%94%E2%80%94%E5%A4%9A%E5%81%9A%E5%8F%AF%E8%BF%81%E7%A7%BB%E4%B8%94%E8%83%BD%E4%BA%A7%E7%94%9F%E5%A4%8D%E5%88%A9%E7%9A%84%E4%BA%8B%E6%83%85/"/>
    <id>https://project256.com/work/%E4%BB%80%E4%B9%88%E6%89%8D%E6%98%AF%E8%87%AA%E5%B7%B1%E7%9A%84%E2%80%94%E2%80%94%E5%A4%9A%E5%81%9A%E5%8F%AF%E8%BF%81%E7%A7%BB%E4%B8%94%E8%83%BD%E4%BA%A7%E7%94%9F%E5%A4%8D%E5%88%A9%E7%9A%84%E4%BA%8B%E6%83%85/</id>
    <published>2025-05-09T23:16:21.000Z</published>
    <updated>2026-03-29T09:06:11.643Z</updated>
    
    <content type="html"><![CDATA[<h2 id="【多做可迁移且能产生复利的事情】"><a href="#【多做可迁移且能产生复利的事情】" class="headerlink" title="【多做可迁移且能产生复利的事情】"></a>【多做可迁移且能产生复利的事情】</h2><p>最近本人第三次提交香港优才计划，填报时不断思考哪些材料会对我申请永居签证更加有利。</p><p>很惭愧，除了学业和学历证明以外，唯一的专利证书还是17年申请、20年获批。</p><p>那么这几年我在做什么？我的积累是否被外界所承认？</p><p>如果你想让自己被市场所认可，去申请永居签证、去创办一家小公司、去大胆面试罢。</p><h2 id="“画饼”祛魅"><a href="#“画饼”祛魅" class="headerlink" title="“画饼”祛魅"></a>“画饼”祛魅</h2><p>公司为了留人或是激励，总喜欢内部推选优秀员工并给一些称号和纪念品，结果年终钱一分不涨。</p><p>先暂且不讨论钱的问题，对于个人未来发展而言，沉迷内部的荣誉或称号，是百害而无一利的。</p><p>当然，如果铁定要在这家公司升职、干到死，也是有一些作用的，权当我没说。</p><p>当有人尝试对你画饼时，最好的应对方法就是用真实的工时、薪资等数据和自身技能成长说话。</p><h2 id="什么才是自己的"><a href="#什么才是自己的" class="headerlink" title="什么才是自己的"></a>什么才是自己的</h2><p>为了更好的薪资或是发展，总有一天要走出去，或跳槽或创业。</p><p>但在跳槽或是创业的时候，能说自己加班猛、得了XXXX内部奖励么？</p><p>不要在决定跳槽或创业时才发现，除了加班造成的身心创伤以外，口袋空空。</p><p>作为乙方而言，我需要提供足够的证明来证实我的能力或是资质可以符合甲方的需求。</p><p>要么是面试秀肌肉，要么是权威第三方认证的<strong>证书、成绩、论文、专利、期刊著作、出版物、竞赛奖牌、学历学位</strong>。</p><p>前者虽然看起来更容易实现，但又会进入用时间和健康换一次性金钱收入的循环。</p><p>更进一步，我希望我能拥有即使睡觉了，也能稳定进账的能力。</p><h2 id="把自己当成潜伏在公司的间谍，知识和经验能带走多少带走多少"><a href="#把自己当成潜伏在公司的间谍，知识和经验能带走多少带走多少" class="headerlink" title="把自己当成潜伏在公司的间谍，知识和经验能带走多少带走多少"></a>把自己当成潜伏在公司的间谍，知识和经验能带走多少带走多少</h2><p>最近看到有一个说法：“把自己当成潜伏在公司的间谍，知识和经验能带走多少带走多少。“</p><p>我认为这句话形象又精辟，在”被剥削“的同时也得想办法”剥削公司“。</p><p>就算公司无法保证8小时工作、无法确保身心健康，那么也能通过重点做能迁移的事情，让自己变得更有价值。</p><p>去观察大佬的行事风格、去结交互利的人际关系、去积累高质量文档和方案。</p><p>毕竟这个世界无时无刻不衡量着我们的价值，破局之道要么卷出天际、要么探索出能自动产生复利的道路。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;【多做可迁移且能产生复利的事情】&quot;&gt;&lt;a href=&quot;#【多做可迁移且能产生复利的事情】&quot; class=&quot;headerlink&quot; title=&quot;【多做可迁移且能产生复利的事情】&quot;&gt;&lt;/a&gt;【多做可迁移且能产生复利的事情】&lt;/h2&gt;&lt;p&gt;最近本人第三次提交香港优才计</summary>
      
    
    
    
    <category term="work" scheme="https://project256.com/categories/work/"/>
    
    
    <category term="职场" scheme="https://project256.com/tags/%E8%81%8C%E5%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>记一次子线程内无限超时阻塞执行的问题排查</title>
    <link href="https://project256.com/JAVA/%E8%AE%B0%E4%B8%80%E6%AC%A1%E5%AD%90%E7%BA%BF%E7%A8%8B%E5%86%85%E6%97%A0%E9%99%90%E8%B6%85%E6%97%B6%E9%98%BB%E5%A1%9E%E6%89%A7%E8%A1%8C%E7%9A%84%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5/"/>
    <id>https://project256.com/JAVA/%E8%AE%B0%E4%B8%80%E6%AC%A1%E5%AD%90%E7%BA%BF%E7%A8%8B%E5%86%85%E6%97%A0%E9%99%90%E8%B6%85%E6%97%B6%E9%98%BB%E5%A1%9E%E6%89%A7%E8%A1%8C%E7%9A%84%E9%97%AE%E9%A2%98%E6%8E%92%E6%9F%A5/</id>
    <published>2025-01-17T08:56:12.000Z</published>
    <updated>2026-03-29T09:06:11.645Z</updated>
    
    <content type="html"><![CDATA[<p>某日发现定时任务线程池中的所有线程全部阻塞：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/rOU1il.jpg" alt="无限失败阻塞" title="">                </div>                <div class="image-caption">无限失败阻塞</div>            </figure><p>代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 由于任务规模、执行频次较低，直接无界队列。如果高频任务建议new ThreadPoolExecutor</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ExecutorService royalFlushExecutor = Executors.newFixedThreadPool(RoyalFlush.THREAD_NUM, <span class="keyword">new</span> NamedThreadFactory(<span class="string">"royalFlushExecutor"</span>, <span class="keyword">false</span>));</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> List&lt;StockRoyalFlushDto&gt; <span class="title">fetchRoyalFlushData</span><span class="params">(List&lt;StockBaseInfoEntity&gt; stockBaseInfoEntities)</span> </span>&#123;</span><br><span class="line">    String currentTime = DateUtil.currentTimeStr();</span><br><span class="line"></span><br><span class="line">    List&lt;StockRoyalFlushDto&gt; results = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    List&lt;Future&lt;StockRoyalFlushDto&gt;&gt; futures = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    System.setProperty(<span class="string">"https.protocols"</span>, <span class="string">"TLSv1,TLSv1.1,TLSv1.2"</span>);</span><br><span class="line"></span><br><span class="line">    AtomicInteger current = <span class="keyword">new</span> AtomicInteger(<span class="number">0</span>);</span><br><span class="line">    <span class="keyword">for</span> (StockBaseInfoEntity stockBaseInfoEntity : stockBaseInfoEntities) &#123;</span><br><span class="line">        String url = String.format(String.format(RoyalFlush.AI_URL, stockBaseInfoEntity.getCode() + RoyalFlush.QUERY));</span><br><span class="line">        futures.add(royalFlushExecutor.submit(() -&gt; &#123;</span><br><span class="line">            log.info(<span class="string">"royalFlushData-当前进度:&#123;&#125;/&#123;&#125;-启动时间:&#123;&#125;"</span>, current.getAndIncrement(), stockBaseInfoEntities.size(), currentTime);</span><br><span class="line">            <span class="keyword">return</span> fetchAndParseData(stockBaseInfoEntity, url);</span><br><span class="line">        &#125;));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (Future&lt;StockRoyalFlushDto&gt; future : futures) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            StockRoyalFlushDto stockRoyalFlushDto = future.get(<span class="number">1</span>, java.util.concurrent.TimeUnit.SECONDS);</span><br><span class="line">            <span class="keyword">if</span> (stockRoyalFlushDto != <span class="keyword">null</span>) &#123;</span><br><span class="line">                results.add(stockRoyalFlushDto);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">"royalFlushData-任务执行失败:&#123;&#125; e:&#123;&#125;"</span>, e.getMessage(), e.toString());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> results;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>尝试复现，发现一段时间后1号线程池不见了：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/bJR2HW.jpg" alt="1号线程没了" title="">                </div>                <div class="image-caption">1号线程没了</div>            </figure><h1 id="定位排查"><a href="#定位排查" class="headerlink" title="定位排查"></a>定位排查</h1><p>使用ps aux | grep java能够定位进程号，当然日志的INFO后面就是进程id，为22385</p><p>使用top -H -p命令找到该进程：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">top -H -p 22385</span><br></pre></td></tr></table></figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/GpMJwl.jpg" alt="挂掉的线程" title="">                </div>                <div class="image-caption">挂掉的线程</div>            </figure><p>使用jstack查看线程状态及堆栈信息：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jstack 22385 | grep -C 50 22426</span><br></pre></td></tr></table></figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/UPmTQW.jpg" alt="jstack结果" title="">                </div>                <div class="image-caption">jstack结果</div>            </figure><p>能看到线程是RUNNABLE状态，下意识可能认为RUNNABLE状态并没有执行，但RUNNABLE还被分为Running和Ready两种状态。</p><p>也就是说，处于RUNNABLE状态的线程有可能正在运行，也有可能没有正在执行、等待分配CPU资源。</p><p>因此，一个处于RUNNING状态的线程，当运行到任务一半时，执行该线程的CPU被调度去做其他事情，则该线程暂时不运行。但它的状态不变，还是RUNNABLE，因为它有可能随时被调度回来继续执行任务。</p><h1 id="源码排查及修正"><a href="#源码排查及修正" class="headerlink" title="源码排查及修正"></a>源码排查及修正</h1><p>定位了逻辑入口后，看依赖方法的底层代码，发现timeout为-1，即不超时：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/yMGbTG.jpg" alt="默认timeout为-1，即不限制" title="">                </div>                <div class="image-caption">默认timeout为-1，即不限制</div>            </figure><p>在原有代码第90行的上方加上超时时间：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/4jdIMN.jpg" alt="超时时间设置" title="">                </div>                <div class="image-caption">超时时间设置</div>            </figure><p>问题顺利解决，其实官方文档也有提示，一定进行修改防止把系统拖死。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/2QMrYe.jpg" alt="HuTool官方文档提示" title="">                </div>                <div class="image-caption">HuTool官方文档提示</div>            </figure><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>远程调用一定设置超时时间</li><li>线程池中的线程被阻塞了，尽管取结果时配置了超时，但也只是取逻辑可以往下走，执行线程还是在那里阻塞着</li><li>最新版本的Java，jstack可以不用把线程转为8进制即可查询</li><li>日志打印线程信息重要性，不要随便用sout打信息</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;某日发现定时任务线程池中的所有线程全部阻塞：&lt;/p&gt;
&lt;figure class=&quot;image-bubble&quot;&gt;
                &lt;div class=&quot;img-lightbox&quot;&gt;
                    &lt;div class=&quot;overl</summary>
      
    
    
    
    <category term="JAVA" scheme="https://project256.com/categories/JAVA/"/>
    
    
    <category term="JAVA" scheme="https://project256.com/tags/JAVA/"/>
    
  </entry>
  
  <entry>
    <title>OOM排查及解决</title>
    <link href="https://project256.com/JAVA/OOM%E6%8E%92%E6%9F%A5%E5%8F%8A%E8%A7%A3%E5%86%B3/"/>
    <id>https://project256.com/JAVA/OOM%E6%8E%92%E6%9F%A5%E5%8F%8A%E8%A7%A3%E5%86%B3/</id>
    <published>2025-01-10T07:54:15.000Z</published>
    <updated>2026-03-29T09:06:11.642Z</updated>
    
    <content type="html"><![CDATA[<p>某天在1C1G小实例上定时跑的任务没有执行，查看日志发现OOM了。</p><p>之前写过JAVA Dump小连招，但更偏重于死锁等问题，本文偏重于内存OOM问题。</p><p>日志如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">2025-01-10 11:55:01.155  INFO 7036 --- [ol-224-thread-1] c.p.bs.service.impl.ScheduleServiceImpl  : fetchAndSaveStockFundFlow schedule job start</span><br><span class="line">Exception in thread &quot;pool-224-thread-1&quot; java.lang.OutOfMemoryError: Java heap space</span><br></pre></td></tr></table></figure><p>当时代码写得比较暴力，线程池没有命名、一次性将相关数据取出、调用远程服务获取数据、然后一次性灌入数据库中。</p><p>因此，闭着眼睛也能猜到1G的小实例默认只分配300M左右堆内存，又加上每个任务设置了5个线程同时操作，所以就算没有内存泄露，OOM也只是时间问题。</p><h1 id="DUMP"><a href="#DUMP" class="headerlink" title="DUMP"></a>DUMP</h1><p>第一步需要将内存快照dump下来，一般使用jmap或jhsdb操作。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jmap -dump:live,format&#x3D;b,file&#x3D;heapDump.hprof [PID]</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jhsdb jmap --binaryheap --pid [PID]</span><br></pre></td></tr></table></figure><p>虽然其他博主建议使用jhsdb，但执行的时候也OOM了，实属套娃。</p><p>执行jamp后，在执行位置获得了heapDump.hprof文件。</p><p>接下来但得想办法把文件从服务器导出，方法有很多种，scp也可以。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">scp username@servername:/remote_path/filename ~/local_destination</span><br></pre></td></tr></table></figure><p>也可以用Nginx搭一个文件服务器，方便随时下载其他数据。（注意数据安全问题，不要在目录中存放敏感数据，内存dump也会暴露敏感数据）</p><h1 id="Nginx文件服务器搭建"><a href="#Nginx文件服务器搭建" class="headerlink" title="Nginx文件服务器搭建"></a>Nginx文件服务器搭建</h1><p>起手安装nginx</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install nginx</span><br></pre></td></tr></table></figure><p>创建文件服务器使用的公开文件夹</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkdir -p /var/www/</span><br></pre></td></tr></table></figure><p><strong>注意：请不要在&#x2F;root文件夹下新建服务器文件夹，会有403权限问题。</strong></p><p>样例报错：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">2025&#x2F;01&#x2F;10 15:25:15 [error] 30497#30497: *18 &quot;&#x2F;root&#x2F;fileroot&#x2F;index.html&quot; is forbidden (13: Permission denied), client: IP, server: , request: &quot;GET &#x2F; HTTP&#x2F;1.1&quot;, host: &quot;IP&quot;</span><br></pre></td></tr></table></figure><p>修改权限和所有者，优于默认是root用户，就不加sudo了。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 修改权限和所有者</span></span><br><span class="line">chown -R nginx:nginx /var/www/</span><br><span class="line">chmod -R 755 /var/www/</span><br></pre></td></tr></table></figure><p>创建nginx配置：&#x2F;etc&#x2F;nginx&#x2F;conf.d&#x2F;file.conf</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">server</span> &#123;</span><br><span class="line">  <span class="attribute">listen</span> <span class="number">80</span>;</span><br><span class="line">  <span class="attribute">location</span> / &#123;</span><br><span class="line">    <span class="attribute">root</span> /var/www/;</span><br><span class="line">    <span class="attribute">autoindex</span> <span class="literal">on</span>;</span><br><span class="line">    <span class="attribute">autoindex_exact_size</span> <span class="literal">off</span>;</span><br><span class="line">    <span class="attribute">autoindex_localtime</span> <span class="literal">on</span>;</span><br><span class="line">    <span class="comment"># 添加跨域设置</span></span><br><span class="line">    <span class="attribute">add_header</span> <span class="string">'Access-Control-Allow-Origin'</span> <span class="string">'*'</span>;</span><br><span class="line">    <span class="attribute">add_header</span> <span class="string">'Access-Control-Allow-Methods'</span> <span class="string">'GET, POST, OPTIONS'</span>;</span><br><span class="line">    <span class="attribute">add_header</span> <span class="string">'Access-Control-Allow-Headers'</span> <span class="string">'Authorization'</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 开启访问日志记录</span></span><br><span class="line">    <span class="attribute">access_log</span> /var/log/nginx/access.log;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>启动服务</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl start nginx</span><br></pre></td></tr></table></figure><p>将第一步dump结果移动到&#x2F;var&#x2F;www文件夹下，打开浏览器，输入ip地址即可看到对应的文件夹内容。</p><p>如果是一次性服务器，下载后使用systemctl stop nginx关闭nginx即可。</p><h1 id="解析"><a href="#解析" class="headerlink" title="解析"></a>解析</h1><p>解析工具很多，但Intellij IDE能直接打开并分析hprof文件。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/KmqaUx.jpg" alt="解析hprof" title="">                </div>                <div class="image-caption">解析hprof</div>            </figure><p>能够看到哪些DTO数量比较多，很显然，问题就出现在不断地【一次性将相关数据取出】，产生了太多的StockBaseInfoEntity，占用159.8MB。上面也说了，JAVA默认的最大堆内存为机器内存的25%，所以很容易就OOM了。</p><h1 id="代码优化"><a href="#代码优化" class="headerlink" title="代码优化"></a>代码优化</h1><h2 id="公共数据静态化"><a href="#公共数据静态化" class="headerlink" title="公共数据静态化"></a>公共数据静态化</h2><p>由于多个任务都需要同一份公共数据，而原有的代码中会不断全量拉取公共数据，因此导致内存不断膨胀。</p><p>因此，将公共数据静态化，多次读取操作指向同一块内存。</p><p>但也需要注意 1.工程冷启动时需要初始化数据、2.线程安全问题。</p><h2 id="批量分片"><a href="#批量分片" class="headerlink" title="批量分片"></a>批量分片</h2><p>将全部查出、全部处理、全部写入改为每1K或是1百条操作一次。</p><p>可以用guava直接分片，这里不新增依赖，使用原生的for方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; stockBaseInfoEntities.size(); i += Base.BATCH_SIZE) &#123;</span><br><span class="line">    Date lowestDate = fetchService.fetchAndSaveBsPointInstruct(stockBaseInfoEntities.subList(i, Math.min(i + Base.BATCH_SIZE, stockBaseInfoEntities.size())));</span><br><span class="line">    <span class="keyword">if</span> (lowestDate == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">continue</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    fetchService.calcLastSignalDaysAndCloseDiff();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="线程池命名"><a href="#线程池命名" class="headerlink" title="线程池命名"></a>线程池命名</h2><p>这里直接用了hutool的NamedThreadFactory类。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ExecutorService instructExecutorService = Executors.newFixedThreadPool(BsInstruct.THREAD_NUM, <span class="keyword">new</span> NamedThreadFactory(<span class="string">"InstructExecutor"</span>, <span class="keyword">false</span>));</span><br></pre></td></tr></table></figure><h2 id="修改jar运行参数"><a href="#修改jar运行参数" class="headerlink" title="修改jar运行参数"></a>修改jar运行参数</h2><p>调整堆大小：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -Xms128m -Xmx750m -jar yourapp.jar</span><br></pre></td></tr></table></figure><p>新增GC相关参数：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -Xms128m -Xmx750m -XX:+PrintGCDetails -jar yourapp.jar</span><br></pre></td></tr></table></figure><h1 id="优化后"><a href="#优化后" class="headerlink" title="优化后"></a>优化后</h1><p>338MB → 59.6MB</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/xxvmtD.jpg" alt="优化后" title="">                </div>                <div class="image-caption">优化后</div>            </figure><h1 id="附：JAVA额外参数"><a href="#附：JAVA额外参数" class="headerlink" title="附：JAVA额外参数"></a>附：JAVA额外参数</h1><p>使用java -X，能看到所有的额外参数和解释。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><span class="line">java -X</span><br><span class="line"></span><br><span class="line">    -Xbatch           禁用后台编译</span><br><span class="line">    -Xbootclasspath&#x2F;a:&lt;以 : 分隔的目录和 zip&#x2F;jar 文件&gt;</span><br><span class="line">                      附加在引导类路径末尾</span><br><span class="line">    -Xcheck:jni       对 JNI 函数执行其他检查</span><br><span class="line">    -Xcomp            强制在首次调用时编译方法</span><br><span class="line">    -Xdebug           不执行任何操作；已过时，将在未来发行版中删除。</span><br><span class="line">    -Xdiag            显示附加诊断消息</span><br><span class="line">    -Xfuture          启用最严格的检查，预期将来的默认值。</span><br><span class="line">                      此选项已过时，可能会在</span><br><span class="line">                      未来发行版中删除。</span><br><span class="line">    -Xint             仅解释模式执行</span><br><span class="line">    -Xinternalversion</span><br><span class="line">                      显示比 -version 选项更详细的</span><br><span class="line">                      JVM 版本信息</span><br><span class="line">    -Xlog:&lt;opts&gt;      配置或启用采用 Java 虚拟</span><br><span class="line">                      机 (Java Virtual Machine, JVM) 统一记录框架进行事件记录。使用 -Xlog:help</span><br><span class="line">                      可了解详细信息。</span><br><span class="line">    -Xloggc:&lt;file&gt;    将 GC 状态记录在文件中（带时间戳）。</span><br><span class="line">                      此选项已过时，可能会在</span><br><span class="line">                      将来的发行版中删除。它将替换为 -Xlog:gc:&lt;file&gt;。</span><br><span class="line">    -Xmixed           混合模式执行（默认值）</span><br><span class="line">    -Xmn&lt;size&gt;        为年轻代（新生代）设置初始和最大堆大小</span><br><span class="line">                      （以字节为单位）</span><br><span class="line">    -Xms&lt;size&gt;        设置初始 Java 堆大小</span><br><span class="line">    -Xmx&lt;size&gt;        设置最大 Java 堆大小</span><br><span class="line">    -Xnoclassgc       禁用类垃圾收集</span><br><span class="line">    -Xrs              减少 Java&#x2F;VM 对操作系统信号的使用（请参见文档）</span><br><span class="line">    -Xshare:auto      在可能的情况下使用共享类数据（默认值）</span><br><span class="line">    -Xshare:off       不尝试使用共享类数据</span><br><span class="line">    -Xshare:on        要求使用共享类数据，否则将失败。</span><br><span class="line">                      这是一个测试选项，可能导致间歇性</span><br><span class="line">                      故障。不应在生产环境中使用它。</span><br><span class="line">    -XshowSettings    显示所有设置并继续</span><br><span class="line">    -XshowSettings:all</span><br><span class="line">                      显示所有设置并继续</span><br><span class="line">    -XshowSettings:locale</span><br><span class="line">                      显示所有与区域设置相关的设置并继续</span><br><span class="line">    -XshowSettings:properties</span><br><span class="line">                      显示所有属性设置并继续</span><br><span class="line">    -XshowSettings:vm</span><br><span class="line">                      显示所有与 vm 相关的设置并继续</span><br><span class="line">    -XshowSettings:security</span><br><span class="line">                      显示所有安全设置并继续</span><br><span class="line">    -XshowSettings:security:all</span><br><span class="line">                      显示所有安全设置并继续</span><br><span class="line">    -XshowSettings:security:properties</span><br><span class="line">                      显示安全属性并继续</span><br><span class="line">    -XshowSettings:security:providers</span><br><span class="line">                      显示静态安全提供方设置并继续</span><br><span class="line">    -XshowSettings:security:tls</span><br><span class="line">                      显示与 TLS 相关的安全设置并继续</span><br><span class="line">    -XshowSettings:system</span><br><span class="line">                      （仅 Linux）显示主机系统或容器</span><br><span class="line">                      配置并继续</span><br><span class="line">    -Xss&lt;size&gt;        设置 Java 线程堆栈大小</span><br><span class="line">                      实际大小可以舍入到</span><br><span class="line">                      操作系统要求的系统页面大小的倍数。</span><br><span class="line">    -Xverify          设置字节码验证器的模式</span><br><span class="line">                      请注意，选项 -Xverify:none 已过时，</span><br><span class="line">                      可能会在未来发行版中删除。</span><br><span class="line">    --add-reads &lt;module&gt;&#x3D;&lt;target-module&gt;(,&lt;target-module&gt;)*</span><br><span class="line">                      更新 &lt;module&gt; 以读取 &lt;target-module&gt;，而无论</span><br><span class="line">                      模块如何声明。</span><br><span class="line">                      &lt;target-module&gt; 可以是 ALL-UNNAMED，将读取所有未命名</span><br><span class="line">                      模块。</span><br><span class="line">    --add-exports &lt;module&gt;&#x2F;&lt;package&gt;&#x3D;&lt;target-module&gt;(,&lt;target-module&gt;)*</span><br><span class="line">                      更新 &lt;module&gt; 以将 &lt;package&gt; 导出到 &lt;target-module&gt;，</span><br><span class="line">                      而无论模块如何声明。</span><br><span class="line">                      &lt;target-module&gt; 可以是 ALL-UNNAMED，将导出到所有</span><br><span class="line">                      未命名模块。</span><br><span class="line">    --add-opens &lt;module&gt;&#x2F;&lt;package&gt;&#x3D;&lt;target-module&gt;(,&lt;target-module&gt;)*</span><br><span class="line">                      更新 &lt;module&gt; 以在 &lt;target-module&gt; 中打开</span><br><span class="line">                      &lt;package&gt;，而无论模块如何声明。</span><br><span class="line">    --limit-modules &lt;module name&gt;[,&lt;module name&gt;...]</span><br><span class="line">                      限制可观察模块的领域</span><br><span class="line">    --patch-module &lt;module&gt;&#x3D;&lt;file&gt;(:&lt;file&gt;)*</span><br><span class="line">                      使用 JAR 文件或目录中的类和资源</span><br><span class="line">                      覆盖或增强模块。</span><br><span class="line">    --source &lt;version&gt;</span><br><span class="line">                      设置源文件模式中源的版本。</span><br><span class="line">    --finalization&#x3D;&lt;value&gt;</span><br><span class="line">                      控制 JVM 是否执行对象最终处理，</span><br><span class="line">                      其中 &lt;value&gt; 为 &quot;enabled&quot; 或 &quot;disabled&quot; 之一。</span><br><span class="line">                      默认情况下，最终处理处于启用状态。</span><br></pre></td></tr></table></figure><p>GC日志相关选项，转自 <a href="https://www.cnblogs.com/dupengpeng/p/17620200.html" target="_blank" rel="noopener">https://www.cnblogs.com/dupengpeng/p/17620200.html</a> ：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">-XX:+PrintGC &lt;&#x3D;&#x3D;&gt; -verbose:gc  打印简要日志信息</span><br><span class="line">-XX:+PrintGCDetails            打印详细日志信息</span><br><span class="line">-XX:+PrintGCTimeStamps  打印程序启动到GC发生的时间，搭配-XX:+PrintGCDetails使用</span><br><span class="line">-XX:+PrintGCDateStamps  打印GC发生时的时间戳，搭配-XX:+PrintGCDetails使用</span><br><span class="line">-XX:+PrintHeapAtGC  打印GC前后的堆信息，如下图</span><br><span class="line">-Xloggc:&lt;file&gt; 输出GC导指定路径下的文件中</span><br><span class="line">-XX:+TraceClassLoading  监控类的加载</span><br><span class="line">-XX:+PrintGCApplicationStoppedTime  打印GC时线程的停顿时间</span><br><span class="line">-XX:+PrintGCApplicationConcurrentTime  打印垃圾收集之前应用未中断的执行时间</span><br><span class="line">-XX:+PrintReferenceGC 打印回收了多少种不同引用类型的引用</span><br><span class="line">-XX:+PrintTenuringDistribution  打印JVM在每次MinorGC后当前使用的Survivor中对象的年龄分布</span><br><span class="line">-XX:+UseGCLogFileRotation 启用GC日志文件的自动转储</span><br><span class="line">-XX:NumberOfGCLogFiles&#x3D;1  设置GC日志文件的循环数目</span><br><span class="line">-XX:GCLogFileSize&#x3D;1M  设置GC日志文件的大小</span><br></pre></td></tr></table></figure><h1 id="附2：jhsdb-jmap查看堆信息"><a href="#附2：jhsdb-jmap查看堆信息" class="headerlink" title="附2：jhsdb jmap查看堆信息"></a>附2：jhsdb jmap查看堆信息</h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jhsdb jmap --pid [PID] --heap</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line">Attaching to process ID 31724, please wait...</span><br><span class="line">Debugger attached successfully.</span><br><span class="line">Server compiler detected.</span><br><span class="line">JVM version is 21.0.5+9-LTS-239</span><br><span class="line"></span><br><span class="line">using thread-local object allocation.</span><br><span class="line">Mark Sweep Compact GC</span><br><span class="line"></span><br><span class="line">Heap Configuration:</span><br><span class="line">   MinHeapFreeRatio         &#x3D; 40</span><br><span class="line">   MaxHeapFreeRatio         &#x3D; 70</span><br><span class="line">   MaxHeapSize              &#x3D; 786432000 (750.0MB)</span><br><span class="line">   NewSize                  &#x3D; 44695552 (42.625MB)</span><br><span class="line">   MaxNewSize               &#x3D; 262144000 (250.0MB)</span><br><span class="line">   OldSize                  &#x3D; 89522176 (85.375MB)</span><br><span class="line">   NewRatio                 &#x3D; 2</span><br><span class="line">   SurvivorRatio            &#x3D; 8</span><br><span class="line">   MetaspaceSize            &#x3D; 22020096 (21.0MB)</span><br><span class="line">   CompressedClassSpaceSize &#x3D; 1073741824 (1024.0MB)</span><br><span class="line">   MaxMetaspaceSize         &#x3D; 17592186044415 MB</span><br><span class="line"></span><br><span class="line">Heap Usage:</span><br><span class="line">New Generation (Eden + 1 Survivor Space):</span><br><span class="line">   capacity &#x3D; 63569920 (60.625MB)</span><br><span class="line">   used     &#x3D; 972208 (0.9271697998046875MB)</span><br><span class="line">   free     &#x3D; 62597712 (59.69783020019531MB)</span><br><span class="line">   1.5293522471005154% used</span><br><span class="line">Eden Space:</span><br><span class="line">   capacity &#x3D; 56557568 (53.9375MB)</span><br><span class="line">   used     &#x3D; 972208 (0.9271697998046875MB)</span><br><span class="line">   free     &#x3D; 55585360 (53.01033020019531MB)</span><br><span class="line">   1.7189706601245656% used</span><br><span class="line">From Space:</span><br><span class="line">   capacity &#x3D; 7012352 (6.6875MB)</span><br><span class="line">   used     &#x3D; 0 (0.0MB)</span><br><span class="line">   free     &#x3D; 7012352 (6.6875MB)</span><br><span class="line">   0.0% used</span><br><span class="line">To Space:</span><br><span class="line">   capacity &#x3D; 7012352 (6.6875MB)</span><br><span class="line">   used     &#x3D; 0 (0.0MB)</span><br><span class="line">   free     &#x3D; 7012352 (6.6875MB)</span><br><span class="line">   0.0% used</span><br><span class="line">tenured generation:</span><br><span class="line">   capacity &#x3D; 140910592 (134.3828125MB)</span><br><span class="line">   used     &#x3D; 38475656 (36.69324493408203MB)</span><br><span class="line">   free     &#x3D; 102434936 (97.68956756591797MB)</span><br><span class="line">   27.305013380399394% used</span><br></pre></td></tr></table></figure><h1 id="附3：jhsdb-jmap-histo查看class内存情况"><a href="#附3：jhsdb-jmap-histo查看class内存情况" class="headerlink" title="附3：jhsdb jmap histo查看class内存情况"></a>附3：jhsdb jmap histo查看class内存情况</h1><p>按照内存使用量逆序排列，注意会打印一堆信息，建议带上more。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jhsdb jmap --pid [PID] --histo | more</span><br></pre></td></tr></table></figure><p>前面一堆信息中只有一个眼熟的。这个entity就是上文说的静态化对象，因此内存泄露问题解决。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/ejiQpm.jpg" alt="histo" title="">                </div>                <div class="image-caption">histo</div>            </figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;某天在1C1G小实例上定时跑的任务没有执行，查看日志发现OOM了。&lt;/p&gt;
&lt;p&gt;之前写过JAVA Dump小连招，但更偏重于死锁等问题，本文偏重于内存OOM问题。&lt;/p&gt;
&lt;p&gt;日志如下：&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;tabl</summary>
      
    
    
    
    <category term="JAVA" scheme="https://project256.com/categories/JAVA/"/>
    
    
    <category term="JAVA" scheme="https://project256.com/tags/JAVA/"/>
    
  </entry>
  
  <entry>
    <title>自动化构建及部署Java工程</title>
    <link href="https://project256.com/JAVA/%E8%87%AA%E5%8A%A8%E5%8C%96%E6%9E%84%E5%BB%BA%E5%8F%8A%E9%83%A8%E7%BD%B2Java%E5%B7%A5%E7%A8%8B/"/>
    <id>https://project256.com/JAVA/%E8%87%AA%E5%8A%A8%E5%8C%96%E6%9E%84%E5%BB%BA%E5%8F%8A%E9%83%A8%E7%BD%B2Java%E5%B7%A5%E7%A8%8B/</id>
    <published>2025-01-08T11:51:54.000Z</published>
    <updated>2026-03-29T09:06:11.644Z</updated>
    
    <content type="html"><![CDATA[<p>最近业余搞了一堆JAVA项目，每次都是手动git push再pull、编译打包运行，感觉很low。</p><p>尤其买了一堆各个区域的云服务器，规模一扩大，手动运维的效率急剧下降。</p><p>这个博客站点其实用的是Github Action自动触发编译部署，我把本文在本机写好，git push即可完成发布。</p><p>因此，JAVA相关项目我也准备使用CI工具自动部署和运维。本人之前也做了好几年SRE，另外阿里云效也和每天操作的公司内部CI工具类似，所以上手很快。</p><h1 id="JAVA工程MVN配置"><a href="#JAVA工程MVN配置" class="headerlink" title="JAVA工程MVN配置"></a>JAVA工程MVN配置</h1><p>JAVA工程结构为</p><p>gateway</p><p>|-starter</p><p>|-tb</p><p>其中启动类在starter中，tb里面主要是业务逻辑。</p><p>尝试了一些打包插件，但发现执行starter打出的jar执行后并不包含tb模块。</p><p>因此使用maven-shade-plugin作为打包插件。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-compiler-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">annotationProcessorPaths</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">path</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.projectlombok<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lombok<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;/<span class="name">path</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">annotationProcessorPaths</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">skip</span>&gt;</span>true<span class="tag">&lt;/<span class="name">skip</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-shade-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">executions</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">execution</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">id</span>&gt;</span>shade-jar-with-dependencies<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">phase</span>&gt;</span>package<span class="tag">&lt;/<span class="name">phase</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">goals</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">goal</span>&gt;</span>shade<span class="tag">&lt;/<span class="name">goal</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;/<span class="name">goals</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">finalName</span>&gt;</span>starter<span class="tag">&lt;/<span class="name">finalName</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">transformers</span>&gt;</span></span><br><span class="line">                                <span class="tag">&lt;<span class="name">transformer</span> <span class="attr">implementation</span>=<span class="string">"org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"</span>&gt;</span></span><br><span class="line">                                    <span class="tag">&lt;<span class="name">manifestEntries</span>&gt;</span></span><br><span class="line">                                        <span class="tag">&lt;<span class="name">Main-Class</span>&gt;</span>com.project256.iot.gateway.Application<span class="tag">&lt;/<span class="name">Main-Class</span>&gt;</span></span><br><span class="line">                                    <span class="tag">&lt;/<span class="name">manifestEntries</span>&gt;</span></span><br><span class="line">                                <span class="tag">&lt;/<span class="name">transformer</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;/<span class="name">transformers</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">execution</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">executions</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br></pre></td></tr></table></figure><p>finalName为starter是为了指定输出jar包的文件名，避免包版本升级后，部署脚本也得跟着改。</p><p>Main-Class指定启动入口类，避免找不到启动类的情况。</p><h1 id="部署脚本编写"><a href="#部署脚本编写" class="headerlink" title="部署脚本编写"></a>部署脚本编写</h1><p>让大模型帮我写，描述清楚需求即可。</p><p>虽然杀死进程不是很优雅，先用着。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">我通过持续集成工具，将starter.jar部署到&#x2F;root&#x2F;app文件夹</span><br><span class="line">但我需要通过deploy.sh停止已经启动的服务，并后台运行java -jar starter.jar</span><br><span class="line">类似nohup，在日志文件中输出日志</span><br><span class="line"></span><br><span class="line">请编写deploy.sh</span><br></pre></td></tr></table></figure><p>得到脚本，命名为deploy.sh</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置变量</span></span><br><span class="line">APP_NAME=<span class="string">"starter"</span></span><br><span class="line">JAR_PATH=<span class="string">"/root/app/starter.jar"</span></span><br><span class="line">LOG_FILE=<span class="string">"/root/app/starter.log"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 停止已运行的服务</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"Stopping existing <span class="variable">$APP_NAME</span> service..."</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查找正在运行的 Java 进程并杀死它</span></span><br><span class="line">PID=$(pgrep -f <span class="variable">$APP_NAME</span>)</span><br><span class="line"><span class="keyword">if</span> [ -n <span class="string">"<span class="variable">$PID</span>"</span> ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"Found running <span class="variable">$APP_NAME</span> with PID: <span class="variable">$PID</span>. Stopping it..."</span></span><br><span class="line">    <span class="built_in">kill</span> -9 <span class="variable">$PID</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"<span class="variable">$APP_NAME</span> stopped."</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"<span class="variable">$APP_NAME</span> is not running."</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动新的服务</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"Starting <span class="variable">$APP_NAME</span> service..."</span></span><br><span class="line">nohup java -jar <span class="variable">$JAR_PATH</span> &gt;&gt; <span class="variable">$LOG_FILE</span> 2&gt;&amp;1 &amp;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 输出启动信息</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"<span class="variable">$APP_NAME</span> started. Logs are being written to <span class="variable">$LOG_FILE</span>."</span></span><br></pre></td></tr></table></figure><p>将其放置到starter模块的resources文件夹下，当然也可以放到其他文件夹，由CI工具进行复制并执行。</p><h1 id="配置云效代码库"><a href="#配置云效代码库" class="headerlink" title="配置云效代码库"></a>配置云效代码库</h1><p>云效拉取Github过于缓慢，10分钟都拉不下来阻塞后续流程，而拉取耗时也走资源用量（每月会送3000核分）。</p><p>因此将Github代码同步一份到代码管理中，并配置.git&#x2F;config，当执行git push时自动双推。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/YYlJvV.jpg" alt="导入代码库" title="">                </div>                <div class="image-caption">导入代码库</div>            </figure><p>具体操作看官方文档即可，再把本机的rsa公钥配置到云效代码库中。</p><p>.git&#x2F;config配置如下 ，在remote “origin”下方配置两个目标url。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">[core]</span><br><span class="line">    repositoryformatversion &#x3D; 0</span><br><span class="line">    filemode &#x3D; true</span><br><span class="line">    bare &#x3D; false</span><br><span class="line">    logallrefupdates &#x3D; true</span><br><span class="line">    ignorecase &#x3D; true</span><br><span class="line">    precomposeunicode &#x3D; true</span><br><span class="line">[remote &quot;origin&quot;]</span><br><span class="line">    url &#x3D; git@github.com:SUTFutureCoder&#x2F;XXX.git</span><br><span class="line">    url &#x3D; git@codeup.aliyun.com:xxx&#x2F;SUTFutureCoder&#x2F;XXX.git</span><br><span class="line">    fetch &#x3D; +refs&#x2F;heads&#x2F;*:refs&#x2F;remotes&#x2F;origin&#x2F;*</span><br><span class="line">[branch &quot;main&quot;]</span><br><span class="line">    remote &#x3D; origin</span><br><span class="line">    merge &#x3D; refs&#x2F;heads&#x2F;main</span><br></pre></td></tr></table></figure><h1 id="配置流水线"><a href="#配置流水线" class="headerlink" title="配置流水线"></a>配置流水线</h1><p>可以选择包含构建上传和主机部署的流程模板，后续把代码扫描和单元测试删掉即可。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/kKtdex.jpg" alt="新建流水线" title="">                </div>                <div class="image-caption">新建流水线</div>            </figure><p>注意修改构建上传的配置，将脚本复制进打包路径中，默认的配置会报错。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/DWDnE2.jpg" alt="构建配置" title="">                </div>                <div class="image-caption">构建配置</div>            </figure><p>JAVA构建上传的产物，能够通过不同策略部署到单台或多台机器上。</p><p>由于是个人开发、部署单台服务器，所以不用什么SOP，直接无脑不暂停即可。</p><p>当多人开发、部署集群时，操作SOP和灰度、回滚策略就需要考虑了。</p><p>主机直接按照指引执行命令注册即可。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/ZYn40f.jpg" alt="主机部署" title="">                </div>                <div class="image-caption">主机部署</div>            </figure><h1 id="登录机器观察"><a href="#登录机器观察" class="headerlink" title="登录机器观察"></a>登录机器观察</h1><p>登录机器后，查看app目录，观察jar包、脚本是否被正确部署。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/3lyAAa.jpg" alt="观察部署结果" title="">                </div>                <div class="image-caption">观察部署结果</div>            </figure><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>相比于自建Jenkins，对于个人项目使用云服务就足够了。</p><p>编译成Docker镜像也可以，但对于个人小项目有点大炮打蚊子了。</p><p>流水线的Webhook触发运行不推荐使用，除非能确保每次推送代码都不会出错，这种触发方式和无脑一把梭没什么区别。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;最近业余搞了一堆JAVA项目，每次都是手动git push再pull、编译打包运行，感觉很low。&lt;/p&gt;
&lt;p&gt;尤其买了一堆各个区域的云服务器，规模一扩大，手动运维的效率急剧下降。&lt;/p&gt;
&lt;p&gt;这个博客站点其实用的是Github Action自动触发编译部署，我把本文在</summary>
      
    
    
    
    <category term="JAVA" scheme="https://project256.com/categories/JAVA/"/>
    
    
    <category term="JAVA" scheme="https://project256.com/tags/JAVA/"/>
    
  </entry>
  
  <entry>
    <title>股票F10技术指标</title>
    <link href="https://project256.com/quant/%E8%82%A1%E7%A5%A8F10%E6%8A%80%E6%9C%AF%E6%8C%87%E6%A0%87/"/>
    <id>https://project256.com/quant/%E8%82%A1%E7%A5%A8F10%E6%8A%80%E6%9C%AF%E6%8C%87%E6%A0%87/</id>
    <published>2025-01-05T08:27:41.000Z</published>
    <updated>2026-03-29T09:06:11.644Z</updated>
    
    <content type="html"><![CDATA[<p>各位投资过股票么？是如何选股呢？</p><p>是看支付宝的金选推荐？看KOL的视频瞎分析？听别人强烈推荐？还是直接收益率排序后闭着眼睛选一个？</p><p>说实话，这几个我都干过，但这些只带给我爆亏的账户、半夜胃疼睡不着、狂刷软件企图通过祈祷让曲线涨上去的蛋疼日常。</p><p>冷静下来看，如果卢本伟是“赌怪”，那这些行为就像韭留美一样的“赌狗”。</p><p>这些行为的胜率甚至连摇骰子、猜硬币的50%胜率都没有，在没有充分学习、思考、试错之前头铁入市还不如做慈善。</p><p>因此，本文将会学习和记录股票F10技术指标的含义。</p><p>我认为，技术指标不仅是选股的重要依据，而且是和其他人讨论的字典或“黑话”。</p><p>如果不知道什么含义，怎么拉通底层逻辑、对齐颗粒度、打出组合拳、赋能选股策略？</p><p>本文不涉及MACD、KDJ、CCI等即时走势指标，仅分析和财报相关的F10相关技术指标。</p><p><strong>风险及免责提示：以上内容不构成任何金融营销或投资邀约，亦不构成任何投资建议，在作出任何投资决定前，投资者应根据自身情况考虑投资产品相关的风险因素，并于需要时咨询专业投资顾问意见。</strong></p><h1 id="F10是什么-amp-在哪里"><a href="#F10是什么-amp-在哪里" class="headerlink" title="F10是什么&amp;在哪里"></a>F10是什么&amp;在哪里</h1><p>F10也称为公司和股票的基本面数据，因为炒股软件的F10按钮能够呼出数据看板界面，从而得名F10。</p><p>打开东方财富网站，在当前价格和涨跌幅的右侧，就能看到基础且重要的数据。</p><p>这些数据放到这里，也意味着极度重要，否则不会占用头部的位置。</p><p>但由于空间限制，更多的数据需要在下方的F10档案中查看。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/BtGfxm.jpg" alt="F10" title="">                </div>                <div class="image-caption">F10</div>            </figure><h1 id="指标构成及关联"><a href="#指标构成及关联" class="headerlink" title="指标构成及关联"></a>指标构成及关联</h1><p>多个指标之间肯定不是相互独立的，关于指标如何计算，可以通过【财务分析】→【杜邦分析】查看。</p><p>指标数据一般从年报中获得，此类指标较为静态，即几个月都不会变。</p><p>虽然基本面财务指标能够为选股提供数据基础，但对于多变的股票市场，尽可能协同动态指标结合分析。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/bWsgsi.jpg" alt="杜邦分析" title="">                </div>                <div class="image-caption">杜邦分析</div>            </figure><h1 id="指标说明"><a href="#指标说明" class="headerlink" title="指标说明"></a>指标说明</h1><h2 id="指标基础构成"><a href="#指标基础构成" class="headerlink" title="指标基础构成"></a>指标基础构成</h2><p>P 总市值&#x2F;股价 —— Price</p><p>E  净利润&#x2F;每股收益 —— Earning</p><p>B 净资产 —— Book Value —— 公司总资产减去总负债后的剩余部分</p><p>R 回报 —— Return</p><h2 id="ROE-——-净资产收益率-——-Return-on-Equity"><a href="#ROE-——-净资产收益率-——-Return-on-Equity" class="headerlink" title="ROE —— 净资产收益率 —— Return on Equity"></a>ROE —— 净资产收益率 —— Return on Equity</h2><blockquote><p>如果非要我用一个指标进行选股，我会选择ROE（净资产收益率），那些ROE能常年持续稳定在20%以上的公司都是好公司，投资者应当考虑买入。——沃伦•巴菲特</p></blockquote><p>一般用于评估公司利用股东权益产生利润的能力。</p><p>$ROE &#x3D; \frac{净利润(Net\ Income)}{股东权益(Shareholder’s\ Equity)}$</p><p>假设一家公司在某个财年的净利润为200万元，股东权益为1000万元，则其ROE为：</p><p>$ROE &#x3D; \frac{200万元}{1000万元} &#x3D; 0.2\ 或\ 20%$</p><p>也意味着公司每使用1元的股东收益，可以产生0.20元的净利润。</p><p>东方财富中的指标查看方法如下图所示：</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/yijJ3F.jpg" alt="ROE" title="">                </div>                <div class="image-caption">ROE</div>            </figure><h3 id="应用"><a href="#应用" class="headerlink" title="应用"></a>应用</h3><p>高ROE通常表明公司在利用股东权益方面表现良好，是投资者评估公司盈利能力的重要指标。</p><h2 id="PB-——-市净率-——-Price-to-Book-Ratio"><a href="#PB-——-市净率-——-Price-to-Book-Ratio" class="headerlink" title="PB —— 市净率 —— Price to Book Ratio"></a>PB —— 市净率 —— Price to Book Ratio</h2><p>用于评估公司股票价值的财务指标，反映了市场对公司每股净资产的估值。</p><p>$PB &#x3D; \frac{股价(Price)}{每股净资产(Book\ Value\ per\ Share)}$</p><p>而每股净资产计算方式如下：</p><p>$每股净资产 &#x3D; \frac{总资产\ -\ 总负债}{总股本}$</p><p>假设一家公司当前股价为50元，每股净资产为25元，则PB为：</p><p>$PB &#x3D; \frac{50元}{25元} &#x3D; 2$</p><p>意味着投资者愿意为每1元的净资产支付2元的价格。</p><h3 id="应用-1"><a href="#应用-1" class="headerlink" title="应用"></a>应用</h3><p>PB值通常用来评估股票是否被高估或低估。一般来说，PB值低于1可能表示股票被低估，而高于1可能表示股票被高估。</p><h2 id="ROE-x2F-PB比率-——-实际投资报酬率-——-PE的倒数"><a href="#ROE-x2F-PB比率-——-实际投资报酬率-——-PE的倒数" class="headerlink" title="ROE&#x2F;PB比率 —— 实际投资报酬率 —— PE的倒数"></a>ROE&#x2F;PB比率 —— 实际投资报酬率 —— PE的倒数</h2><p>ROE高且PB低的公司，通常被认为是投资价值比较高的公司，因为它们能够以较低的市场价格提供高回报。</p><p>高ROE&#x2F;PB比率可能表明公司在资本利用效率上表现优秀，同时市场对其估值相对低估。</p><p>$PE &#x3D; \frac{(PB<em>B)}{(ROE</em>B)} &#x3D; \frac{PB}{ROE}$</p><h3 id="应用-2"><a href="#应用-2" class="headerlink" title="应用"></a>应用</h3><p>假设一家公司ROE为30%，市净率PB为20，因此实际报酬率为1.5%。</p><p>很显然，1.5%的报酬率并不是一笔有吸引力的交易，甚至跑输了定期存款。</p><p>如果想说服某人把存款从银行或债基取出来炒股，至少于要超过五年定期，甚至高一倍才值得冒险。</p><p><strong>因此投资股票的安全标准，要求投资收益率不少于8%</strong></p><h2 id="PE-——-市盈率-——-Price-Earning-Ratio"><a href="#PE-——-市盈率-——-Price-Earning-Ratio" class="headerlink" title="PE —— 市盈率 —— Price Earning Ratio"></a>PE —— 市盈率 —— Price Earning Ratio</h2><h3 id="静态市盈率"><a href="#静态市盈率" class="headerlink" title="静态市盈率"></a>静态市盈率</h3><p>市盈率能直接表达投资者的投入与产出的关系。</p><p>$PE &#x3D; \frac{当前股价}{每股收益率} &#x3D; \frac{当前股票总市值}{上年度净利润}$</p><h3 id="动态市盈率"><a href="#动态市盈率" class="headerlink" title="动态市盈率"></a>动态市盈率</h3><p>上面的公式使用过去的数据计算而来，因此也称为静态市盈率，那么动态市盈率则预测该股票的未来。</p><p>动态市盈率的分母为预测未来一年的预测净利润，预测方法也能够用到下文的PEG计算上。</p><p>例如：该股票已经发布第一季度财报，公式的分母要乘4、半年财报乘2、三季报乘4&#x2F;3。</p><p>假设某股票当天收盘价为1000元，一季度财报的每股收益为10元，因此计算时分母要乘4：</p><p>$PE(动态) &#x3D; \frac{1000}{10*4} &#x3D; 1$</p><p>假如动态市盈率大于静态市盈率，可能代表该股票的每股收益、净利润变低了，需要警惕。</p><h3 id="滚动市盈率（TTM-——-Trailing-Twelve-Month）"><a href="#滚动市盈率（TTM-——-Trailing-Twelve-Month）" class="headerlink" title="滚动市盈率（TTM —— Trailing Twelve Month）"></a>滚动市盈率（TTM —— Trailing Twelve Month）</h3><p>类似于静态市盈率，滚动市盈率的分母是根据最近四个季度的净利润之和。</p><p>如果使用最近四个季度的净利润，也称为PE3；如果使用每股收益率，则称为PE4。</p><h3 id="应用-3"><a href="#应用-3" class="headerlink" title="应用"></a>应用</h3><p>假设一家公司的PE是10，持有该股票10年才能收回投入成本。意味着按现价买入该股票，假设该公司保持不变的盈利能力，需要10年时间所赚的净利润按照持有股份换算，刚好等于初始投入买股票的钱。</p><p>上面说到，实际投资报酬率是PE的倒数，<strong>则要求PE应不大于12.5</strong> </p><h2 id="PEG"><a href="#PEG" class="headerlink" title="PEG"></a>PEG</h2><blockquote><p>任何一家公司的股票如果定价合理的话，市盈率就会与收益增长率相等。 —— 彼得·林奇</p></blockquote><p>林奇认为，<strong>PEG比率低于1的股票可能被低估，而高于1的股票可能被高估</strong>。</p><p>PEG是一个综合指标，既考察价值，又兼顾成长性。因此<strong>PEG比较适用于成长型公司的股票价值评估</strong>。</p><p>$PEG &#x3D; \frac{PE}{年预期盈利增长率}$</p><p>PE根据“市盈率”指标计算，G值根据报告期匹配相应的值，和动态市盈率的计算方法类似：一季报根据当年第一季度净利润×4较上一年度净利润的增长率确定，二季报根据当年中报净利润×2较上一年度净利润的增长率确定，以此类推。</p><h3 id="应用-4"><a href="#应用-4" class="headerlink" title="应用"></a>应用</h3><p>假如某股票市盈率为30，而分析师预计其下一年的盈利增长率为50%，因此该股票的PEG比率仅为0.6。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>当我使用F10技术指标时，一般用于第一轮选股，选出低估且具有成长潜力的股票。第二轮再用MACD、KDJ、CII等实时走势技术指标。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;各位投资过股票么？是如何选股呢？&lt;/p&gt;
&lt;p&gt;是看支付宝的金选推荐？看KOL的视频瞎分析？听别人强烈推荐？还是直接收益率排序后闭着眼睛选一个？&lt;/p&gt;
&lt;p&gt;说实话，这几个我都干过，但这些只带给我爆亏的账户、半夜胃疼睡不着、狂刷软件企图通过祈祷让曲线涨上去的蛋疼日常。&lt;/</summary>
      
    
    
    
    <category term="quant" scheme="https://project256.com/categories/quant/"/>
    
    
    <category term="量化交易" scheme="https://project256.com/tags/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93/"/>
    
  </entry>
  
  <entry>
    <title>2025</title>
    <link href="https://project256.com/chit-chat/2025/"/>
    <id>https://project256.com/chit-chat/2025/</id>
    <published>2024-12-31T16:00:00.000Z</published>
    <updated>2026-03-29T09:06:11.641Z</updated>
    
    <content type="html"><![CDATA[<h1 id="2025新年快乐！"><a href="#2025新年快乐！" class="headerlink" title="2025新年快乐！"></a>2025新年快乐！</h1><p>很长时间没有更新博客了，一方面沉迷LOL、CS2无法自拔，另一方面需要和更高ROI事情对线。对线压力大了，继续沉迷LOL强效减压（虽然也有很多压力怪……）。</p><p>2024年仅完成了计划的17&#x2F;64，但这一年真的就没有成长么？我的心理咨询师和我认为列出的计划是很不错、也很全面，但没有看到主线。</p><p>我认为是这样的，尽管我详细规划了“健康”、“人际”、“主业”、“附加价值输出”、“时间管理”、“自我提升“、”财务“和“艺术”这五大方向，但看不出我真正想要的是什么。</p><p>“不知道”、“不清楚”这三个字是最好的逃避借口，或早或晚肯定得面对自己真正想要的目标，对于我而言就是“自由”——</p><ul><li>拥有打卡时间的自由</li><li>拥有选择的自由</li><li>拥有如何面对选择后结果的自由</li><li>拥有在任意地区晒太阳睡午觉的自由……</li></ul><h1 id="自由的责任和义务"><a href="#自由的责任和义务" class="headerlink" title="自由的责任和义务"></a>自由的责任和义务</h1><p>2024年读了一堆财务自由之路相关的书，我认为在讨论如何享受自由前，让自己的财务达到安全线并打造持续摇钱的油田，是需要努力履行的责任和义务。</p><p>我现在就能享受上述自由么？可以，完全现在可以提辞职通知，大手笔挥霍，没钱了借网贷进入《戒社》当名人堂出生老哥。</p><p>然而，财富的衡量单位是时间，不是元或其他货币单位。是指停止工作后，能够维持生活的天数。</p><p>但当我找到并打造持续摇钱的油田之后，就算我在世界和任何地方，都能够产出和之前努力相符的财富，从而获得自由。</p><p>上面两种实现自由的方法，一种是向外求：求公司发薪、求网贷公司下款、求朋友们借钱、求银行降息……另一种是通过向内求，求得一份适合自己的答案，试错并持续执行。</p><h1 id="第二增长曲线"><a href="#第二增长曲线" class="headerlink" title="第二增长曲线"></a>第二增长曲线</h1><p>2024年我请教了很多人，问的最多的就是除了本职工作以外是否有第二职业。如果没有，再看是否能够合作共赢。</p><p>作为在公司内部各个组流浪过一遍的老登，深知消耗全部时间和精力为公司效忠卖命有多可笑，可能某次站错队或是因为上面的明争暗斗等不可抗力，之前的努力全部木大。</p><p>因此，我剩下的打工生涯里，培养自己、把公司当做人际交往场、反向“剥削”公司、让公司补剩余价值差价，将会是重点中的重点。</p><p>幸运的是，我从年初尝试各个方向到年终，筛选掉了一些不适合自己的方向，也对适合自己的方向有了较多的思考和实践。</p><p>简单来说，2025年我的一个方向是量化交易，另一个方向是通过可复制的工具作为杠杆，为更多人和自己提供便利和价值。</p><p>这几年，我为公司做了一堆工具，也诞生了无数人取得高绩效。但当我离职之后，除了经验以外，一切都消失了，所以我要为我自己制造私域的工具，成为能为自己所用的杠杆。</p><p>当然，和其他所有软件行业创业者一样，起步时还是需要以外包作为原始资本积累的途径。</p><p>2025年，祝愿各位都能找到适合自己的发展方向，通过思考和实践找到第二增长曲线，实现各方面的“自由”。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;2025新年快乐！&quot;&gt;&lt;a href=&quot;#2025新年快乐！&quot; class=&quot;headerlink&quot; title=&quot;2025新年快乐！&quot;&gt;&lt;/a&gt;2025新年快乐！&lt;/h1&gt;&lt;p&gt;很长时间没有更新博客了，一方面沉迷LOL、CS2无法自拔，另一方面需要和更高ROI事</summary>
      
    
    
    
    <category term="chit-chat" scheme="https://project256.com/categories/chit-chat/"/>
    
    
    <category term="杂谈" scheme="https://project256.com/tags/%E6%9D%82%E8%B0%88/"/>
    
  </entry>
  
  <entry>
    <title>阿佩尔均线操盘术#1</title>
    <link href="https://project256.com/quant/%E9%98%BF%E4%BD%A9%E5%B0%94%E5%9D%87%E7%BA%BF%E6%93%8D%E7%9B%98%E6%9C%AF-1/"/>
    <id>https://project256.com/quant/%E9%98%BF%E4%BD%A9%E5%B0%94%E5%9D%87%E7%BA%BF%E6%93%8D%E7%9B%98%E6%9C%AF-1/</id>
    <published>2024-11-02T08:10:47.000Z</published>
    <updated>2026-03-29T09:06:11.645Z</updated>
    
    <content type="html"><![CDATA[<p>《阿佩尔均线操盘术》是MACD技术指标发明人杰拉尔德•阿佩尔著作，我对所有的二手加工过的知识持怀疑态度，因此我开始了这本书的阅读。反向思考来说，如果在社交媒体中搜索这本书的名字，相信也能搜索到真正能够帮助到我们的信息。</p><p>前言第一句话就让我下定了啃完这本书的决心：“如果你因为轻信自己的证券公司，轻信自己所熟悉的共同基金经理或者盲目信任那些炙手可热的交易大师而受过伤害，那么本书正是为你而写。”</p><p>同时作者也在前言保证第一章所涉及的两个技术指标，每周只需要占用5~10分钟的分析时间，足以让投资者判断市场气候是有利于还是不利于交易。即使读完第一章就不再读下去，也已经掌握了很有用的、能够提升投资业绩的分析工具。</p><p>成功的投资虽然很简单：买卖的是什么、什么时候买卖，但却很少有关于技术指标的讲解，让我们通过这本书逐步迈入“百万富翁的快车道”。</p><hr><h1 id="1-超级实用的投资策略"><a href="#1-超级实用的投资策略" class="headerlink" title="#1 超级实用的投资策略"></a>#1 超级实用的投资策略</h1><h2 id="选择投资工具的第一原则"><a href="#选择投资工具的第一原则" class="headerlink" title="选择投资工具的第一原则"></a>选择投资工具的第一原则</h2><ul><li>重要的不是你能赚多少，而是你准备亏多少</li></ul><p>为弥补股票市场的亏损，你必须获取比所受损失更大的收益百分比。至于亏损和获利的先后顺序并不重要。</p><p>为确保长期投资的成功，保护好本金要远比获取偶然暴利重要得多。</p><p>整体而言，投资者如果将资金主要投资于高风险类基金，最终盈利确实不大。长期来看，低波动率和高波动率组别的基金获得的投资收益基本一样，但是前者的风险却更低。</p><p><strong>结论：投资高波动性股票的收益要小于投资低波动率股票的收益。</strong></p><h2 id="相对强度投资法——时时持有最优组合"><a href="#相对强度投资法——时时持有最优组合" class="headerlink" title="相对强度投资法——时时持有最优组合"></a>相对强度投资法——时时持有最优组合</h2><p>基本原则如下：</p><ul><li>找出领涨股</li><li>买进领涨股</li><li>只要它们保持领先地位就一直持有领涨股</li><li>当领涨股开始减缓上涨速度时，卖出它们并买进新的领涨股</li></ul><p>从共同基金池中，挑选出在过去三个月中业绩表现在前10%（前十名、波动率约等于或不超过标普500指数的共同基金）中最少选择两只、最好是四到五只共同基金买进。</p><p>即使是只有两只基金的投资组合，也比仅有一只基金的投资组合具备更高的安全性。寻找那些只需持有90天以上就免收申购和赎回费用的基金效果会更好。</p><p>因为新的季度报告数据的出现，投资者应当每三个月检查一遍自己的投资组合。如果其中任何一只的业绩跌出前10%，那就卖掉它，并用其他一直保持或刚进入前10%的基金来替代。继续持有其他当前的业绩表现扔保持在前10%的基金。</p><p>遵循这一过程，就能够时不时地对你的共同基金投资组合进行再平衡和再分配，在每个极度都持有始终引领同行业平均水平的共同基金组合。最终，你的投资组合将由表现最优异的共同基金组成，如同在买一圈都处于领先地位的赛马。</p><p><strong>结论：长期来看，采取更激进的投资策略即使能带来额外的收益，其额外收益也非常有限。</strong></p><p><strong>结论2：增加共同基金投资组合的波动率并不会显著地增加收益，而风险水平却实实在在地增加了。所以一般来说，你应该坚持重点投资低于平均波动率的共同基金。</strong></p><h2 id="管理共同基金投资组合的三步走"><a href="#管理共同基金投资组合的三步走" class="headerlink" title="管理共同基金投资组合的三步走"></a>管理共同基金投资组合的三步走</h2><ol><li>需要一个能够访问至少500只（越多越好）共同基金在最近一个季度的价格和波动率数据的数据源。</li><li>开设一个可以进行多元化投资组合的共同基金投资账户，用于投资那些在上个季度业绩排名在前10%，且波动率低于标普500指数的共同基金，这些共同基金的波动率最高不能超过整个投资组合的平均波动率。</li><li>在每一个新季度的开始，剔除跌出前10%的基金，用新进的前10%共同基金来替换。</li></ol><h1 id="2-两个简便易用的股市行情指标"><a href="#2-两个简便易用的股市行情指标" class="headerlink" title="#2 两个简便易用的股市行情指标"></a>#2 两个简便易用的股市行情指标</h1><h2 id="货币指标"><a href="#货币指标" class="headerlink" title="货币指标"></a>货币指标</h2><p>利率持续下跌之时股市表现最好，利率稳步上涨之时股市表现最差。</p><h2 id="Nasdaq-x2F-NYSE相对强度指标"><a href="#Nasdaq-x2F-NYSE相对强度指标" class="headerlink" title="Nasdaq&#x2F;NYSE相对强度指标"></a>Nasdaq&#x2F;NYSE相对强度指标</h2><p>当纳斯达克综合指数的相对强度领先于纽约证券交易所指数时，市场上涨的概率大大增加了。</p><p>用Nasdaq&#x2F;NYSE比率法进行投资的年华总收益，远远高于买进并持有策略，而且用于投资的时间还仅仅只有后者的一半多一点——在减少投入在股市中的人来说，这一指标也可以在作单独的买卖决策时使用。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/yCd8ZV.jpg" alt="project256.oss-cn-beijing.aliyuncs.com/uPic/yCd8ZV.jpg" title="">                </div>                <div class="image-caption">project256.oss-cn-beijing.aliyuncs.com/uPic/yCd8ZV.jpg</div>            </figure><h1 id="3-移动平均线和变动率指标（ROC）：跟踪趋势与动量"><a href="#3-移动平均线和变动率指标（ROC）：跟踪趋势与动量" class="headerlink" title="#3 移动平均线和变动率指标（ROC）：跟踪趋势与动量"></a>#3 移动平均线和变动率指标（ROC）：跟踪趋势与动量</h1><p>移动平均线系统被用来平滑短期价格波动的“”噪音“，这样可以更便于投资者识别和定义重要的市场趋势。</p><p><strong>一定要时时关注移动平均线的斜率以及波动的高度：脉冲持续时间越长，斜率越垂直，出现持续趋势的概率就更大；而如果斜率和脉冲开始变得温和，那么出现市场逆转的危险就迫在眉睫了。（上穿和下穿信号分别标志着高于平均水平的上涨或低于平均水平的下跌）</strong></p><p>当我们研究移动平均线交易通道时，需要结合波浪理论（Elliott Wave Theory——市场走势不断重复一种模式，每一周期由5个上升浪和3个下跌浪组成。），它可是非常有效的市场择时工具。</p><h2 id="变动率（ROC）指标的解释和运用"><a href="#变动率（ROC）指标的解释和运用" class="headerlink" title="变动率（ROC）指标的解释和运用"></a>变动率（ROC）指标的解释和运用</h2><p>ROC&#x3D;（当日收盘价-n日前收盘价）&#x2F;n日前收盘价</p><p>当ROC上涨到相对较高的位置，称为超买状态，通常被视为卖出信号。当ROC下跌到相对较低的位置，称为超卖状态，通常被视为买入信号。</p><p>当ROC突破零线自下往上，意味买房较为强势，是买入信号；自上往下则为卖出信号。</p><p>价格创新高而动量指标下跌的形态被称作顶背离。顶背离的产生预示着熊市的到来，因为推动市场上涨的动量无法与市场价格的上涨保持一致，从而导致上涨的力量减弱，随之而来的只能是下跌。与之相反的是，在动量读数一路上涨的同时，价格水平却跌至新低。这种情形反映出的是下跌的动能正在逐步衰竭，被称作底背离，它是牛市来临的信号。但单独的背离现象并不能作为交易信号，结合ROCM移动平均线分析。</p><p>10日变动率指标对于短期交易非常有用，而21日到25日的变动率指标常被用作中期交易分析指标。结合利用短期和长期变动率指标，将会对交易非常有利。具体方法是：<strong>用短期变动率指标的读数率先做出趋势方向的预测，接着用长期变动率指标对方向进行确认。</strong></p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/bZwIQL.jpg" alt="" title="">                </div>                <div class="image-caption"></div>            </figure><p>大多数情况下，市场发生明显上涨的起点，并不是在变动率指标和其他动量指标读数最低，或者处于超卖区时，它们往往开始于动量指标已经从它们的最低度数上涨了一段时间之后。例如，上图中2002年10月份的上涨开始时，21日变动率指标已经构造了一个上涨的双底形态——第二个低点高于前一个低点。</p><p>但如区域C，价格在11月份抵达最高值时，变动率指标却一直处于下跌趋势之中，这就是典型的顶背离，预示着未来市场会走弱。</p><h2 id="三重动量的纳斯达克指数交易模型"><a href="#三重动量的纳斯达克指数交易模型" class="headerlink" title="三重动量的纳斯达克指数交易模型"></a>三重动量的纳斯达克指数交易模型</h2><p>”打得赢就打，打不赢就跑“的择时模型。使投资者有效规避获利概率不高的交易，进而显著地降低交易风险。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/8b0Oo1.jpg" alt="" title="">                </div>                <div class="image-caption"></div>            </figure><p>维护三个日线级别的变动率指标：一个纳斯达克综合指数收盘价的5日变动率，一个15日变动率指标，以及一个25日变动率指标。</p><p>例如，今天收盘价为2000，而10天前是1900，那么10日变动率的值就是+5.26%（2000-1900&#x3D;100；100&#x2F;1900&#x3D;0.0526；0.0526*100&#x3D;+5.26% ）</p><p>每天收盘时，分别为5日、15日和25日变动率指标加入以百分比为基础计算的值，来获取一个符合变动率值——今天的三重动量值。例如：5日+3.0%、15日+4.5%、25日6%，那么三重动量值应该为+13.5%，表名指数向上穿越了所有的时间框架，预示着市场即将发生上涨。</p><p>买入和卖出的原则只有一个：<strong>当三重动量指标的具体值——5日、15日和25日变动率指标读数之和——自下而上突破4%时买入、自上而下击穿4%时卖出。</strong></p><p><strong>没有其他规则，这是一个简洁到优雅完美的模型。</strong></p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/qm3hl8.jpg" alt="" title="">                </div>                <div class="image-caption"></div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/pRhlKm.jpg" alt="" title="">                </div>                <div class="image-caption"></div>            </figure><h1 id="4-超越图表：强大的技术图形工具"><a href="#4-超越图表：强大的技术图形工具" class="headerlink" title="#4 超越图表：强大的技术图形工具"></a>#4 超越图表：强大的技术图形工具</h1><h2 id="协同效应"><a href="#协同效应" class="headerlink" title="协同效应"></a>协同效应</h2><p>指将两种或两种以上的分析工具相加或调配在一起所产生的作用，大于各种分析工具单独应用时作用的总和，也就是通常我们所讲的1+1&gt;2</p><p>没有完美的股票市场指标，甚至没有任何近乎完美的指标。退一步说，即使存在最完美的技术指标，它的神秘内容也迟早会人尽皆知，而且当投资者开始集体追随它时，它的有效性就会日渐降低。即便最好的预测也是不完美的，顶多只能是以一定的概率命中而已。相对切实可行的目标是尽量多做正确的决策，发展快速识别错误、采取正确行动，合理调整心态的能力，即使这意味着接受股票市场的亏损也在所不惜（一般情况下，在股票市场上，应对亏损的最佳方式就是快速止损。）</p><p>成功的投资者并不是一直持有证券头寸。他们会尽可能认真地评估某一机会的成功概率，并且只有时机有利时才进行投资。提升获胜概率的方法之一就是<strong>应用协同效应：如果有多个指标相互确认对股票市场变化的预期，那么交易成功的概率就会大幅度提升。</strong>（例如单个指标成功率60%、两个指标结合84%、三个指标93.6%）</p><h2 id="基于角度变化的价格预测"><a href="#基于角度变化的价格预测" class="headerlink" title="基于角度变化的价格预测"></a>基于角度变化的价格预测</h2><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/Kaj89d.jpg" alt="" title="">                </div>                <div class="image-caption"></div>            </figure><p>如果在陡峭的拉升或下跌期成交量比较小的话，股价运行角度很容易发生改变，变得平缓；与之相反，稳步上涨或者下跌的市场也可能会突然发生斜率的变化，以几乎垂直的角度运行。</p><p>当识别到这种形态时，此时应该正处于第二段初期，此时可以准确地预测市场波动的长度和剩余时间。具体操作分两步走：第一步，沿着第一个上涨或下跌的角度计算总的时间，本段被称作段A；第二步，一旦你识别到相对于段A股价的运行角度发生了变化，就应该立即着手测量第二段的长度，本段被称作段B。具体的测量方法是从段B的起点开始，以段A的长度沿着段B的角度画出完整的走势预测。这样不仅能预测出段B的延伸长度，而且还能预测出段B的运行时间。</p><hr><p>第一篇暂时写到这里，因为还没想明白这玩意在A股怎么用，又因为研究这些还不如直接投美股指数，所以在想明白之前，先不继续往下读了。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;《阿佩尔均线操盘术》是MACD技术指标发明人杰拉尔德•阿佩尔著作，我对所有的二手加工过的知识持怀疑态度，因此我开始了这本书的阅读。反向思考来说，如果在社交媒体中搜索这本书的名字，相信也能搜索到真正能够帮助到我们的信息。&lt;/p&gt;
&lt;p&gt;前言第一句话就让我下定了啃完这本书的决心</summary>
      
    
    
    
    <category term="quant" scheme="https://project256.com/categories/quant/"/>
    
    
    <category term="量化交易" scheme="https://project256.com/tags/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93/"/>
    
    <category term="Python" scheme="https://project256.com/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>批量生成安卓apk包</title>
    <link href="https://project256.com/tools/%E6%89%B9%E9%87%8F%E7%94%9F%E6%88%90%E5%AE%89%E5%8D%93apk%E5%8C%85/"/>
    <id>https://project256.com/tools/%E6%89%B9%E9%87%8F%E7%94%9F%E6%88%90%E5%AE%89%E5%8D%93apk%E5%8C%85/</id>
    <published>2024-08-01T15:23:25.000Z</published>
    <updated>2026-03-29T09:06:11.643Z</updated>
    
    <content type="html"><![CDATA[<p>4月的某天，接到了一单批量生成安卓apk包的私活。要求是点开后通过展示不同的背景图片，展示主角的极强业务能力。</p><p>由于之前也没写过原生安卓，顶多用React Native写跨端应用。手工创建一堆工程，再手动修改，就完全是体力工作，这个肯定是不行的。</p><h1 id="LLM先行"><a href="#LLM先行" class="headerlink" title="LLM先行"></a>LLM先行</h1><p>最近一直用大模型解决重复性劳动，直接问下大模型这玩意怎么写。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">假如你是安卓和Bash脚本开发专家，我需要通过Bash命令生成不定数量的安卓安装包，每个安卓安装包都有不同的图标和图片，请写出Bash脚本。</span><br></pre></td></tr></table></figure><p>虽然GPT4写了一堆Bash脚本内容，但在细枝末节还是报错，就把报错信息提供给LLM，让其解决。</p><p>但也需要注意，并不是对话越长越好，有时候报错原文和代码一长，就会出现严重的幻觉。因此，写一点、运行一点、验证一点就清理上下文重新提问。</p><h1 id="一起学习安卓和Bash"><a href="#一起学习安卓和Bash" class="headerlink" title="一起学习安卓和Bash"></a>一起学习安卓和Bash</h1><p>最后LLM和人工的不懈调试下（也就2个小时），输出了可运行Bash脚本：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line">PROJECTS_COUNT=1</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> $(seq 1 <span class="variable">$PROJECTS_COUNT</span>); <span class="keyword">do</span></span><br><span class="line">    PROJECT_NAME=<span class="string">"Dual_Record_<span class="variable">$(openssl rand -hex 3)</span>"</span></span><br><span class="line">    PACKAGE_NAME=<span class="string">"com.example.<span class="variable">$PROJECT_NAME</span>"</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 创建基础的项目结构</span></span><br><span class="line">    mkdir -p <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/java/<span class="variable">$PACKAGE_NAME</span>"</span></span><br><span class="line">    mkdir -p <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/res/layout"</span></span><br><span class="line">    mkdir -p <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/res/mipmap"</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"rootProject.name='<span class="variable">$PROJECT_NAME</span>'"</span> &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/settings.gradle"</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"include ':app'"</span> &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/settings.gradle"</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 复制图标到项目中</span></span><br><span class="line">    cp /Users/project256/Downloads/5a1acc930a4f210f5ee9e73e45bec3c7_1.png <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/res/mipmap/ic_launcher.png"</span></span><br><span class="line">    cp /Users/project256/Downloads/5a1acc930a4f210f5ee9e73e45bec3c7_1.png <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/res/mipmap/ic_launcher_round.png"</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 创建strings.xml文件并添加app_name字符串资源</span></span><br><span class="line">    mkdir -p <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/res/values/"</span></span><br><span class="line">    cat &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/res/values/strings.xml"</span> &lt;&lt;EOF</span><br><span class="line">&lt;?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"utf-8"</span>?&gt;</span><br><span class="line">&lt;resources&gt;</span><br><span class="line">    &lt;string name=<span class="string">"app_name"</span>&gt;APP名字&lt;/string&gt;</span><br><span class="line">&lt;/resources&gt;</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Inside the loop, setting PACKAGE_NAME and PROJECT_NAME</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># This goes in the project-level build.gradle creation step</span></span><br><span class="line">cat &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/build.gradle"</span> &lt;&lt;EOF</span><br><span class="line">buildscript &#123;</span><br><span class="line">    repositories &#123;</span><br><span class="line">        google()</span><br><span class="line">        mavenCentral()</span><br><span class="line">    &#125;</span><br><span class="line">    dependencies &#123;</span><br><span class="line">        // Make sure to use a compatible version</span><br><span class="line">        classpath <span class="string">'com.android.tools.build:gradle:7.4.2'</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">allprojects &#123;</span><br><span class="line">    repositories &#123;</span><br><span class="line">        google()</span><br><span class="line">        mavenCentral()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">task clean(<span class="built_in">type</span>: Delete) &#123;</span><br><span class="line">    delete rootProject.buildDir</span><br><span class="line">&#125;</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line"><span class="comment"># Adjusted AndroidManifest.xml creation with android:exported="true"</span></span><br><span class="line">cat &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/AndroidManifest.xml"</span> &lt;&lt;EOF</span><br><span class="line">&lt;?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"utf-8"</span>?&gt;</span><br><span class="line">&lt;manifest xmlns:android=<span class="string">"http://schemas.android.com/apk/res/android"</span></span><br><span class="line">    package=<span class="string">"<span class="variable">$PACKAGE_NAME</span>"</span>&gt;</span><br><span class="line"></span><br><span class="line">    &lt;application</span><br><span class="line">        android:allowBackup=<span class="string">"true"</span></span><br><span class="line">        android:icon=<span class="string">"@mipmap/ic_launcher"</span></span><br><span class="line">        android:label=<span class="string">"@string/app_name"</span></span><br><span class="line">        android:roundIcon=<span class="string">"@mipmap/ic_launcher_round"</span></span><br><span class="line">        android:supportsRtl=<span class="string">"true"</span></span><br><span class="line">        android:theme=<span class="string">"@style/Theme.AppCompat.Light.NoActionBar"</span>&gt;</span><br><span class="line">        &lt;activity android:name=<span class="string">".MainActivity"</span></span><br><span class="line">            android:exported=<span class="string">"true"</span>&gt; &lt;!-- Ensure this line is added --&gt;</span><br><span class="line">            &lt;intent-filter&gt;</span><br><span class="line">                &lt;action android:name=<span class="string">"android.intent.action.MAIN"</span> /&gt;</span><br><span class="line">                &lt;category android:name=<span class="string">"android.intent.category.LAUNCHER"</span> /&gt;</span><br><span class="line">            &lt;/intent-filter&gt;</span><br><span class="line">        &lt;/activity&gt;</span><br><span class="line">    &lt;/application&gt;</span><br><span class="line"></span><br><span class="line">&lt;/manifest&gt;</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 创建一个简单的MainActivity</span></span><br><span class="line">    cat &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/java/<span class="variable">$PACKAGE_NAME</span>/MainActivity.java"</span> &lt;&lt;EOF</span><br><span class="line">package <span class="variable">$PACKAGE_NAME</span>;</span><br><span class="line"></span><br><span class="line">import android.os.Bundle;</span><br><span class="line">import androidx.appcompat.app.AppCompatActivity;</span><br><span class="line"></span><br><span class="line">public class MainActivity extends AppCompatActivity &#123;</span><br><span class="line">    @Override</span><br><span class="line">    protected void onCreate(Bundle savedInstanceState) &#123;</span><br><span class="line">        super.onCreate(savedInstanceState);</span><br><span class="line">        setContentView(R.layout.activity_main);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 创建一个简单的activity_main.xml布局文件</span></span><br><span class="line">    cat &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/res/layout/activity_main.xml"</span> &lt;&lt;EOF</span><br><span class="line">&lt;?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"utf-8"</span>?&gt;</span><br><span class="line">&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=<span class="string">"http://schemas.android.com/apk/res/android"</span></span><br><span class="line">    xmlns:app=<span class="string">"http://schemas.android.com/apk/res-auto"</span></span><br><span class="line">    xmlns:tools=<span class="string">"http://schemas.android.com/tools"</span></span><br><span class="line">    android:layout_width=<span class="string">"match_parent"</span></span><br><span class="line">    android:layout_height=<span class="string">"match_parent"</span></span><br><span class="line">    tools:context=<span class="string">".MainActivity"</span>&gt;</span><br><span class="line"></span><br><span class="line">    &lt;TextView</span><br><span class="line">        android:layout_width=<span class="string">"wrap_content"</span></span><br><span class="line">        android:layout_height=<span class="string">"wrap_content"</span></span><br><span class="line">        android:text=<span class="string">"欢迎使用XX系统_<span class="variable">$PROJECT_NAME</span>!"</span></span><br><span class="line">        app:layout_constraintBottom_toBottomOf=<span class="string">"parent"</span></span><br><span class="line">        app:layout_constraintLeft_toLeftOf=<span class="string">"parent"</span></span><br><span class="line">        app:layout_constraintRight_toRightOf=<span class="string">"parent"</span></span><br><span class="line">        app:layout_constraintTop_toTopOf=<span class="string">"parent"</span> /&gt;</span><br><span class="line">&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建app模块的build.gradle文件</span></span><br><span class="line">cat &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/build.gradle"</span> &lt;&lt;EOF</span><br><span class="line">apply plugin: <span class="string">'com.android.application'</span></span><br><span class="line"></span><br><span class="line">android &#123;</span><br><span class="line">    compileSdkVersion 31</span><br><span class="line">    defaultConfig &#123;</span><br><span class="line">        applicationId <span class="string">"<span class="variable">$PACKAGE_NAME</span>"</span></span><br><span class="line">        minSdkVersion 21</span><br><span class="line">        targetSdkVersion 31</span><br><span class="line">        versionCode 1</span><br><span class="line">        versionName <span class="string">"1.0"</span></span><br><span class="line"></span><br><span class="line">        testInstrumentationRunner <span class="string">"androidx.test.runner.AndroidJUnitRunner"</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    buildTypes &#123;</span><br><span class="line">        release &#123;</span><br><span class="line">            minifyEnabled <span class="literal">false</span></span><br><span class="line">            proguardFiles getDefaultProguardFile(<span class="string">'proguard-android-optimize.txt'</span>), <span class="string">'proguard-rules.pro'</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">dependencies &#123;</span><br><span class="line">    implementation <span class="string">'androidx.appcompat:appcompat:1.3.0'</span></span><br><span class="line">    implementation <span class="string">'androidx.constraintlayout:constraintlayout:2.0.4'</span></span><br><span class="line">    testImplementation <span class="string">'junit:junit:4.13.2'</span></span><br><span class="line">    androidTestImplementation <span class="string">'androidx.test.ext:junit:1.1.3'</span></span><br><span class="line">    androidTestImplementation <span class="string">'androidx.test.espresso:espresso-core:3.4.0'</span></span><br><span class="line">&#125;</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 初始化Gradle Wrapper</span></span><br><span class="line">    <span class="built_in">cd</span> <span class="string">"<span class="variable">$PROJECT_NAME</span>"</span></span><br><span class="line">    gradle wrapper --gradle-version=7.5</span><br><span class="line">    <span class="built_in">cd</span> ..</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 构建项目</span></span><br><span class="line">    <span class="built_in">cd</span> <span class="string">"<span class="variable">$PROJECT_NAME</span>"</span></span><br><span class="line">    ./gradlew assembleDebug</span><br><span class="line">    <span class="built_in">cd</span> ..</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 复制APK到脚本执行目录</span></span><br><span class="line">    cp ./<span class="string">"<span class="variable">$PROJECT_NAME</span>"</span>/app/build/outputs/apk/debug/app-debug.apk ./<span class="string">"<span class="variable">$PROJECT_NAME</span>.apk"</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">"Project <span class="variable">$PROJECT_NAME</span> has been created and packaged."</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"Finished creating and packaging <span class="variable">$PROJECTS_COUNT</span> projects."</span></span><br></pre></td></tr></table></figure><p>其实很久不用Bash写脚本了，借着这个项目来学习学习~</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br></pre></td></tr></table></figure><p>第一行，指定了bash执行器位置，一般.&#x2F;xxx.sh的时候会读取第一行。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PROJECTS_COUNT=1</span><br></pre></td></tr></table></figure><p>第二行，设定了一个变量，用来指定生成项目数量。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> $(seq 1 <span class="variable">$PROJECTS_COUNT</span>); <span class="keyword">do</span></span><br><span class="line">…………</span><br><span class="line"><span class="keyword">done</span></span><br></pre></td></tr></table></figure><p>接下来写了一个for语句，seq 1 $PROJECTS_COUNT则生成从1到PROJECTS_COUNT的序列，前方的$符号则将括号内的表达式打包成一个变量，最后在do和done之间写具体逻辑。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">PROJECT_NAME=<span class="string">"Dual_Record_<span class="variable">$(openssl rand -hex 3)</span>"</span></span><br><span class="line">PACKAGE_NAME=<span class="string">"com.example.<span class="variable">$PROJECT_NAME</span>"</span></span><br></pre></td></tr></table></figure><p>由于每个包名不能重复，因此使用openssl rand -hex 3生成一串随机字符，并将生成结果直接拼接至字符串末尾。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建基础的项目结构</span></span><br><span class="line">mkdir -p <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/java/<span class="variable">$PACKAGE_NAME</span>"</span></span><br><span class="line">mkdir -p <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/res/layout"</span></span><br><span class="line">mkdir -p <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/res/mipmap"</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"rootProject.name='<span class="variable">$PROJECT_NAME</span>'"</span> &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/settings.gradle"</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"include ':app'"</span> &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/settings.gradle"</span></span><br></pre></td></tr></table></figure><p>安卓或JAVA项目一般需要遵循一些固定的项目文件夹规范，因此需要提前mkdir -p直接创建层级文件夹。创建完毕后，再往settings.gradle项目配置中写入项目信息。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 复制图标到项目中</span></span><br><span class="line">cp /Users/project256/Downloads/5a1acc930a4f210f5ee9e73e45bec3c7_1.png <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/res/mipmap/ic_launcher.png"</span></span><br><span class="line">cp /Users/project256/Downloads/5a1acc930a4f210f5ee9e73e45bec3c7_1.png <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/res/mipmap/ic_launcher_round.png"</span></span><br></pre></td></tr></table></figure><p>使用cp命令复制自定图标到目标文件夹中并使用指定文件名。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建strings.xml文件并添加app_name字符串资源</span></span><br><span class="line">    mkdir -p <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/res/values/"</span></span><br><span class="line">    cat &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/res/values/strings.xml"</span> &lt;&lt;EOF</span><br><span class="line">&lt;?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"utf-8"</span>?&gt;</span><br><span class="line">&lt;resources&gt;</span><br><span class="line">    &lt;string name=<span class="string">"app_name"</span>&gt;APP名字&lt;/string&gt;</span><br><span class="line">&lt;/resources&gt;</span><br><span class="line">EOF</span><br></pre></td></tr></table></figure><p>使用cat &gt; “文件” &lt;&lt;EOF，将多行字符串写入到指定文件中。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># This goes in the project-level build.gradle creation step</span></span><br><span class="line">cat &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/build.gradle"</span> &lt;&lt;EOF</span><br><span class="line">buildscript &#123;</span><br><span class="line">    repositories &#123;</span><br><span class="line">        google()</span><br><span class="line">        mavenCentral()</span><br><span class="line">    &#125;</span><br><span class="line">    dependencies &#123;</span><br><span class="line">        // Make sure to use a compatible version</span><br><span class="line">        classpath <span class="string">'com.android.tools.build:gradle:7.4.2'</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">allprojects &#123;</span><br><span class="line">    repositories &#123;</span><br><span class="line">        google()</span><br><span class="line">        mavenCentral()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">task clean(<span class="built_in">type</span>: Delete) &#123;</span><br><span class="line">    delete rootProject.buildDir</span><br><span class="line">&#125;</span><br><span class="line">EOF</span><br></pre></td></tr></table></figure><p>build.gradle中指定了编译项目时需要执行的动作，注意gradle的版本要和本机保持一致。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Adjusted AndroidManifest.xml creation with android:exported="true"</span></span><br><span class="line">cat &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/AndroidManifest.xml"</span> &lt;&lt;EOF</span><br><span class="line">&lt;?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"utf-8"</span>?&gt;</span><br><span class="line">&lt;manifest xmlns:android=<span class="string">"http://schemas.android.com/apk/res/android"</span></span><br><span class="line">    package=<span class="string">"<span class="variable">$PACKAGE_NAME</span>"</span>&gt;</span><br><span class="line"></span><br><span class="line">    &lt;application</span><br><span class="line">        android:allowBackup=<span class="string">"true"</span></span><br><span class="line">        android:icon=<span class="string">"@mipmap/ic_launcher"</span></span><br><span class="line">        android:label=<span class="string">"@string/app_name"</span></span><br><span class="line">        android:roundIcon=<span class="string">"@mipmap/ic_launcher_round"</span></span><br><span class="line">        android:supportsRtl=<span class="string">"true"</span></span><br><span class="line">        android:theme=<span class="string">"@style/Theme.AppCompat.Light.NoActionBar"</span>&gt;</span><br><span class="line">        &lt;activity android:name=<span class="string">".MainActivity"</span></span><br><span class="line">            android:exported=<span class="string">"true"</span>&gt; &lt;!-- Ensure this line is added --&gt;</span><br><span class="line">            &lt;intent-filter&gt;</span><br><span class="line">                &lt;action android:name=<span class="string">"android.intent.action.MAIN"</span> /&gt;</span><br><span class="line">                &lt;category android:name=<span class="string">"android.intent.category.LAUNCHER"</span> /&gt;</span><br><span class="line">            &lt;/intent-filter&gt;</span><br><span class="line">        &lt;/activity&gt;</span><br><span class="line">    &lt;/application&gt;</span><br><span class="line"></span><br><span class="line">&lt;/manifest&gt;</span><br><span class="line">EOF</span><br></pre></td></tr></table></figure><p>将安卓核心信息写入AndroidManifest.xml，包含图标、主题等信息。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">    <span class="comment"># 创建一个简单的MainActivity</span></span><br><span class="line">    cat &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/java/<span class="variable">$PACKAGE_NAME</span>/MainActivity.java"</span> &lt;&lt;EOF</span><br><span class="line">package <span class="variable">$PACKAGE_NAME</span>;</span><br><span class="line"></span><br><span class="line">import android.os.Bundle;</span><br><span class="line">import androidx.appcompat.app.AppCompatActivity;</span><br><span class="line"></span><br><span class="line">public class MainActivity extends AppCompatActivity &#123;</span><br><span class="line">    @Override</span><br><span class="line">    protected void onCreate(Bundle savedInstanceState) &#123;</span><br><span class="line">        super.onCreate(savedInstanceState);</span><br><span class="line">        setContentView(R.layout.activity_main);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">EOF</span><br></pre></td></tr></table></figure><p>MainActivity.java是应用执行的程序入口，这里显示了主视图即完成工作。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">    <span class="comment"># 创建一个简单的activity_main.xml布局文件</span></span><br><span class="line">    cat &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/src/main/res/layout/activity_main.xml"</span> &lt;&lt;EOF</span><br><span class="line">&lt;?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"utf-8"</span>?&gt;</span><br><span class="line">&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=<span class="string">"http://schemas.android.com/apk/res/android"</span></span><br><span class="line">    xmlns:app=<span class="string">"http://schemas.android.com/apk/res-auto"</span></span><br><span class="line">    xmlns:tools=<span class="string">"http://schemas.android.com/tools"</span></span><br><span class="line">    android:layout_width=<span class="string">"match_parent"</span></span><br><span class="line">    android:layout_height=<span class="string">"match_parent"</span></span><br><span class="line">    tools:context=<span class="string">".MainActivity"</span>&gt;</span><br><span class="line"></span><br><span class="line">    &lt;TextView</span><br><span class="line">        android:layout_width=<span class="string">"wrap_content"</span></span><br><span class="line">        android:layout_height=<span class="string">"wrap_content"</span></span><br><span class="line">        android:text=<span class="string">"欢迎使用XX系统_<span class="variable">$PROJECT_NAME</span>!"</span></span><br><span class="line">        app:layout_constraintBottom_toBottomOf=<span class="string">"parent"</span></span><br><span class="line">        app:layout_constraintLeft_toLeftOf=<span class="string">"parent"</span></span><br><span class="line">        app:layout_constraintRight_toRightOf=<span class="string">"parent"</span></span><br><span class="line">        app:layout_constraintTop_toTopOf=<span class="string">"parent"</span> /&gt;</span><br><span class="line">&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;</span><br><span class="line">EOF</span><br></pre></td></tr></table></figure><p>就算最简单的安卓应用也需要设置布局，布局在activity_main.xml中指定。这里只设置一个TextView作为展示。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建app模块的build.gradle文件</span></span><br><span class="line">cat &gt; <span class="string">"./<span class="variable">$PROJECT_NAME</span>/app/build.gradle"</span> &lt;&lt;EOF</span><br><span class="line">apply plugin: <span class="string">'com.android.application'</span></span><br><span class="line"></span><br><span class="line">android &#123;</span><br><span class="line">    compileSdkVersion 31</span><br><span class="line">    defaultConfig &#123;</span><br><span class="line">        applicationId <span class="string">"<span class="variable">$PACKAGE_NAME</span>"</span></span><br><span class="line">        minSdkVersion 21</span><br><span class="line">        targetSdkVersion 31</span><br><span class="line">        versionCode 1</span><br><span class="line">        versionName <span class="string">"1.0"</span></span><br><span class="line"></span><br><span class="line">        testInstrumentationRunner <span class="string">"androidx.test.runner.AndroidJUnitRunner"</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    buildTypes &#123;</span><br><span class="line">        release &#123;</span><br><span class="line">            minifyEnabled <span class="literal">false</span></span><br><span class="line">            proguardFiles getDefaultProguardFile(<span class="string">'proguard-android-optimize.txt'</span>), <span class="string">'proguard-rules.pro'</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">dependencies &#123;</span><br><span class="line">    implementation <span class="string">'androidx.appcompat:appcompat:1.3.0'</span></span><br><span class="line">    implementation <span class="string">'androidx.constraintlayout:constraintlayout:2.0.4'</span></span><br><span class="line">    testImplementation <span class="string">'junit:junit:4.13.2'</span></span><br><span class="line">    androidTestImplementation <span class="string">'androidx.test.ext:junit:1.1.3'</span></span><br><span class="line">    androidTestImplementation <span class="string">'androidx.test.espresso:espresso-core:3.4.0'</span></span><br><span class="line">&#125;</span><br><span class="line">EOF</span><br></pre></td></tr></table></figure><p>子模块的build.gradle中配置了安卓编译的版本及依赖信息，像这种简单的应用也不会依赖太多的东西。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 初始化Gradle Wrapper</span></span><br><span class="line"><span class="built_in">cd</span> <span class="string">"<span class="variable">$PROJECT_NAME</span>"</span></span><br><span class="line">gradle wrapper --gradle-version=7.5</span><br><span class="line"><span class="built_in">cd</span> ..</span><br><span class="line"></span><br><span class="line"><span class="comment"># 构建项目</span></span><br><span class="line"><span class="built_in">cd</span> <span class="string">"<span class="variable">$PROJECT_NAME</span>"</span></span><br><span class="line">./gradlew assembleDebug</span><br><span class="line"><span class="built_in">cd</span> ..</span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制APK到脚本执行目录</span></span><br><span class="line">cp ./<span class="string">"<span class="variable">$PROJECT_NAME</span>"</span>/app/build/outputs/apk/debug/app-debug.apk ./<span class="string">"<span class="variable">$PROJECT_NAME</span>.apk"</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"Project <span class="variable">$PROJECT_NAME</span> has been created and packaged."</span></span><br></pre></td></tr></table></figure><p>最后进入目录中，使用gradle打包、构建项目，并复制apk到bash执行目录中，方便后续统一收集、上传、部署。</p><p>至此一个能够批量生成安卓APK包的Bash脚本就做好了，通过脚本化，让LLM和机器给我打工，节省了大量的时间。</p><p>在外行人看来，苦劳和规模很大，就可以多要点价，所以工具自动化能够让副业的开展如虎添翼。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;4月的某天，接到了一单批量生成安卓apk包的私活。要求是点开后通过展示不同的背景图片，展示主角的极强业务能力。&lt;/p&gt;
&lt;p&gt;由于之前也没写过原生安卓，顶多用React Native写跨端应用。手工创建一堆工程，再手动修改，就完全是体力工作，这个肯定是不行的。&lt;/p&gt;
&lt;h</summary>
      
    
    
    
    <category term="tools" scheme="https://project256.com/categories/tools/"/>
    
    
    <category term="Bash" scheme="https://project256.com/tags/Bash/"/>
    
  </entry>
  
  <entry>
    <title>AI写作引起的对副业思考</title>
    <link href="https://project256.com/aigc/AI%E5%86%99%E4%BD%9C%E5%BC%95%E8%B5%B7%E7%9A%84%E5%AF%B9%E5%89%AF%E4%B8%9A%E6%80%9D%E8%80%83/"/>
    <id>https://project256.com/aigc/AI%E5%86%99%E4%BD%9C%E5%BC%95%E8%B5%B7%E7%9A%84%E5%AF%B9%E5%89%AF%E4%B8%9A%E6%80%9D%E8%80%83/</id>
    <published>2024-04-20T09:49:22.000Z</published>
    <updated>2026-03-29T09:06:11.642Z</updated>
    
    <content type="html"><![CDATA[<p>副业是否落地和炒股是否盈利一样，不能说炒股血亏就白干了，如果带着脑子去炒股或经营副业，也会对行业和经济有深刻的理解和看法。（这也是为什么管炒股亏钱或低抛高吸叫做“交学费”）</p><h2 id="副业的终极目标"><a href="#副业的终极目标" class="headerlink" title="副业的终极目标"></a>副业的终极目标</h2><p>我认为，所有人开展副业的终极目标就是——财务自由。</p><p>博多·费舍尔在《财务自由之路》系列书籍中，对财富自由的定义是：“只有当你的钱多到可以使你仅靠利息便可生活时，你才算真正的富有和独立。”。他也指出了实现的方法：“想拥有一台赚钱机器，而非穷其一生当一台赚钱机器。”。</p><p>他也指出，“目标财富数字仅仅是一个检测是否真正达到了目标的标尺，这其中更多关乎个性。重要的是，在这个过程中会变成怎样的人。生命短暂，不应该碌碌无为。”。</p><p>关于财务自由中的“自由”，做副业前需要思考主业以外的时间还想出卖劳动力换取收入么？</p><p>综上所述，我认为通往《财务自由之路》的途径是<strong>成为自动化工具开发或使用专家</strong>。开发能主动接触陌生领域，并产生价值。当然，能生产铲子给别人是最好的。而使用的成本较低，重在工具的选择和领域的选择。</p><p><strong>工作和生意上，最赚钱的都不是实际干活的，而是把资源聚合在一起满足了需求的。</strong></p><h2 id="副业不可能三角和原则"><a href="#副业不可能三角和原则" class="headerlink" title="副业不可能三角和原则"></a>副业不可能三角和原则</h2><p>不可能三角：门槛低、天花板高、可持续。只能三选二。</p><p>原则：快速闭环，快速得到正反馈。正所谓<strong>人因痛苦而改变，因获益而持续行动。</strong></p><p>一个好的副业会赚到如下四点：</p><ul><li>赚金钱——副业的收入。</li><li>赚认知——对商业、对人性有了更深刻的理解。有助于做出更好的判断，也为下一步赚更多打下基础。</li><li>赚技能——提升了自己的技能，例如AI、写作、社群技能等。</li><li>赚系统——通过项目搭建了一套系统，形成了一些自己做事的方法。</li></ul><p>副业不仅是赚钱的途径，还应该视为自己IP的基础设施，公域和私域转化的桥梁，实现长线坚持。</p><h2 id="关于文章写作"><a href="#关于文章写作" class="headerlink" title="关于文章写作"></a>关于文章写作</h2><p>虽然使用AI写作是一个副业的切入点，但首先得让自己把文章写明白。如果一个人本身的艺术修养很烂，如何作出让人欣赏的作品呢？</p><p>大辉《进步黑客》公众号中有一篇文章虽然讲AI写作相关的文章，写了如何写一篇微信公众号爆款文章。其实可以迁移学习到所有场景中，例如日常表达、文档协作、工作总结、分享展示等。下文摘录文章一些内容，在此感谢原作者大辉，他的<a href="https://wx.zsxq.com/dweb2/index/group/88858585542822" target="_blank" rel="noopener">知识星球</a>目前限免，是个很慷慨的老哥。</p><p>微信公众号的推荐算法主要有两个因素：<strong>标题的吸引力和文章的完读率</strong>。简单来说，就是让标题吸引人，再让文章内容好到让人看完。进一步，要想进入推荐池，要将80%的时间放在打磨标题上。</p><h3 id="爆款标题"><a href="#爆款标题" class="headerlink" title="爆款标题"></a>爆款标题</h3><p>一个有爆款潜力的标题，满足三个原则：</p><ul><li>覆盖人数要广 —— 大家都知道的大众平台</li><li>痛点程度高，痛点就是恐惧，恐惧程度 —— 「现在根本做不起来」标题表达现实的负面，在文中给出解决方案</li><li>激发社交源动力 —— 进入文章后看完，发现提供了新的视角，也有干货，点赞转发走起</li></ul><p>而做爆款标题的三个步骤：抄-换-创</p><ul><li>抄 —— 有限找到贴合主题的爆款标题，直接用</li><li>换 —— 保持语法结构，替换爆款中的名词。</li><li>创 —— 自己或借助AI工具创作。</li></ul><p>对于标题，能找到可用的，就尽量不要重头造轮子，做原创。并非走捷径，而是复制成功。比如上山要尽量走已经趟开的路，靠自己摸索容易掉到沟里。</p><p><strong>能找到贴合内容的爆款标题，就直接用。不能直接用的，保持结构、微调，替换名词不换动词。实在不行，就自己想或GPT生成组合。</strong></p><p>一切接在平时做功课，换标题对推流的作用非常大。</p><p>做自媒体，把收集火爆标题，当做一门功课来做，一个好标题，曝光量会增加十倍、百倍。</p><p>本人在阿里内网也写了一些ATA文章，其中点赞和收藏的标题是《穷哥们训练对话大模型——基于Chinese-LLaMA-Alpaca-2》。这篇文章标题中的“穷哥们”抄了其他一位老哥的创意，使用最低的预算来训练大模型。虽然很多同事都有使用大模型的经验，但也都想要一个手把手教的教程来入门训练大模型，本文正好切中了这个痛点。</p><h3 id="完读率"><a href="#完读率" class="headerlink" title="完读率"></a>完读率</h3><p>两个关键策略：</p><ul><li>控制文章长度 —— 太长的文章容易让人打退堂鼓，建议爆款文章长度在800到1000字左右。这个长度不仅能够满足原创性的要求，同时也足够紧凑，保持读者的注意力。（类比大模型的token限制和失忆问题）</li><li>用故事来吸引人——人们都喜欢听故事，把要说的道理和信息通过一个故事来表达，读者更容易读完整篇文章。（演讲也是如此，《乔布斯的魔力演讲》中也强调了故事和愿景的重要性）如果不擅长讲故事，可以使用擅长编故事的GPT来写，通过辅助构思各种吸引人的情节和框架，让文章更加生动有趣。</li></ul><h2 id="产品推广的写作技巧"><a href="#产品推广的写作技巧" class="headerlink" title="产品推广的写作技巧"></a>产品推广的写作技巧</h2><h3 id="文章中介绍产品的技巧"><a href="#文章中介绍产品的技巧" class="headerlink" title="文章中介绍产品的技巧"></a>文章中介绍产品的技巧</h3><p>六种方法：</p><ul><li>写用户、用户故事 —— <strong>身份和经历的共情是最大的信任来源，信任是变现的基础。</strong>不会被一个产品激励，但会被一个人激励。</li><li>写卖点 —— 通过讲故事的方式呈现卖点。</li><li>写理由 —— 因为自己受益，所以想把它推广给更多人。「得道者多助，失道者寡助」，占领「道」的时候，你的产品就会非常好卖。想要跟你达成同样状态的人，就愿意跟你一起做这件事，买这个产品，进入这个圈子。</li><li>写场景 —— 构建场景。低端销售讲卖点、高端销售讲情怀。高客单价或比较有历史的设计都会非常用心。</li><li>写痛点 —— 直接戳用户痛点（一眼望不到头、不想过这样的生活、不想被PUA等），在你的产品和圈子，跟用户的心智、痛点、将来以及解决方案深深地绑定在一起。</li><li>写用户证言 —— 知识付费就像庙里的神，一定不是去跟别人要什么才让香火越来越旺，而是能给出去的东西很多、能够实现别人的愿望，才会口口相传。注意晒的同时，也要夸你的用户。不要朋友圈都是成交记录、订单截图、聊天记录，而是成为别人想成为的样子，这才是个人IP。</li></ul><h3 id="把所有消费变成生产资料"><a href="#把所有消费变成生产资料" class="headerlink" title="把所有消费变成生产资料"></a>把所有消费变成生产资料</h3><p>花的每一分时间，都会把它写到文章或朋友圈里。带着一种做题家的心态去生活，做自媒体最重要的一点就是生产资料。</p><p>购买的一个社群或去店里消费，每次被成交都会总结一下为什么被成交，可以写出来展示给别人，影响别人的心智，再去成交我的产品。我的时间和精力非常珍贵，所以要最大化地发挥价值。</p><p>写作主题来源：</p><ul><li>把用户问题的答案写成很长很长的朋友圈，让其觉得非常重视他，其他有同样问题的人也会被我的这条朋友圈启发，以后就可能回来找我买产品。</li><li>大家问题差不多，问不出花来。设身处地地给他讲，帮他更好地去做选择，他会感激你，会觉得非常实在、靠谱，以后就可能会买你的产品。有时候是产品来转化，有时候是人来转化。</li></ul><p>不要纯想着产品卖点，而是要输出干货，有帮别人解决问题的方案或是普世的价值。</p><p>写完之后，可以从第三方角度审视一下，如果别人看了你这边内容，他是看到了满屏的广告还是真的有所收获。</p><h2 id="免费知识星球"><a href="#免费知识星球" class="headerlink" title="免费知识星球"></a>免费知识星球</h2><h3 id="IP-钩子"><a href="#IP-钩子" class="headerlink" title="IP+钩子"></a>IP+钩子</h3><p>钩子：<strong>能给读者什么好处 + 私域入口 + 自我介绍</strong></p><p>使用免费知识星球的理由：</p><ul><li>免费星球打开率高 —— 1W人的免费星球，每个帖子会有1K以上的阅读，平均打开率在10%以上。但公众号打开率在2%。</li><li>创作、运营要比公众号简单 —— 几句话、几十字，每天无限发。适合碎片化读写。</li><li>内容可以生成图片 —— 方便转发给朋友圈和微信群。</li><li>容量无限 —— 一个微信号最多1万人。</li><li>权限设置灵活 —— 可以设置为仅星主、合伙人和嘉宾发言，形成自己的主场，建立信任和成交转化都非常好用。</li></ul><p>也可以用AI将星球内容扩写，或将多篇星球帖子整合成，一篇内容丰富的公众号文章。</p><p>因此，从打开率、运营难度、分发效率来说。<strong>免费星球，很适合做内容生产基地，私域大本营。</strong></p><h3 id="做好一个知识星球"><a href="#做好一个知识星球" class="headerlink" title="做好一个知识星球"></a>做好一个知识星球</h3><p>三方面考虑：</p><ul><li>内容 —— 短内容，<strong>高频发</strong>对人有启发的短内容。一天5~20条。</li><li>运营 —— 做一些抽奖、打卡、布置作业等。有利于打开和留存，奖品可以是图书、专栏、微信红包，<strong>逐步加深链接</strong>。</li><li>拉新 —— 通过微信拉新进入星球很方便。</li></ul><h3 id="AI赋能知识星球"><a href="#AI赋能知识星球" class="headerlink" title="AI赋能知识星球"></a>AI赋能知识星球</h3><ul><li>用AI取名、绘制图标</li><li>AI辅助星球选题 —— 没有灵感、不知道写什么的时候，通过AI对话提供选题和内容。</li><li>用AI做数据分析</li></ul><h3 id="构建可持续性基础设施"><a href="#构建可持续性基础设施" class="headerlink" title="构建可持续性基础设施"></a>构建可持续性基础设施</h3><p>如果写作知识用来赚广告费，那就只能短期赚点钱。但如果从IP内核出发，把公众号当成一个基础设施，一个公域与私域的转化桥梁，那么这个就是一个可持续的事业。</p><p>在写的过程中，<strong>积累素材、积累用户、形成SOP、建设自媒体工具箱</strong>，那就是金钱、认知、技能和系统都赚到了。</p><h3 id="个人IP快速出圈"><a href="#个人IP快速出圈" class="headerlink" title="个人IP快速出圈"></a>个人IP快速出圈</h3><p>蹭热度、借口碑，靠近阳光，自身就会发光。卖有品牌货的同时，在用户眼里，就成了专家。</p><p>IP不是空口说说，要用成果，成绩去推进。公域做曝光、私域做沉淀，IP本质上是信任和曙光。</p><p>没有看见，就不会相信。</p><p>没有曝光，就没有信任。</p><p>多出场让目标人群看到场景、看到效果、看到过程、看到原料、看到他想要看到的一切。</p><h2 id="以AI辅助写作为例"><a href="#以AI辅助写作为例" class="headerlink" title="以AI辅助写作为例"></a>以AI辅助写作为例</h2><p>最有利可图的赚钱模式，就是老需求+新工具：辅助别人写作是老需求，AI是新工具。</p><p>在辅助写作这个古老行业，用上AI技术杠杆提高效率，就可以规模化放大。原始的辅助也就是谋生，赚辛苦费，但加上AI就能成规模放大。</p><p>有了AI外行人掌握基本的提示词方法，明确用户需求，就可以写出高质量原创内容。</p><p>挣钱多的人，并不是多有能力、认知有多高。而是提前入圈，提前获得了信息差，然后持续耕耘，就会形成自己的优势。</p><p>经典入局三个姿势：</p><ul><li>成为写手 —— 经典打工人思维，卖自己的技能和时间。</li><li>自己接单、自己写 —— 自己跑通全流程的个体户思维，容易顾头不顾尾。</li><li>工作室、资源整合 —— 前端做内容开店，后端找写手交付。</li></ul><h3 id="赛道和架构"><a href="#赛道和架构" class="headerlink" title="赛道和架构"></a>赛道和架构</h3><p>生意赚不赚钱，赚多少钱，一跟赛道有关，二跟架构有关。而差异就在架构上，架构要求现在的每个产品、每笔投入，要对未来发展无障碍、有贡献。</p><p>一开始就要把顶层架构和底层逻辑设计好，就能立即体会其中奥妙：</p><ul><li>生意是一个自增长的飞轮</li><li>老板不需要四处救火</li><li>维持日常运营，不需要很多人</li><li>自组织协作，行动敏捷</li></ul><p>任何项目都有周期，如果有规划的去做，每个项目都是自己成长路上的垫脚石。如果没有规划，项目红利期过后，再做其他项目，还是从零起步。</p><p>而做生意，就是架构盈利机器，架构好机器后，就剩三件事：</p><ul><li>铺设流量渠道</li><li>梳理成交流程</li><li>维护日常运营</li></ul><p>如同打造了一台机器，持续打磨、调教这台机器，它就会变成印钞机。例如大辉的免费知识星球，每天花十几分钟发发帖子，就有收入。</p><p>能赚到钱的核心，就是<strong>在获客上有创新，用新的玩法去获客</strong>。不要把电商当做主要的获客渠道，只作为一个获客的补充渠道。</p><h3 id="客户和平台的选择"><a href="#客户和平台的选择" class="headerlink" title="客户和平台的选择"></a>客户和平台的选择</h3><p>做项目，能做高客单，肯定就不做低客单。高客单的客户，基本都是高净值商户，放在私域也很值钱。</p><p>辅助写作是个非标的虚拟服务，100元和1000元的单，沟通成本和AI创作的差不多。</p><p>前期可以借一点低客单练练手，走顺了以后一定要做高客单。在辅助写作领域，1000元就算高客单。</p><p>高客单特征：一个有支付能力，另一个需要挖掘用户的需求。</p><p>电商几个平台之间，同样的服务淘宝要比拼多多卖更高的价格，闲鱼比淘宝高，小红书比闲鱼高。类似同样一瓶水，放在酒店、KTV和超市里面的感觉。<strong>辅助写作等非标服务，更与平台调性相匹配，其实和用户的心理预期相关。</strong>例如用户到拼多多就是贪便宜，完成任务，不想花太多钱。</p><p>低客单的出路：服务模板化、自动化，提高获客能力。例如主持稿可复制性强，开头结尾可以重复使用，积累几套后，换下名字稍微改下即可使用。</p><h3 id="避坑"><a href="#避坑" class="headerlink" title="避坑"></a>避坑</h3><p>核心要明确用户的需求，否则做完就需要返工。客户分为两种：有明确需求和需求模糊。</p><ul><li>有明确需求 —— 根据文章类型，结合AI指令，直接撰写文章。</li><li>需求不明确 —— 需要多次、反复跟用户沟通，确保自己的理解无偏差。</li></ul><p>明确需求后，拟定大纲。大纲也一定要跟客户再次确认。</p><p>最后生成文章，为了防止白嫖，需要打上水印，或只提供一部分内容，付完款确认后再完整提供。</p><p>为了避坑：<strong>找到靠谱的人，不要挑战人性，设计一套机制来避免</strong>（靠谱的人加上完善的奖惩机制）。遇到白嫖和跑路很正常，亲兄弟创业还经常撕破脸搞分家。就当成损耗来对待，无法避免但可以减少。</p><h3 id="如何获客"><a href="#如何获客" class="headerlink" title="如何获客"></a>如何获客</h3><p>用户行为路径：<strong>注意 → 兴趣 → 搜索 → 购买 → 使用 → 习惯</strong></p><p>有很多东西，购买后也就结束了，没有使用，也不会养成习惯。</p><p>辅助写作本质上是虚拟的服务电商，从用户行为路径出发，有三种获客方式：</p><ul><li>传统的货架电商 —— 淘宝、京东、闲鱼，通过搜索或目录浏览方式人找货</li><li>社交电商 —— 拼多多，砍一刀</li><li>兴趣电商 —— 抖音、视频号、小红书</li></ul><p>但人是懒惰的，除了重决策的购买行为，主动搜索会越来越少，所以淘宝日子越来越不好过。但货架电商前期也可以做，能够快速获得反馈，同时也能积累私域用户。</p><p>有一定私域用户，在朋友圈也可以直接获客。如果以前没有给人写作很厉害的形象，就以自己是消费者的身份来分享。例如：我需要写作一个什么东西，自己写了几天也没有写出来，后来认识到一个写作很厉害的朋友，很快就给我写出来了。你们谁有写作的需求，也可以找我，我把这位朋友介绍给你。</p><p>要想持续赚钱，得走这样的路线：<strong>深耕一个专业领域，内容获客，在私域提供销售，才有钱可赚，才能建立自己的壁垒</strong>。</p><h3 id="全面锻炼能力"><a href="#全面锻炼能力" class="headerlink" title="全面锻炼能力"></a>全面锻炼能力</h3><p>写写公众号，用爆文的思路去获客引流。</p><p>收获有三类：</p><ul><li>赚钱</li><li>积累私域</li><li>搭建自己的小团队</li></ul><p>AI时代，大部分事情，AI都能完成。但<strong>对情绪的共鸣、跟人打交道的能力</strong>短时间内AI还搞不定，这将越来越重要和之前。</p><h3 id="三个阶段"><a href="#三个阶段" class="headerlink" title="三个阶段"></a>三个阶段</h3><ul><li><p>月入3K —— 每天花3~4小时来写作，坐得住也愿意写作可以考虑这个模式</p></li><li><p>月入过万 —— 自己写的同时，要找其他写手来合作，这样能接的单就多了。你不能写的，可以给别人写，自己也有收入。</p></li><li><p>月入5W以上 —— 专业工作室玩法，建立好写作流程，做好分工，有稳定的合作写手、渠道、转化。</p></li></ul><h2 id="小故事结尾"><a href="#小故事结尾" class="headerlink" title="小故事结尾"></a>小故事结尾</h2><p>有个留学移民机构，业务是帮某地区代办高端人才签证，公司只有两个人，全程跟下来，总共收费一万左右，却赚了几千万。</p><p>亮点在收费机制：办成了拿走一万，办不成只收3000辛苦费。</p><p>猫腻在可以实际啥都不干，收资料装样子，不会用心办。一个人负责投广告，一个人负责接待客户。客户资料齐全，一次性成功的，就直接过了，不齐全的也就不用再管了。（本人是觉得这样很败人品，口碑还是很重要的。但如果做微信小程序，没有评价也就可以无视口碑。）</p><p>这在商业上也有很多变种，比如考研辅导班，金牌班一万，普通班3000，金牌班考不上退钱。</p><p>以本人申请香港优才被拒后，购买香港优才推荐信和自荐信书写服务为例。肯定是有一整套SOP流程在的，而且还有AI痕迹。最重要的是，签证是否申请成功是一个时间跨度很长的黑盒，本身就非常大的不确定性。因此，我认为这个故事非常真实。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;副业是否落地和炒股是否盈利一样，不能说炒股血亏就白干了，如果带着脑子去炒股或经营副业，也会对行业和经济有深刻的理解和看法。（这也是为什么管炒股亏钱或低抛高吸叫做“交学费”）&lt;/p&gt;
&lt;h2 id=&quot;副业的终极目标&quot;&gt;&lt;a href=&quot;#副业的终极目标&quot; class=&quot;hea</summary>
      
    
    
    
    <category term="aigc" scheme="https://project256.com/categories/aigc/"/>
    
    
    <category term="大模型" scheme="https://project256.com/tags/%E5%A4%A7%E6%A8%A1%E5%9E%8B/"/>
    
  </entry>
  
  <entry>
    <title>专家养成</title>
    <link href="https://project256.com/work/%E4%B8%93%E5%AE%B6%E5%85%BB%E6%88%90/"/>
    <id>https://project256.com/work/%E4%B8%93%E5%AE%B6%E5%85%BB%E6%88%90/</id>
    <published>2024-04-09T06:46:53.000Z</published>
    <updated>2026-03-29T09:06:11.643Z</updated>
    
    <content type="html"><![CDATA[<h2 id="核心"><a href="#核心" class="headerlink" title="核心"></a>核心</h2><ul><li>价值贡献 —— 规划并识别问题，预判方向并布局</li><li>技术素养 —— 抽象认知，较好架构设计能力，知晓上下游架构和规划，有持续改进能力，预判并防范系统稳定风险</li><li>开放与成长 —— 怎么做实现组织影响力</li></ul><p>使用STAR原则，对工作进行总结和展示。</p><h2 id="系统性思考"><a href="#系统性思考" class="headerlink" title="系统性思考"></a>系统性思考</h2><p>让我们面对选择时，能持续性地做出阶段性的最优选择，不会漏掉重要选择。学习效率更高，举一反三。</p><p>扩展阅读：<a href="https://wiki.mbalib.com/wiki/%E7%B3%BB%E7%BB%9F%E5%9F%BA%E6%A8%A1" target="_blank" rel="noopener">MBA智库百科——系统基模</a></p><h3 id="关键点"><a href="#关键点" class="headerlink" title="关键点"></a>关键点</h3><ul><li>拆解 —— 通过拆解，把一个复杂问题拆解成多个不那么复杂的问题，再把不那么复杂的问题继续拆解成简单的问题</li><li>构建逻辑关系：因果、5W2H —— 相互作用、相互关联、相互依赖的部分形成复杂又统一、具有特殊目的的整体</li><li>重要排序（ROI）</li></ul><h3 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h3><ul><li>深度思考 —— 从现象抓到事物的本质</li><li>全局思考 —— 从局部考虑到整体</li><li>动态思考 —— 理解每个人、每个业务之间都是动态变化的</li></ul><p>是一个Zoom Out和Zoom In的过程</p><h3 id="冰山模型"><a href="#冰山模型" class="headerlink" title="冰山模型"></a>冰山模型</h3><ul><li>事件 —— 正在发生的是什么？</li><li>规律 —— 什么被改变了，趋势是怎样的？</li><li>结构 —— 为什么会发生这个，怎么解释这个原因？</li><li>价值观 —— 是什么理念驱动了这个事情，背后的假设是什么？</li></ul><h3 id="工具方法"><a href="#工具方法" class="headerlink" title="工具方法"></a>工具方法</h3><h4 id="因果回路图"><a href="#因果回路图" class="headerlink" title="因果回路图"></a>因果回路图</h4><ul><li>变量 —— 建模的系统结构里的因素，它的值是随时间而变化的，一般是名词</li><li>链路 —— 变量之间可以形成链路，这个链路是因果链路</li><li>回路 —— 几条链路可以形成回路，如果从变量A到变量B有一条链路，当从变量B到变量A之间可以通过一系列其他的变量，也有一条链路时，就形成了回路。<ul><li>增强回路（滚雪球效应）</li><li>平衡回路（链路形成平衡，不会持续增加。例如洗澡水温控制到合适的温度，具有波动性和时延）</li></ul></li></ul><h4 id="5个因果回路基本模型"><a href="#5个因果回路基本模型" class="headerlink" title="5个因果回路基本模型"></a>5个因果回路基本模型</h4><ul><li>饮鸩止渴（临时方案虽然会带来平衡，但在时延后会导致未来的问题。会导致一部分平衡回路、另一部分恶性增强回路，长远来看越来越糟糕）</li><li>舍本逐末（优化架构对交付效率的提升有时延，还是可能走临时方案）</li><li>目标侵蚀（降低目标，降低完成目标决心，但会加速行动速度）</li><li>成长上限（一侧增长回路，而另一侧具有天花板）</li><li>公共悲剧（例如通过弹窗增长流量，用户对弹窗的容忍度和阈值在降低，最后所有都得弹窗）</li></ul><h3 id="一些引导性问题"><a href="#一些引导性问题" class="headerlink" title="一些引导性问题"></a>一些引导性问题</h3><p><strong>最好循环三次（五个为什么）</strong>——多谋、善断：</p><ul><li>为了解决我面对的子域实际业务问题，当前有哪些可能的解法？</li><li>这些解法的优势、劣势分别是什么？适合什么场景？</li><li>我的场景特点是什么？</li><li>与我场景最匹配的方案是什么？</li></ul><h3 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h3><p>一杯能装一升水，如何把两升水装进杯子里？</p><ul><li><p>先拆解核心要素——从多视角来看，尽可能多的可能性：杯、水、地球引力（第一性原理）</p></li><li><p>构建内在连接关系</p></li><li><p>做哪些事对解决的帮助最大</p></li></ul><p>描述：想要的水位 → 所感知的差距 → 水龙头调节速度 → 水龙头流速 → 现在的水位 → 所感知的差距……</p><h2 id="影响力"><a href="#影响力" class="headerlink" title="影响力"></a>影响力</h2><ul><li><p>一个人走得快、一群人走得远</p></li><li><p>双全难敌四手</p></li><li><p>三个臭皮匠、顶个诸葛亮</p></li></ul><p>关键点：</p><ul><li>干系人有哪些诉求</li><li>构建什么方案，能满足上述干系人的核心诉求。让其投入资源和时间，让项目成功。</li><li>项目流程机制管理</li><li>风险管理</li></ul><p>引导性问题：</p><ul><li>项目的背景是？</li><li>项目的核心目标是？</li><li>项目落地的阻碍有哪些？</li><li>有哪些可能性方案能够有助于解决这些阻碍？</li><li>选择什么方案来解决这些阻碍？</li></ul><h2 id="设计成长框架"><a href="#设计成长框架" class="headerlink" title="设计成长框架"></a>设计成长框架</h2><ul><li>主动设计学习计划</li><li>做好时间管理</li><li>运用反馈机制</li></ul><h3 id="最佳实践——知识"><a href="#最佳实践——知识" class="headerlink" title="最佳实践——知识"></a>最佳实践——知识</h3><p>一句话能说明白</p><ul><li>时间管理</li><li>情景听课</li><li>主题式学习</li><li>结构化PPT</li><li>细节文章</li><li>微思考</li></ul><h3 id="认知升级-——-认知"><a href="#认知升级-——-认知" class="headerlink" title="认知升级 —— 认知"></a>认知升级 —— 认知</h3><p>了解底层原理，改变行为</p><ul><li>思考&#x2F;反射脑</li><li>执行力</li><li>会议</li><li>花钱捡便宜</li><li>职业规划</li><li>贵人</li></ul><h3 id="扩大能力圈-——-技能"><a href="#扩大能力圈-——-技能" class="headerlink" title="扩大能力圈 —— 技能"></a>扩大能力圈 —— 技能</h3><p>反复学习和实践</p><ul><li>软件架构</li><li>管理能力</li><li>领导力</li><li>演讲能力</li><li>写作能力</li><li>育人育己</li></ul><h2 id="三大财富"><a href="#三大财富" class="headerlink" title="三大财富"></a>三大财富</h2><h3 id="注意力"><a href="#注意力" class="headerlink" title="注意力"></a>注意力</h3><p>分析注意力的消费去向：</p><ul><li>随大流刷信息流（根因在缺乏安全感。放松的同时，也是一种麻痹）</li><li>无意识阅读文章或公众号文章（自认为在学习，其实浪费时间和注意力）</li><li>兴趣爱好（兴趣和技能很重要）</li></ul><p>真正地安全感、成就感和归属感来自自我成长、自我沉淀</p><p>不需要完全戒除，合理分配注意力，专注于有积累的事情上</p><p><strong>60天内只定1<del>2个领域的学习目标，只看1</del>2个领域相关书籍、文章、课程</strong></p><p>沉浸式治愈注意力缺失：</p><ul><li>只字不差地阅读和理解</li><li>边读边思考边记录</li><li>写文章或抽象PPT</li></ul><h3 id="好习惯"><a href="#好习惯" class="headerlink" title="好习惯"></a>好习惯</h3><p>践行<strong>弹窗式提醒</strong>养成好习惯，精英之所以精英，无非是不间断的正向行为习惯的驱使。</p><p>触发条件 → 日常习惯 → 大脑即时奖励 → 日常习惯意识强化</p><p>当发生“下班了”的事件，<strong>给自己弹个框</strong>，通过多个选择空间来改变日常习惯。</p><p>触发条件 （弹窗）→ 日常习惯 → 成就&#x2F;价值激励 → 日常习惯意识强化</p><h4 id="4项精进"><a href="#4项精进" class="headerlink" title="4项精进"></a>4项精进</h4><ul><li>读好书，做笔记</li><li>勤总结，练架构</li><li>勤写作，捡细节</li><li>爱分享，成贵人</li></ul><h4 id="3份成就"><a href="#3份成就" class="headerlink" title="3份成就"></a>3份成就</h4><ul><li>一份时间卖出多份</li><li>深度思考力应对各场景</li><li>提升学习力改变认知</li></ul><h3 id="时间"><a href="#时间" class="headerlink" title="时间"></a>时间</h3><p>第三个八小时常干的：</p><ul><li>消费</li><li>交易</li><li>投资</li></ul><p>固化每日的“不被打扰时间”，<strong>人生的不同由第3个8小时创造</strong>，投资自己的成长。</p><p>每天抽2小时加上周末，一年可用的“不被打扰时间”：192 + 2 * 365 &#x3D; 922</p><h2 id="两大方法"><a href="#两大方法" class="headerlink" title="两大方法"></a>两大方法</h2><h3 id="OKR目标法"><a href="#OKR目标法" class="headerlink" title="OKR目标法"></a>OKR目标法</h3><p>聚焦 + 优先 + 关键</p><p>目标 + 资料 + 输出</p><h3 id="波浪输出式学习"><a href="#波浪输出式学习" class="headerlink" title="波浪输出式学习"></a>波浪输出式学习</h3><ul><li>只字不差阅读和听 —— 内容理解和作者思想</li><li>两轮笔记法 —— 重点知识概要整理</li><li>写PPT —— 抽象和结构化能力</li><li>写文章 —— 系结和表达能力</li></ul><h2 id="进阶成长路径"><a href="#进阶成长路径" class="headerlink" title="进阶成长路径"></a>进阶成长路径</h2><ul><li>技术性能力 —— 专业知识和相关操作能力</li><li>人际性能力 —— 沟通、协调、组织合作能力</li><li>概念性能力 —— 综合分析，找出趋势、时机、方向和目标</li></ul><h3 id="管理能力：组织机制设计、文化打造、组织阵型。"><a href="#管理能力：组织机制设计、文化打造、组织阵型。" class="headerlink" title="管理能力：组织机制设计、文化打造、组织阵型。"></a>管理能力：组织机制设计、文化打造、组织阵型。</h3><ul><li>组织建设 —— 主管任命、组织易协同、合作通道、团队沟通</li><li>文化土壤 —— 文化价值观、人员发展、梯队建设、组织能力、信任环境</li><li>定目标&#x2F;拆目标 → 进展、协同、效能、风险 → 拿结果</li></ul><h3 id="领导力"><a href="#领导力" class="headerlink" title="领导力"></a>领导力</h3><p>基础要求</p><ul><li>勇于担当</li><li>诚实正直</li><li>乐观自信</li><li>自我驱动</li></ul><p>定义：<strong>做好和做对的能力、榜样力量</strong></p><ul><li>及时有效双向沟通 —— 减少惊吓，建设安全和信任的沟通环境</li><li>执行力 —— 强调行动，把目标和规划变成成果的能力</li><li>做决策追过程  —— 务实性、突出，主动贡献价值</li><li>细节力 —— 强调专精，精益求精做好技术自信和产品的能力</li><li>学方法做教练 —— 带头学习，建梯队和培养他人</li><li>创新力 —— 强调新思路、新方法，保持团队的活力和创造力</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;核心&quot;&gt;&lt;a href=&quot;#核心&quot; class=&quot;headerlink&quot; title=&quot;核心&quot;&gt;&lt;/a&gt;核心&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;价值贡献 —— 规划并识别问题，预判方向并布局&lt;/li&gt;
&lt;li&gt;技术素养 —— 抽象认知，较好架构设计能力，知晓上下游架构和规</summary>
      
    
    
    
    <category term="work" scheme="https://project256.com/categories/work/"/>
    
    
    <category term="职场" scheme="https://project256.com/tags/%E8%81%8C%E5%9C%BA/"/>
    
  </entry>
  
  <entry>
    <title>Suno音乐生成——Coffee and Sunshine</title>
    <link href="https://project256.com/aigc/Suno%E9%9F%B3%E4%B9%90%E7%94%9F%E6%88%90%E2%80%94%E2%80%94Coffee-and-Sunshine/"/>
    <id>https://project256.com/aigc/Suno%E9%9F%B3%E4%B9%90%E7%94%9F%E6%88%90%E2%80%94%E2%80%94Coffee-and-Sunshine/</id>
    <published>2024-04-02T09:08:20.000Z</published>
    <updated>2026-03-29T09:06:11.642Z</updated>
    
    <content type="html"><![CDATA[<p>最近使用Suno生成音乐很火，这下又能扩展每个人的能力边界了。</p><p>只要有想法、预算充足、艺术审美在线就能创建自己的作品。</p><p>生成了一首《Coffee and Sunshine》，歌词、曲风都非常喜欢，明天早上喝一杯☕️迎接阳光吧~</p><audio src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/Coffee_and_Sunshine_v2.mp3" preload="none" controls loop>  你的浏览器不支持 audio 标签。</audio><p>里面有一句歌词我特别喜欢</p><blockquote><p>They bring me joy and warm my soul, no need for explanation<br>Sippin’ on that java, feelin’ the rays on my face</p></blockquote><h2 id="歌词"><a href="#歌词" class="headerlink" title="歌词"></a>歌词</h2><p>[Verse]<br>Wake up in the morning, I stretch and I yawn<br>The sunlight’s streaming through my window, it’s a brand new dawn<br>I stumble to the kitchen, my eyes still half-closed<br>But there’s one thing that I need before anything else, I suppose</p><p>[Chorus]<br>Coffee and sunshine, the perfect combination<br>They bring me joy and warm my soul, no need for explanation<br>Sippin’ on that java, feelin’ the rays on my face<br>Coffee and sunshine, life’s little embrace</p><p>[Verse 2]<br>Take a sip of the brew, the aroma fills the air<br>My senses awaken, problems seem to disappear<br>I step outside, the sun’s rays kiss my skin<br>It’s gonna be a good day, I can’t help but grin</p><p>[Verse 2]<br>The sky is painted in hues of blue and gold<br>A gentle breeze whispers secrets yet untold<br>Nature’s symphony, the birds begin to sing<br>I’m in a sunshine state of mind, let my soul take wing</p><p>[Chorus]<br>Oh, the world is mine, I’m free as the wind<br>With every breath, a new adventure begins</p><p><a href="https://app.suno.ai/song/94323ee5-2539-4f80-b480-1b90438b3b5e" target="_blank" rel="noopener">suno音乐地址</a></p><h2 id="生成方法"><a href="#生成方法" class="headerlink" title="生成方法"></a>生成方法</h2><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/WCV3mU.jpg" alt="Suno" title="">                </div>                <div class="image-caption">Suno</div>            </figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;最近使用Suno生成音乐很火，这下又能扩展每个人的能力边界了。&lt;/p&gt;
&lt;p&gt;只要有想法、预算充足、艺术审美在线就能创建自己的作品。&lt;/p&gt;
&lt;p&gt;生成了一首《Coffee and Sunshine》，歌词、曲风都非常喜欢，明天早上喝一杯☕️迎接阳光吧~&lt;/p&gt;
&lt;audi</summary>
      
    
    
    
    <category term="aigc" scheme="https://project256.com/categories/aigc/"/>
    
    
    <category term="大模型" scheme="https://project256.com/tags/%E5%A4%A7%E6%A8%A1%E5%9E%8B/"/>
    
  </entry>
  
  <entry>
    <title>量化交易之路——策略实践</title>
    <link href="https://project256.com/quant/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93%E4%B9%8B%E8%B7%AF%E2%80%94%E2%80%94%E7%AD%96%E7%95%A5%E5%AE%9E%E8%B7%B5/"/>
    <id>https://project256.com/quant/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93%E4%B9%8B%E8%B7%AF%E2%80%94%E2%80%94%E7%AD%96%E7%95%A5%E5%AE%9E%E8%B7%B5/</id>
    <published>2024-03-31T23:54:43.000Z</published>
    <updated>2026-03-29T09:06:11.645Z</updated>
    
    <content type="html"><![CDATA[<p>上一节介绍了量化交易的工具以及基础研究，本文讲述了量化交易的策略实践，及其效果。</p><p>通过GPT4.0书写交易策略，零成本跨界金融编程领域。如果效果不理想，也可以让其分析盈透API返回的表格文件，从而优化参数（小心过拟合）。</p><p><strong>免责声明：以下是我个人研究内容，代码由GPT生成，不代表任何投资建议，投资请谨慎。</strong></p><h2 id="个人代码库"><a href="#个人代码库" class="headerlink" title="个人代码库"></a>个人代码库</h2><p><a href="https://github.com/SUTFutureCoder/Quant" target="_blank" rel="noopener">Quant</a></p><h2 id="快速入门"><a href="#快速入门" class="headerlink" title="快速入门"></a>快速入门</h2><ol><li><p>注册<a href="https://www.interactivebrokers.com/cn/pagemap/pagemap_newaccounts_v3.php" target="_blank" rel="noopener">盈透账户</a>，下载<a href="https://www.interactivebrokers.com/en/trading/ibgateway-stable.php" target="_blank" rel="noopener">gateway</a></p></li><li><p>安装<a href="https://github.com/mementum/backtrader" target="_blank" rel="noopener">backtrader</a>库（量化回测框架）</p></li><li><p>安装<a href="https://github.com/erdewit/ib_insync" target="_blank" rel="noopener">ib_insync</a>库（对接盈透gateway，开发者老哥<a href="https://github.com/erdewit" target="_blank" rel="noopener">erdewit</a>于2024年3月11日去世，<strong>RIP</strong>）</p></li><li><p>通过ib_insync文档或是使用GPT写一个样例SMA策略</p></li><li><p>运行，观察图表</p></li></ol><h2 id="常见框架推荐"><a href="#常见框架推荐" class="headerlink" title="常见框架推荐"></a>常见框架推荐</h2><p><a href="https://github.com/mementum/backtrader" target="_blank" rel="noopener">backtrader </a>传统策略框架，本人先不上AI玄学，先用好传统框架。该框架由德国人开发，代码风格、结构和设计很适合学习。</p><p><a href="https://www.youtube.com/watch?v=oEG5LP1ZEvc" target="_blank" rel="noopener">qlib</a> AI量化框架</p><h2 id="IB-Gateway对接"><a href="#IB-Gateway对接" class="headerlink" title="IB Gateway对接"></a>IB Gateway对接</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> backtrader <span class="keyword">as</span> bt</span><br><span class="line"><span class="keyword">from</span> ib_insync <span class="keyword">import</span> IB, Stock, util</span><br><span class="line"><span class="keyword">import</span> pandas <span class="keyword">as</span> pd</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">IBStore</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self, host=<span class="string">'127.0.0.1'</span>, port=<span class="number">7497</span>, clientId=<span class="number">1</span>)</span>:</span></span><br><span class="line">        self.ib = IB()</span><br><span class="line">        self.ib.connect(host, port, clientId)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 默认回测一年，每一天为一个bar</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">get_data</span><span class="params">(self, symbol, durationStr=<span class="string">'1 Y'</span>, barSizeSetting=<span class="string">'1 day'</span>, whatToShow=<span class="string">'MIDPOINT'</span>)</span>:</span></span><br><span class="line">        contract = Stock(symbol, <span class="string">'SMART'</span>, <span class="string">'USD'</span>)</span><br><span class="line">        bars = self.ib.reqHistoricalData(</span><br><span class="line">            contract, endDateTime=<span class="string">''</span>, durationStr=durationStr,</span><br><span class="line">            barSizeSetting=barSizeSetting, whatToShow=whatToShow, useRTH=<span class="literal">True</span>)</span><br><span class="line">        df = util.df(bars)</span><br><span class="line">        <span class="comment"># 输出csv，用于GPT解释或优化参数</span></span><br><span class="line">        df.to_csv(<span class="string">'~/'</span> + symbol + <span class="string">'.csv'</span>, sep=<span class="string">','</span>, index=<span class="literal">False</span>, header=<span class="literal">True</span>)</span><br><span class="line">        <span class="keyword">return</span> df</span><br><span class="line">      </span><br><span class="line"><span class="comment"># 这里写策略类</span></span><br><span class="line"><span class="comment"># class SmaCross(bt.Strategy):</span></span><br><span class="line">      </span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">  <span class="comment"># 对接IB gateway API，端口改为IB gateway设置中的端口</span></span><br><span class="line">    ibstore = IBStore(<span class="string">'127.0.0.1'</span>, <span class="number">4002</span>, clientId=<span class="number">1</span>)</span><br><span class="line">    <span class="comment"># 选股，建议多选几种走势的股票</span></span><br><span class="line">    df = ibstore.get_data(<span class="string">'NVDA'</span>) <span class="comment"># NVDA单调递增</span></span><br><span class="line">    <span class="comment"># df = ibstore.get_data('AAPL') # AAPL波动剧烈</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># Ensure the 'date' column is in the correct datetime format</span></span><br><span class="line">    df[<span class="string">'date'</span>] = pd.to_datetime(df[<span class="string">'date'</span>])</span><br><span class="line">    df.set_index(<span class="string">'date'</span>, inplace=<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Load data into Backtrader</span></span><br><span class="line">    data = bt.feeds.PandasData(dataname=df)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Initialize and run Cerebro</span></span><br><span class="line">    cerebro = bt.Cerebro(stdstats=<span class="literal">True</span>, cheat_on_open=<span class="literal">True</span>, optreturn=<span class="literal">False</span>)</span><br><span class="line">    cerebro.addstrategy(SmaCross) <span class="comment"># 这里改为你的策略类名</span></span><br><span class="line">    cerebro.broker.set_cash(<span class="number">100000</span>) <span class="comment"># 本金</span></span><br><span class="line">    <span class="comment"># cerebro.broker.setcommission(commission=0.001) #交易手续费</span></span><br><span class="line">    cerebro.adddata(data)</span><br><span class="line">    cerebro.run()</span><br><span class="line">    cerebro.plot()</span><br></pre></td></tr></table></figure><h2 id="SMA策略"><a href="#SMA策略" class="headerlink" title="SMA策略"></a>SMA策略</h2><h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>SMA（简单的移动平均线），名字看上去就很简单，因此用这个策略入门能快速跑通一个回测流程。</p><p>SMA是选取过去一段时间内的收盘价作为数据，并对这些数据进行平均计算得到的。</p><blockquote><p>例如，要计算证券的20 天SMA，将过去20天的收盘价相加，然后除以20。</p><p>同样，要计算证券的200天SMA，将过去200天的收盘价相加，然后除以200。</p></blockquote><p>SMA的每个数据都有相同权重，但投资者可能认为当前数据比之前更重要，需要赋予更高的权重。因此更倾向于另一种形式的移动平均线，即EMA（指数移动平均线）。EMA可以在行情发生快速、剧烈波动时更具参考价值。</p><h3 id="趋势识别"><a href="#趋势识别" class="headerlink" title="趋势识别"></a>趋势识别</h3><p>高于SMA的证券交易处于上升趋势，而低于SMA处于下降趋势。</p><p>也可以识别支撑位和阻力位。</p><h3 id="交叉"><a href="#交叉" class="headerlink" title="交叉"></a>交叉</h3><h4 id="上升交叉和下跌交叉"><a href="#上升交叉和下跌交叉" class="headerlink" title="上升交叉和下跌交叉"></a>上升交叉和下跌交叉</h4><p>涉及SMA的投资策略一般是上升交叉和下跌交叉。</p><p>当证券价格低于SMA后回升到SMA上方时，就会产生上升交叉。代表回调结束，预示上升趋势的开始。反之为下跌交叉，代表开始回调。</p><p>然而在处于震荡的市场中，这个指标不太适用。</p><h4 id="黄金交叉和死亡交叉"><a href="#黄金交叉和死亡交叉" class="headerlink" title="黄金交叉和死亡交叉"></a>黄金交叉和死亡交叉</h4><p>当短期SMA跨越长期SMA时，会出现黄金交叉。例如50天SMA跨越200天SMA时，黄金交叉就会出现，是一个看涨的信号。反之就是死亡交叉，即看跌信号。</p><p>当出现死亡交叉时，投资者可以考虑暂时撤出市场。（回想我购买的医药基金，明明死亡交叉了两年，还要头铁去赌……）</p><h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><p>由GPT4生成，注意为了简单说明，代码中当出现交叉时全仓买入和卖出。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SmaCross</span><span class="params">(bt.Strategy)</span>:</span></span><br><span class="line">    params = dict(pfast=<span class="number">13</span>, pslow=<span class="number">25</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Define trading strategy</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">        sma1 = bt.ind.SMA(period=self.p.pfast)</span><br><span class="line">        sma2 = bt.ind.SMA(period=self.p.pslow)</span><br><span class="line">        self.crossover = bt.ind.CrossOver(sma1, sma2)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># Custom trade tracking</span></span><br><span class="line">        self.trade_data = []</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Execute trades</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">next</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="comment"># Trading the entire portfolio</span></span><br><span class="line">        size = int(self.broker.get_cash() / self.data.close[<span class="number">0</span>])</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> self.position:</span><br><span class="line">            <span class="keyword">if</span> self.crossover &gt; <span class="number">0</span>:</span><br><span class="line">                self.buy(size=size)</span><br><span class="line">                self.entry_bar = len(self)  <span class="comment"># Record entry bar index</span></span><br><span class="line">        <span class="keyword">elif</span> self.crossover &lt; <span class="number">0</span>:</span><br><span class="line">            self.close()</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Record trade details</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">notify_trade</span><span class="params">(self, trade)</span>:</span></span><br><span class="line">        <span class="keyword">if</span> trade.isclosed:</span><br><span class="line">            exit_bar = len(self)</span><br><span class="line">            holding_period = exit_bar - self.entry_bar</span><br><span class="line">            trade_record = &#123;</span><br><span class="line">                <span class="string">'entry'</span>: self.entry_bar,</span><br><span class="line">                <span class="string">'exit'</span>: exit_bar,</span><br><span class="line">                <span class="string">'duration'</span>: holding_period,</span><br><span class="line">                <span class="string">'profit'</span>: trade.pnl</span><br><span class="line">            &#125;</span><br><span class="line">            self.trade_data.append(trade_record)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Caclulating holding periods</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">stop</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="comment"># Calculate and print average holding periods</span></span><br><span class="line">        total_holding = sum([trade[<span class="string">'duration'</span>] <span class="keyword">for</span> trade <span class="keyword">in</span> self.trade_data])</span><br><span class="line">        total_trades = len(self.trade_data)</span><br><span class="line">        avg_holding_period = round(total_holding / total_trades) <span class="keyword">if</span> total_trades &gt; <span class="number">0</span> <span class="keyword">else</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># Calculating for winners and losers separately</span></span><br><span class="line">        winners = [trade <span class="keyword">for</span> trade <span class="keyword">in</span> self.trade_data <span class="keyword">if</span> trade[<span class="string">'profit'</span>] &gt; <span class="number">0</span>]</span><br><span class="line">        losers = [trade <span class="keyword">for</span> trade <span class="keyword">in</span> self.trade_data <span class="keyword">if</span> trade[<span class="string">'profit'</span>] &lt; <span class="number">0</span>]</span><br><span class="line">        avg_winner_holding = round(sum(trade[<span class="string">'duration'</span>] <span class="keyword">for</span> trade <span class="keyword">in</span> winners) / len(winners))<span class="keyword">if</span> winners <span class="keyword">else</span> <span class="number">0</span></span><br><span class="line">        avg_loser_holding = round(sum(trade[<span class="string">'duration'</span>] <span class="keyword">for</span> trade <span class="keyword">in</span> losers) / len(losers)) <span class="keyword">if</span> losers <span class="keyword">else</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># Display average holding period statistics</span></span><br><span class="line">        print(<span class="string">'Average Holding Period:'</span>, avg_holding_period)</span><br><span class="line">        print(<span class="string">'Average Winner Holding Period:'</span>, avg_winner_holding)</span><br><span class="line">        print(<span class="string">'Average Loser Holding Period:'</span>, avg_loser_holding)</span><br></pre></td></tr></table></figure><h3 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/截屏2024-03-3118.23.16.png" alt="NVDA股票" title="">                </div>                <div class="image-caption">NVDA股票</div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/截屏2024-03-3118.25.11.png" alt="AAPL" title="">                </div>                <div class="image-caption">AAPL</div>            </figure><h3 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h3><p>SMA在单调递增的NVDA股票表现不错，NVDA乘着AI的爆点一路上涨，然而此类的爆点又会有多少呢？</p><p>更多的是AAPL的波动趋势，SMA在波动趋势中反而亏损了8%所有。</p><h2 id="MACD策略"><a href="#MACD策略" class="headerlink" title="MACD策略"></a>MACD策略</h2><h3 id="简介-1"><a href="#简介-1" class="headerlink" title="简介"></a>简介</h3><p>MACD（Moving Average Convergence&#x2F;Divergence）名为指数平滑异同平均线，一般交易所图表中都能看到，也被人称作<strong>技术指标之王</strong>。</p><p>MACD指标由三个主要组件组成：DIF线、DEA线和MACD柱状图。参数为12、26、9，在下面的代码中可以看到。</p><ul><li>DIF线是快速移动平均线（一般为12天）减去慢速移动平均线（一般为26天）</li><li>DEA线是DIF线的9天加权移动平均线</li><li>MACD柱状图是DIF线和DEA线的差异，用来显示DIF线和DEA线的背离程度。</li></ul><h3 id="交叉策略"><a href="#交叉策略" class="headerlink" title="交叉策略"></a>交叉策略</h3><p>MACD的基本原理基于金叉和死叉</p><ul><li>金叉表示DIF线上穿DEA线，多头力量增强，预示着市场可能出现上涨趋势，是买入信号。</li><li>死叉表示DIF线下穿DEA线，空头力量增强，预示着市场可能出现下跌趋势，是卖出信号。</li></ul><h3 id="背驰策略"><a href="#背驰策略" class="headerlink" title="背驰策略"></a>背驰策略</h3><h4 id="红绿柱背离"><a href="#红绿柱背离" class="headerlink" title="红绿柱背离"></a>红绿柱背离</h4><p>红柱顶背离：股价创新高，但红柱却没有创新高。上升势能越来越小，可能预示大幅调整。</p><p>绿珠底背离：虽然股价在下跌，但下降的动能却逐渐减少，一旦多方势能占据优势，市场走势将由空转多。</p><h4 id="黄白线背离"><a href="#黄白线背离" class="headerlink" title="黄白线背离"></a>黄白线背离</h4><p>股价创新高&#x2F;低，但黄白线并没有创新高&#x2F;低。预示大幅调整或转多。</p><h4 id="买入点"><a href="#买入点" class="headerlink" title="买入点"></a>买入点</h4><p>底背离可能反复出现，进一步下探，可能被套。只能反复出现多次后（处于下降趋势个股，底背离出现的次数最好在两次以上），才能确认。</p><p>只有震荡走势到横盘走势，才考虑加仓。最佳买点在<strong>零轴金叉</strong>。</p><p>如果没有出现零轴金叉：</p><ul><li>寻找突破后的支撑点，即顶底转换的位置（金针探底收小阳）</li><li>找空中加油的地方（白线开始抬头往上走）</li><li>等放量突破现有横盘区域</li></ul><p>慢慢把仓位加上，不要在第一天金针探底就满仓。</p><h4 id="卖出点"><a href="#卖出点" class="headerlink" title="卖出点"></a>卖出点</h4><p>股价创出新高，红白线没有出现顶背离，但红柱出现了顶背离，上升的势能变低。</p><p>当出现放量滞涨，违背量升价涨的逻辑，有很强的抛压。</p><p>可能是获利盘或知道是高点的人在卖，主力在跑，就是卖点信号。</p><p>双重顶背离信号出现后，大概率会有回撤来消化这次背离。</p><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>出现顶背离，一般会迎来调整：情况好，波段调整；情况糟，趋势扭转。</p><p>出现底背离，很多票会经历长时间吸筹吸盘，然后迎来主升浪。</p><p>背离出现的次数越多越久，主升的行情也会越猛。</p><p>横有多长，竖有多高。</p><p><strong>MACD是趋势指标，只适合做趋势票，不能做情绪票，因为其滞后性很强。（想想那3个参数）</strong></p><p>市场没有成功率100%的方法。</p><h3 id="雪球大神总结口诀"><a href="#雪球大神总结口诀" class="headerlink" title="雪球大神总结口诀"></a>雪球大神总结口诀</h3><p><a href="https://xueqiu.com/2096888159/143983760" target="_blank" rel="noopener">一个中心，两个基本点，四项基本原则</a></p><h3 id="代码-1"><a href="#代码-1" class="headerlink" title="代码"></a>代码</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 定义策略</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">DynamicMACDStrategy</span><span class="params">(bt.Strategy)</span>:</span></span><br><span class="line">    params = (</span><br><span class="line">        (<span class="string">'fast'</span>, <span class="number">12</span>),</span><br><span class="line">        (<span class="string">'slow'</span>, <span class="number">26</span>),</span><br><span class="line">        (<span class="string">'signal'</span>, <span class="number">9</span>),</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">        self.macd = bt.indicators.MACD(self.data.close,</span><br><span class="line">                                       period_me1=self.params.fast,</span><br><span class="line">                                       period_me2=self.params.slow,</span><br><span class="line">                                       period_signal=self.params.signal)</span><br><span class="line">        self.crossover = bt.indicators.CrossOver(self.macd.macd, self.macd.signal)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">next</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> self.position:  <span class="comment"># 没有持仓</span></span><br><span class="line">            <span class="keyword">if</span> self.crossover &gt; <span class="number">0</span>:  <span class="comment"># MACD线上穿信号线</span></span><br><span class="line">                self.buy()</span><br><span class="line">        <span class="keyword">elif</span> self.crossover &lt; <span class="number">0</span>:  <span class="comment"># 已有持仓且MACD线下穿信号线</span></span><br><span class="line">            self.sell()</span><br></pre></td></tr></table></figure><h3 id="效果-1"><a href="#效果-1" class="headerlink" title="效果"></a>效果</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/截屏2024-03-3119.02.12.png" alt="NVDA" title="">                </div>                <div class="image-caption">NVDA</div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/截屏2024-03-3119.03.51.png" alt="AAPL" title="">                </div>                <div class="image-caption">AAPL</div>            </figure><h3 id="分析-1"><a href="#分析-1" class="headerlink" title="分析"></a>分析</h3><p>看起来单调上涨能获得正收益，频繁波动则本金没有什么变化。</p><h2 id="增强MACD策略"><a href="#增强MACD策略" class="headerlink" title="增强MACD策略"></a>增强MACD策略</h2><h3 id="GPT4"><a href="#GPT4" class="headerlink" title="GPT4"></a>GPT4</h3><p>从上面简单的MACD策略代码中，发现出现金叉或死叉时全仓买入和卖出。</p><p>因此将问题提给GPT4，看看它怎么解。</p><blockquote><p>我运行了这个策略，但盈利太少了，可能是因为买入和卖出是全部持仓，请优化策略代码，要求通过合适的买入和卖出策略，获得更大盈利效果。</p></blockquote><h3 id="代码-2"><a href="#代码-2" class="headerlink" title="代码"></a>代码</h3><p>GPT允许指定买入资金比例、止损止盈比例和盈利阈值，看起来比一把梭要科学多了。</p><p>另外提供了寻找快、慢、信号的参数方法。可以先按照默认方法执行，然后查看控制台输出的三个参数。然后将参数填入到cerebro.optstrategy(ImprovedMACDStrategy)中，例如：cerebro.optstrategy(ImprovedMACDStrategy, fast&#x3D;10, slow&#x3D;27, signal&#x3D;5)</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ImprovedMACDStrategy</span><span class="params">(bt.Strategy)</span>:</span></span><br><span class="line">    params = (</span><br><span class="line">        (<span class="string">'fast'</span>, <span class="number">12</span>),</span><br><span class="line">        (<span class="string">'slow'</span>, <span class="number">26</span>),</span><br><span class="line">        (<span class="string">'signal'</span>, <span class="number">9</span>),</span><br><span class="line">        (<span class="string">'order_percentage'</span>, <span class="number">0.95</span>),  <span class="comment"># 买入资金比例</span></span><br><span class="line">        (<span class="string">'stop_loss_percentage'</span>, <span class="number">0.95</span>),  <span class="comment"># 止损比例</span></span><br><span class="line">        (<span class="string">'take_profit_percentage'</span>, <span class="number">1.05</span>),  <span class="comment"># 止盈比例</span></span><br><span class="line">        (<span class="string">'partial_sell_percentage'</span>, <span class="number">0.5</span>),  <span class="comment"># 部分卖出比例</span></span><br><span class="line">        (<span class="string">'profit_threshold'</span>, <span class="number">1.02</span>),  <span class="comment"># 盈利阈值，达到则部分卖出</span></span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">        self.macd = bt.indicators.MACD(self.data.close,</span><br><span class="line">                                       period_me1=self.params.fast,</span><br><span class="line">                                       period_me2=self.params.slow,</span><br><span class="line">                                       period_signal=self.params.signal)</span><br><span class="line">        self.crossover = bt.indicators.CrossOver(self.macd.macd, self.macd.signal)</span><br><span class="line">        self.order = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">notify_order</span><span class="params">(self, order)</span>:</span></span><br><span class="line">        <span class="keyword">if</span> order.status <span class="keyword">in</span> [order.Completed]:</span><br><span class="line">            <span class="keyword">if</span> order.isbuy():</span><br><span class="line">                self.buy_price = order.executed.price</span><br><span class="line">                self.stop_price = self.buy_price * self.params.stop_loss_percentage</span><br><span class="line">                self.limit_price = self.buy_price * self.params.take_profit_percentage</span><br><span class="line">            self.order = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">next</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="keyword">if</span> self.order:</span><br><span class="line">            <span class="keyword">return</span>  <span class="comment"># 如果有未完成的订单，则不执行任何操作</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> self.position:  <span class="comment"># 没有持仓时</span></span><br><span class="line">            <span class="keyword">if</span> self.crossover &gt; <span class="number">0</span>:  <span class="comment"># MACD线上穿信号线，买入信号</span></span><br><span class="line">                self.order = self.buy(size=(self.broker.get_cash() / self.data.close[<span class="number">0</span>]) * self.params.order_percentage)</span><br><span class="line">        <span class="keyword">else</span>:  <span class="comment"># 已有持仓时</span></span><br><span class="line">            <span class="keyword">if</span> self.data.close[<span class="number">0</span>] / self.buy_price &gt; self.params.profit_threshold:</span><br><span class="line">                <span class="comment"># 达到盈利阈值，部分卖出</span></span><br><span class="line">                self.order = self.sell(size=self.position.size * self.params.partial_sell_percentage)</span><br><span class="line">            <span class="keyword">elif</span> self.crossover &lt; <span class="number">0</span> <span class="keyword">or</span> self.data.close[<span class="number">0</span>] &lt; self.stop_price <span class="keyword">or</span> self.data.close[<span class="number">0</span>] &gt; self.limit_price:</span><br><span class="line">                <span class="comment"># MACD线下穿信号线，或触发止损止盈，全仓卖出</span></span><br><span class="line">                self.order = self.close()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    ibstore = IBStore(<span class="string">'127.0.0.1'</span>, <span class="number">4002</span>, clientId=<span class="number">1</span>)</span><br><span class="line">    <span class="comment"># df = ibstore.get_data('NVDA')</span></span><br><span class="line">    df = ibstore.get_data(<span class="string">'AAPL'</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Ensure the 'date' column is in the correct datetime format</span></span><br><span class="line">    df[<span class="string">'date'</span>] = pd.to_datetime(df[<span class="string">'date'</span>])</span><br><span class="line">    df.set_index(<span class="string">'date'</span>, inplace=<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Load data into Backtrader</span></span><br><span class="line">    data = bt.feeds.PandasData(dataname=df)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Initialize and run Cerebro</span></span><br><span class="line">    cerebro = bt.Cerebro(stdstats=<span class="literal">True</span>, cheat_on_open=<span class="literal">True</span>, optreturn=<span class="literal">False</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 1-默认参数</span></span><br><span class="line">    <span class="comment"># cerebro.optstrategy(ImprovedMACDStrategy)</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 2-范围里面找最优解，查看控制台With Parameters输出。不同股票需要重新计算。</span></span><br><span class="line">    <span class="comment"># cerebro.optstrategy(ImprovedMACDStrategy, fast=range(10, 15), slow=range(20, 30), signal=range(5, 10))</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 3-根据With Parameters输出，配置最优解。这个是AAPL的最优解，不同股票需要重新计算。</span></span><br><span class="line">    <span class="comment"># cerebro.optstrategy(ImprovedMACDStrategy, fast=10, slow=27, signal=5)</span></span><br><span class="line">    cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name=<span class="string">'sharpe'</span>)</span><br><span class="line">    cerebro.addanalyzer(bt.analyzers.Returns, _name=<span class="string">'returns'</span>)</span><br><span class="line">    cerebro.broker.set_cash(<span class="number">100000</span>)</span><br><span class="line">    <span class="comment"># cerebro.broker.setcommission(commission=0.001)</span></span><br><span class="line">    cerebro.adddata(data)  <span class="comment"># 确保这里的data是bt.feeds对象，不是pandas DataFrame</span></span><br><span class="line">    result = cerebro.run()</span><br><span class="line"></span><br><span class="line">    best_params = <span class="literal">None</span></span><br><span class="line">    best_sharpe = float(<span class="string">'-inf'</span>)  <span class="comment"># 初始化为负无穷大</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> run <span class="keyword">in</span> result:</span><br><span class="line">        <span class="keyword">for</span> strategy <span class="keyword">in</span> run:</span><br><span class="line">            sharpe = strategy.analyzers.sharpe.get_analysis()[<span class="string">'sharperatio'</span>]</span><br><span class="line">            params = strategy.params</span><br><span class="line">            <span class="keyword">if</span> sharpe &gt; best_sharpe:</span><br><span class="line">                best_sharpe = sharpe</span><br><span class="line">                best_params = params</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 根据</span></span><br><span class="line">    print(<span class="string">f"Best Sharpe Ratio: <span class="subst">&#123;best_sharpe&#125;</span>"</span>)</span><br><span class="line">    print(<span class="string">f"With Parameters: Fast=<span class="subst">&#123;best_params.fast&#125;</span>, Slow=<span class="subst">&#123;best_params.slow&#125;</span>, Signal=<span class="subst">&#123;best_params.signal&#125;</span>"</span>)</span><br><span class="line">    cerebro.plot()</span><br></pre></td></tr></table></figure><h3 id="效果-2"><a href="#效果-2" class="headerlink" title="效果"></a>效果</h3><h4 id="默认参数（12、26、9）"><a href="#默认参数（12、26、9）" class="headerlink" title="默认参数（12、26、9）"></a>默认参数（12、26、9）</h4><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/截屏2024-03-3119.11.52.png" alt="NVDA" title="">                </div>                <div class="image-caption">NVDA</div>            </figure><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/截屏2024-03-3119.11.01.png" alt="AAPL" title="">                </div>                <div class="image-caption">AAPL</div>            </figure><h4 id="各支股票的最优参数"><a href="#各支股票的最优参数" class="headerlink" title="各支股票的最优参数"></a>各支股票的最优参数</h4><p><strong>注意：不同股票的参数需要重新计算，如果不是震荡市，计算参数可能没有优化效果</strong></p><p>NVDA</p><p>Best Sharpe Ratio: 0.8460012912318833<br>With Parameters: Fast&#x3D;13, Slow&#x3D;24, Signal&#x3D;9</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/截屏2024-03-3119.17.03.png" alt="NVDA" title="">                </div>                <div class="image-caption">NVDA</div>            </figure><p>AAPL</p><p>Best Sharpe Ratio: 0.801877924498146<br>With Parameters: Fast&#x3D;10, Slow&#x3D;27, Signal&#x3D;5</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/截屏2024-03-3119.13.48.png" alt="AAPL" title="">                </div>                <div class="image-caption">AAPL</div>            </figure><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><p>本文搭建了一套回测工具集，对一些常见的技术指标进行了介绍，提供了对应的代码和运行效果。</p><p>接下来会继续优化MACD及其他策略，获得更好的效果。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;上一节介绍了量化交易的工具以及基础研究，本文讲述了量化交易的策略实践，及其效果。&lt;/p&gt;
&lt;p&gt;通过GPT4.0书写交易策略，零成本跨界金融编程领域。如果效果不理想，也可以让其分析盈透API返回的表格文件，从而优化参数（小心过拟合）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;免责声明</summary>
      
    
    
    
    <category term="quant" scheme="https://project256.com/categories/quant/"/>
    
    
    <category term="量化交易" scheme="https://project256.com/tags/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93/"/>
    
  </entry>
  
  <entry>
    <title>量化交易之路——基础研究</title>
    <link href="https://project256.com/quant/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93%E4%B9%8B%E8%B7%AF%E2%80%94%E2%80%94%E5%9F%BA%E7%A1%80%E7%A0%94%E7%A9%B6/"/>
    <id>https://project256.com/quant/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93%E4%B9%8B%E8%B7%AF%E2%80%94%E2%80%94%E5%9F%BA%E7%A1%80%E7%A0%94%E7%A9%B6/</id>
    <published>2024-03-31T07:49:14.000Z</published>
    <updated>2026-03-29T09:06:11.645Z</updated>
    
    <content type="html"><![CDATA[<p>投资市场过程的便捷促进了人们的非理性投资。</p><p>2022~2024年的地狱市场把我的心态、生活和账户搞得一团糟，暂且不说基金经理装死的外部问题，本人也执行了一些非理性操作造成了损失。</p><p>痛定思痛，为了避免市场波动对心态和生活的持续影响，本人将目光集中到了量化自动化交易上面。</p><p>关于投资这方面，对于没时间看盘、又想获得超过指数基金收益的我而言，我还是信任计算机的超理性决策。</p><p><strong>免责声明：以下是我个人研究内容，并不代表任何投资建议，投资请谨慎。</strong></p><h2 id="快速入门"><a href="#快速入门" class="headerlink" title="快速入门"></a>快速入门</h2><ol><li><p>注册<a href="https://www.interactivebrokers.com/cn/pagemap/pagemap_newaccounts_v3.php" target="_blank" rel="noopener">盈透账户</a>，下载<a href="https://www.interactivebrokers.com/en/trading/ibgateway-stable.php" target="_blank" rel="noopener">gateway</a></p></li><li><p>安装<a href="https://github.com/mementum/backtrader" target="_blank" rel="noopener">backtrader</a>库（量化回测框架）</p></li><li><p>安装<a href="https://github.com/erdewit/ib_insync" target="_blank" rel="noopener">ib_insync</a>库（对接盈透gateway，开发者老哥<a href="https://github.com/erdewit" target="_blank" rel="noopener">erdewit</a>于2024年3月11日去世，RIP）</p></li><li><p>通过ib_insync文档或是使用GPT写一个样例SMA策略</p></li><li><p>运行，观察图表</p></li></ol><h2 id="常见框架推荐"><a href="#常见框架推荐" class="headerlink" title="常见框架推荐"></a>常见框架推荐</h2><p><a href="https://github.com/mementum/backtrader" target="_blank" rel="noopener">backtrader </a>传统策略框架 </p><p><a href="https://www.youtube.com/watch?v=oEG5LP1ZEvc" target="_blank" rel="noopener">qlib</a> AI量化框架</p><h2 id="海龟交易法"><a href="#海龟交易法" class="headerlink" title="海龟交易法"></a>海龟交易法</h2><p>本质上是一种趋势跟踪策略，法则囊括了交易中的每一个环节，没有给交易者留下主观思考的余地。</p><p>和量化交易有些关联，可以作为相关参数的设置方法。当然参数也可以通过程序来自动寻找。</p><h3 id="核心思维"><a href="#核心思维" class="headerlink" title="核心思维"></a>核心思维</h3><ul><li>以长远的眼光看待交易</li><li>避免结果偏好</li><li>相信正期望的威力</li></ul><h3 id="操作层面"><a href="#操作层面" class="headerlink" title="操作层面"></a>操作层面</h3><ul><li>首先掌握一个优势的系统，找到一个期望值为正的交易策略（至少在历史上回测能产生正的收益）</li><li>管理风险，守住阵地</li><li>坚定不移，执行策略</li><li>简单明了，抓住趋势。大部分利润产生于2~3次的交易，不要错过任何一次趋势。否则全年努力都会化成泡影。</li></ul><h3 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h3><h4 id="市场：买卖什么？"><a href="#市场：买卖什么？" class="headerlink" title="市场：买卖什么？"></a>市场：买卖什么？</h4><p>一般用于期货市场，要有足够流动性 30年期美国国债、10年期美国国债、大宗商品、发达国家的货币</p><h4 id="头寸规模：买卖多少？"><a href="#头寸规模：买卖多少？" class="headerlink" title="头寸规模：买卖多少？"></a>头寸规模：买卖多少？</h4><p>核心思想：波动性大的合约持仓量少，波动小的合约持仓量大</p><p>方法：用波动性来动态建仓控制日内亏损幅度不超过总资产的1%</p><blockquote><p>H:当日最高价</p><p>L:当日最低价</p><p>C:当日收盘价</p><p>TR:日真实波动幅度</p><p>TR &#x3D; MAX(H-L, H-C, C-L)</p></blockquote><p>求TR的20日移动平均值N</p><blockquote><p>N &#x3D; (19 * PDN + TR) &#x2F; 20</p><p>PDN: 前一日的N值</p><p>TR: 日真实波动幅度</p></blockquote><p>在21天开始计算。之前的20天平均值用前面20天的TR相加除以20</p><blockquote><p>(账户的1% &#x2F; 单位头寸金额) &#x3D; (N &#x2F; 股价)</p><p>单位头寸金额 &#x3D; (账户的1% &#x2F; N) * 股价</p><p>单位头寸股数 &#x3D; (账户的1% &#x2F; N)</p></blockquote><p>波动越大的股票，N越大，头寸越小</p><p>海龟交易法要求对于单一市场的持仓，不能超过4个单位的头寸金额</p><h4 id="入市：什么时候买卖？"><a href="#入市：什么时候买卖？" class="headerlink" title="入市：什么时候买卖？"></a>入市：什么时候买卖？</h4><blockquote><p>系统1：以20日突破为基础的短期系统</p><p>系统2：以55日突破为基础的长期系统</p></blockquote><p>长线看超过55日的最高点和最低点就开始行动</p><blockquote><p>如果超过了55日的高点，买入一个头寸，开始做多</p><p>如果跌破55日的低点，卖出一个头寸，开始做空【如果不熟悉做空，可以只考虑做多】</p></blockquote><h4 id="建仓：突破点建立一个头寸单位"><a href="#建仓：突破点建立一个头寸单位" class="headerlink" title="建仓：突破点建立一个头寸单位"></a>建仓：突破点建立一个头寸单位</h4><p>​接着在(1&#x2F;2 N) 的价格间隔一步一步扩大头寸</p><p>​每隔1&#x2F;2N就加一个头寸单位，直到加满4个单位的头寸为止</p><p>​逐步减仓巧妙，如果股价下跌1N总的亏损还是保持总资产的1%</p><h4 id="止损：什么时候放弃一个亏损的头寸？"><a href="#止损：什么时候放弃一个亏损的头寸？" class="headerlink" title="止损：什么时候放弃一个亏损的头寸？"></a>止损：什么时候放弃一个亏损的头寸？</h4><p>​<strong>任何一笔都不能亏损超过总资产的2%</strong></p><p>​波动1N就对应总资产的1%的波动</p><p>​按2%计算，止损的标准就是2N</p><p>​股价最后一笔买入后，如果下跌2N，就止损</p><h4 id="退出：什么时候退出一个盈利的头寸？"><a href="#退出：什么时候退出一个盈利的头寸？" class="headerlink" title="退出：什么时候退出一个盈利的头寸？"></a>退出：什么时候退出一个盈利的头寸？</h4><blockquote><p>系统1：采用10日突破退出法则</p></blockquote><blockquote><p>系统2：采用20日突破退出法则</p></blockquote><p>创了20日新低就退出</p><blockquote><p>优点：虽然比较反直觉，但能保证持仓者不会错过这种大涨幅的趋势</p><p>缺点：眼睁睁看着巨额的利润瞬间蒸发掉</p></blockquote><h4 id="战术：怎么买卖？"><a href="#战术：怎么买卖？" class="headerlink" title="战术：怎么买卖？"></a>战术：怎么买卖？</h4><p>资金量小就不用考虑了</p><h2 id="M-score模型识别财务造假"><a href="#M-score模型识别财务造假" class="headerlink" title="M-score模型识别财务造假"></a>M-score模型识别财务造假</h2><p>选股前过滤一遍，把造假股票踢出去。</p><blockquote><p>M &#x3D; -4.84 + 0.92 * DSRI + 0.528 * GMI + 0.404 * AQI + 0.892 * SGI + 0.115 * DEPI - 0.172 * SGAI + 4.679 * TATA - 0.327 * LVGI</p></blockquote><p>简单线性公式，求M值即可。</p><h3 id="参数说明"><a href="#参数说明" class="headerlink" title="参数说明"></a>参数说明</h3><p>SRI 应收账款指数</p><p>DSRI &#x3D; (本期应收账款 &#x2F; 本期营业收入) &#x2F; (上期应收账款 &#x2F; 上期营业收入)</p><p>GMI 毛利率指数</p><blockquote><p>GMI &#x3D; (上期毛收益率) &#x2F; (本期毛收益率)</p><p>毛收益率 &#x3D; (营业收入 - 营业成本) &#x2F; 营业收入</p></blockquote><p>AQI 资产质量指数</p><blockquote><p>AQI &#x3D; (本期非实物资产 &#x2F; 本期总资产) &#x2F; (上期非实物资产 &#x2F; 上期总资产)</p><p>非实物资产 &#x3D; (总资产 - 流动性资产 - 厂房设备 - 证券)</p></blockquote><p>SGI 营业收入指数</p><blockquote><p>SGI &#x3D; 本期营业收入 &#x2F; 上期营业收入</p></blockquote><p>DEPI 折旧率指数</p><blockquote><p>DEPI &#x3D; 上期折旧率 &#x2F; 本期折旧率</p><p>折旧率 &#x3D; 累计折旧 &#x2F; (厂房设备 + 累计折旧)</p></blockquote><p>SGAI 销售管理费用指数</p><blockquote><p>SGAI &#x3D; (本期销售管理费用 &#x2F; 本期营业收入) &#x2F; (上期销售管理费用 &#x2F; 上期营业收入)</p></blockquote><p>TATA 应计系数</p><blockquote><p>TATA &#x3D; 应计项目 &#x2F; 总资产</p><p>应计项目 &#x3D; 经营性营业收入 - 经营活动产生的现金流</p></blockquote><p>LVGI 财务杠杆指数</p><blockquote><p>LVGI &#x3D; 本期资产负债率 &#x2F; 上期资产负债率</p><p>资产负债率 &#x3D; 总负债 &#x2F; 总资产</p></blockquote><p>M范围 → 财务造假可能性<br>M &lt; -2.22                   低<br>-2.22 &lt;&#x3D; M &lt;&#x3D; -1.78  中<br>-1.78 &lt;&#x3D; M                 高</p><h2 id="股票波动预测"><a href="#股票波动预测" class="headerlink" title="股票波动预测"></a>股票波动预测</h2><p>python实现garch模型，广义自回归条件一方差模型。</p><p>平静期常常跟着平静期，波动期常常跟着波动期 → 波动性聚集。</p><p>尖峰厚尾，金融资产出现极端情况的概率远大于正态分布概率，所以凯利公式要除以2。</p><p>好的想法是拼接出来的，模型和工具是拼接的元件，元件可以不用学得特别透彻。厨子不用知道怎么制造菜刀，只需要菜刀来剁菜和烹调。</p><p>波动率可以预测风险，根据风险可以设计不同的持仓水平。还可以根据波动率调整交易策略。</p><h2 id="业绩测评指标"><a href="#业绩测评指标" class="headerlink" title="业绩测评指标"></a>业绩测评指标</h2><p>盈亏类</p><blockquote><p>总交易次数、赢的次数、亏的次数、胜率</p><p>度量交易频繁程度和胜率如何</p></blockquote><ul><li>总利润：所有盈利和</li><li>总损失：所有亏损和</li><li>净利润：总利润 - 总损失</li><li>利润因子：总利润 &#x2F; -总亏损</li><li>平均交易利润：净利润 &#x2F; 交易次数</li><li>平均盈利：总利润 &#x2F; 交易次数</li><li>平均亏损：总亏损 &#x2F; 交易次数</li><li>累积回报率：总回报率</li><li>平均回报率：年化回报率</li></ul><p>风险率</p><blockquote><p>衡量策略风险程度，以及需要忍受的痛苦有多大、多长时间</p></blockquote><ul><li>最大回撤：在一段时间内，资产的最高点到资产的最低点下跌的幅度</li><li>最大回撤时长：资产从最高点到最低点经历的时间</li><li>波动率：收益的方差</li></ul><p>比率类</p><blockquote><p>夏普比率：(年化收益率 - 无风险收益率) &#x2F; 收益率标准差<br>衡量获得一份收益，需要承受多少波动。<br>收益越高波动越低越好，越大越好。<br>可以观察投资大师的夏普比率有多少。</p></blockquote><ul><li>索诺提比率</li><li>夏普比率的改进</li><li>卡玛比率</li><li>夏普比率方差项用最大回撤来代替</li></ul><p>其他类</p><blockquote><p>Alpha：超额收益率</p><p>你的收益率 - 大盘收益率</p></blockquote><ul><li><p>Beta：你的收益率和大盘收益率相关性</p></li><li><p>SQN：系统质量指数，评估量化策略，优劣的指标</p></li></ul><p>Python库 —— pyfolio库</p><p>期权定价公式</p><blockquote><p>BS公式 二叉树模型 </p></blockquote><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>投资虽然暂时没有让我的财富增长，但也逐步了解了世界运行的基本原理，涨跌中锤炼了心灵。研究行业的同时，也对某些行业有了更加深刻的认识。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;投资市场过程的便捷促进了人们的非理性投资。&lt;/p&gt;
&lt;p&gt;2022~2024年的地狱市场把我的心态、生活和账户搞得一团糟，暂且不说基金经理装死的外部问题，本人也执行了一些非理性操作造成了损失。&lt;/p&gt;
&lt;p&gt;痛定思痛，为了避免市场波动对心态和生活的持续影响，本人将目光集中到</summary>
      
    
    
    
    <category term="quant" scheme="https://project256.com/categories/quant/"/>
    
    
    <category term="量化交易" scheme="https://project256.com/tags/%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93/"/>
    
  </entry>
  
  <entry>
    <title>AIGC的思考</title>
    <link href="https://project256.com/aigc/AIGC%E7%9A%84%E6%80%9D%E8%80%83/"/>
    <id>https://project256.com/aigc/AIGC%E7%9A%84%E6%80%9D%E8%80%83/</id>
    <published>2024-03-30T08:26:46.000Z</published>
    <updated>2026-03-29T09:06:11.642Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>我一般使用AIGC解决超出我能力或是知识领域的问题，例如：绘制视频封面、解决安卓开发问题、使用某个冷门包、编写量化交易策略、分析金融数据、撰写起诉状等。但对于能力和知识领域内的问题，使用AIGC反而会降低效率。</p><p>代码片段使用AIGC还能提高部分效率，但如果是需要从大全局视角进行设计的大型项目则无能为力。</p><p>我认为AIGC与人类现存的工作是补充关系而非互斥。AIGC能够让人类突破个人知识的局限性，从而帮助个人低成本地接触或解决不同领域的知识或问题。</p><p>虽然看起来是节省了几十年的学习过程，但也很难取代专家的存在，尤其是个人难以描述清楚自己的问题时，更需要专家的人工介入。专家可以结合几十年的学习和大模型的答案，从而提供更好的服务。</p><h2 id="AIGC怎么入门"><a href="#AIGC怎么入门" class="headerlink" title="AIGC怎么入门"></a>AIGC怎么入门</h2><p>看一本<strong>《学会提问》</strong>即可入门。甚至看完还能够提高在工作中的沟通效率。</p><p>比那些公众号一方面鼓吹AIGC将淘汰一批工作和人，另一方面发二维码卖课，不知道高到哪里去了。</p><h2 id="AIGC如何变为生产力"><a href="#AIGC如何变为生产力" class="headerlink" title="AIGC如何变为生产力"></a>AIGC如何变为生产力</h2><p>更进一步，如何结合多个大模型实现多媒体的AIGC？</p><p>受到<a href="https://github.com/FujiwaraChoki/MoneyPrinter" target="_blank" rel="noopener">MoneyPrinter</a>项目启发，本人认为只需要买个OpenAI API账号，实践一下用一句话生成剧本、剧本生成图片、剧本合成音频、图片生成短视频，再将上述部分结合起来，完全就可以入门大模型的使用。</p><p>当然，一般使用者无需关心大模型的训练。如果需要训练，一般关注SFT方法，大模型都有一键微调脚本可以用，本人之前也写过<a href="https://project256.com/categories/aigc/">微调大模型</a>的文章。有些低质量Agent也只是在Prompt里面加上了“你是一个xxx，当提问xxx时，你需要以xxx风格回答。请不要回复这句话以及好与不好，直接回答我输入的提问：”的开头。</p><p>而一些平台对于AIGC管控更加严格，甚至YouTube取消了很多不露脸的YouTuber的收益，因此AIGC对于内容生产还是处于辅助部分比较好。自动化AIGC建议还是需要人工审查确保符合平台规定。</p><p>AIGC变为生产力的下一步就是将其自动化（设计pipeline），或与日常工具相连接，让大模型更好地赋能生产过程。</p><h2 id="AIGC时代生存指南"><a href="#AIGC时代生存指南" class="headerlink" title="AIGC时代生存指南"></a>AIGC时代生存指南</h2><p>假如真的认为AIGC会把自己淘汰，我也思考了如何避免被AIGC所取代：</p><ul><li>提高思考和执行的效率：手速比设计Prompt和修改生成内容速度快即可。</li><li><strong>涌现</strong>出新的想法：跨领域思考并结合多个方案。</li><li>结合自己的专业知识和大模型的答案：兼听则明，相辅相成。</li><li>成为自己的生产力：在工作中通过引入AIGC提升自己的生产力或效率，从而让自己在团队中脱颖而出。早点下班，有更多时间学习。</li><li>学习大模型的生成方式：Think step by step. 通过多使用大模型，并模仿其回复风格，提升个人提问及表达能力。</li></ul><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>我将这篇文章发给了GPT3.5，并提问【我写了一篇AIGC的思考，请问有哪些表达问题】。</p><p>以下是它的建议：</p><p>在你的AIGC思考中，可能存在一些表达问题：</p><ol><li><p><strong>缺乏具体例子或案例支持</strong>：提到了使用AIGC解决问题，但没有具体举例说明，会让读者难以理解。</p></li><li><p><strong>逻辑结构不够清晰</strong>：在描述AIGC与人类工作关系时，可能需要更清晰地阐述两者之间的区别和互补关系。</p></li><li><p><strong>术语使用不一致</strong>：在描述AIGC如何入门时，提到了“大模型”，但并未明确解释这个术语，可能会让读者困惑。</p></li><li><p><strong>缺少数据支撑</strong>：在讨论AIGC如何提高生产力时，没有提供具体数据或案例来支持结论，影响说服力。</p></li><li><p><strong>语言表达不够简洁</strong>：有些地方表达较为冗长，可以考虑简化语言，提高可读性。</p></li></ol><p>通过修正这些问题，可以让你的AIGC思考更加清晰和易懂。</p><p>原文我就不修改了，但相信我接受并学习了GPT的建议之后，能够更好地输出接下来的文章。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;我一般使用AIGC解决超出我能力或是知识领域的问题，例如：绘制视频封面、解决安卓开发问题、使用某个冷门包、编写量化交易策略、分析金融数据、撰</summary>
      
    
    
    
    <category term="aigc" scheme="https://project256.com/categories/aigc/"/>
    
    
    <category term="大模型" scheme="https://project256.com/tags/%E5%A4%A7%E6%A8%A1%E5%9E%8B/"/>
    
  </entry>
  
  <entry>
    <title>FrontEndOnlineJudge——面向前端开发者的在线评测系统</title>
    <link href="https://project256.com/project/FrontEndOnlineJudge%E2%80%94%E2%80%94%E9%9D%A2%E5%90%91%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91%E8%80%85%E7%9A%84%E5%9C%A8%E7%BA%BF%E8%AF%84%E6%B5%8B%E7%B3%BB%E7%BB%9F/"/>
    <id>https://project256.com/project/FrontEndOnlineJudge%E2%80%94%E2%80%94%E9%9D%A2%E5%90%91%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91%E8%80%85%E7%9A%84%E5%9C%A8%E7%BA%BF%E8%AF%84%E6%B5%8B%E7%B3%BB%E7%BB%9F/</id>
    <published>2024-03-23T02:45:57.000Z</published>
    <updated>2026-03-29T09:06:11.642Z</updated>
    
    <content type="html"><![CDATA[<p>为了向前端开发者提供前端程序的在线评测服务，本文提出了一套面向前端开发者的在线评测系统，其能够在服务器环境中自动化渲染、执行、调度和评测前端程序的运行结果正确或错误。</p><p>在线评测系统是一种通过执行测试数据，自动评测程序的运行结果正确或错误，为使用者提供提升程序设计技能的实践平台。</p><p>大多数在线评测系统的目标用户是后端开发者，仅能够评测运行于服务器环境的后端程序语言，缺少评测前端程序语言的功能。前端程序的正确性仍然需要人工在浏览器环境评测，且评测结果具有一定的主观性。</p><h2 id="体验-不保证体验服务器稳定"><a href="#体验-不保证体验服务器稳定" class="headerlink" title="体验 (不保证体验服务器稳定)"></a>体验 <em>(不保证体验服务器稳定)</em></h2><p><a href="https://oj.project256.com/login" target="_blank" rel="noopener">https://oj.project256.com/login</a></p><p>账号:test</p><p>密码:123456</p><h2 id="代码仓库"><a href="#代码仓库" class="headerlink" title="代码仓库"></a>代码仓库</h2><p>本系统主要分为三个子项目</p><p><a href="https://github.com/SUTFutureCoder/FrontEndOJFrontEnd" target="_blank" rel="noopener">前端页面</a> 基于Vue NPM WebSocket</p><p><a href="https://github.com/SUTFutureCoder/FrontEndOJGolang" target="_blank" rel="noopener">后端接口</a> 基于Golang MySQL WebSocket</p><p><a href="https://github.com/SUTFutureCoder/FrontEndOJGolang" target="_blank" rel="noopener">评测核心</a> 基于Golang ChromeDP WebSocket</p><h2 id="运行方法"><a href="#运行方法" class="headerlink" title="运行方法"></a>运行方法</h2><h3 id="安装部署"><a href="#安装部署" class="headerlink" title="安装部署"></a>安装部署</h3><p>请参考DockerFile</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">ENV PATH=<span class="string">"/root/anaconda3/bin:<span class="variable">$&#123;PATH&#125;</span>"</span></span><br><span class="line">ENV GOPROXY https://goproxy.io</span><br><span class="line">ENV GO111MODULE on</span><br><span class="line">WORKDIR /root</span><br><span class="line">RUN apt-get install -y wget</span><br><span class="line"><span class="comment"># install anaconda3 pytorch</span></span><br><span class="line">RUN wget http://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-2021.05-Linux-x86_64.sh</span><br><span class="line">RUN /bin/bash Anaconda3-2021.05-Linux-x86_64.sh -b</span><br><span class="line">RUN conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/</span><br><span class="line">RUN conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/</span><br><span class="line">RUN conda install pytorch -c pytorch</span><br><span class="line">RUN conda install torchvision -c pytorch</span><br><span class="line">RUN conda install ipython</span><br><span class="line">RUN conda install matplotlib pandas seaborn scipy numpy</span><br><span class="line"><span class="comment"># install google-chrome</span></span><br><span class="line">RUN apt-get update</span><br><span class="line">RUN apt-get -f install -y</span><br><span class="line">RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb</span><br><span class="line">RUN apt install -y ./google-chrome-stable_current_amd64.deb</span><br><span class="line"><span class="comment"># run golang</span></span><br><span class="line">COPY . /root/go/FrontEndOJGolang</span><br><span class="line">WORKDIR /root/go/FrontEndOJGolang</span><br><span class="line">RUN go run main.go</span><br></pre></td></tr></table></figure><h3 id="MySQL结构"><a href="#MySQL结构" class="headerlink" title="MySQL结构"></a>MySQL结构</h3><p>导入 caroline.sql</p><h3 id="后端接口配置"><a href="#后端接口配置" class="headerlink" title="后端接口配置"></a>后端接口配置</h3><p>conf&#x2F;app.ini</p><h3 id="评测核心配置"><a href="#评测核心配置" class="headerlink" title="评测核心配置"></a>评测核心配置</h3><p>conf&#x2F;judger.ini</p><h3 id="默认密码"><a href="#默认密码" class="headerlink" title="默认密码"></a>默认密码</h3><h4 id="用户名"><a href="#用户名" class="headerlink" title="用户名"></a>用户名</h4><p>管理员</p><h4 id="密码"><a href="#密码" class="headerlink" title="密码"></a>密码</h4><p>654321</p><h2 id="效果预览"><a href="#效果预览" class="headerlink" title="效果预览"></a>效果预览</h2><h3 id="题目列表"><a href="#题目列表" class="headerlink" title="题目列表"></a>题目列表</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/image40.png" alt="题目列表" title="">                </div>                <div class="image-caption">题目列表</div>            </figure><h3 id="题目浏览及提交"><a href="#题目浏览及提交" class="headerlink" title="题目浏览及提交"></a>题目浏览及提交</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/image41.png" alt="题目浏览及提交" title="">                </div>                <div class="image-caption">题目浏览及提交</div>            </figure><h3 id="提交结果"><a href="#提交结果" class="headerlink" title="提交结果"></a>提交结果</h3><p>使用WebSocket实现实时通信</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/image42.png" alt="提交结果界面" title="">                </div>                <div class="image-caption">提交结果界面</div>            </figure><h3 id="界面相似度对比"><a href="#界面相似度对比" class="headerlink" title="界面相似度对比"></a>界面相似度对比</h3><p>基于ResNet对比界面相似度</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/image43.png" alt="界面相似度对比" title="">                </div>                <div class="image-caption">界面相似度对比</div>            </figure><h3 id="竞赛列表"><a href="#竞赛列表" class="headerlink" title="竞赛列表"></a>竞赛列表</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/image44.png" alt="竞赛列表界面" title="">                </div>                <div class="image-caption">竞赛列表界面</div>            </figure><h3 id="竞赛榜单"><a href="#竞赛榜单" class="headerlink" title="竞赛榜单"></a>竞赛榜单</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/image45.png" alt="竞赛榜单界面" title="">                </div>                <div class="image-caption">竞赛榜单界面</div>            </figure><h3 id="个人信息面板"><a href="#个人信息面板" class="headerlink" title="个人信息面板"></a>个人信息面板</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/image46.png" alt="个人信息面板" title="">                </div>                <div class="image-caption">个人信息面板</div>            </figure><h3 id="题目配置界面"><a href="#题目配置界面" class="headerlink" title="题目配置界面"></a>题目配置界面</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/image48.png" alt="题目配置界面" title="">                </div>                <div class="image-caption">题目配置界面</div>            </figure><h2 id="代码流程"><a href="#代码流程" class="headerlink" title="代码流程"></a>代码流程</h2><h3 id="代码用例图"><a href="#代码用例图" class="headerlink" title="代码用例图"></a>代码用例图</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/image21.png" alt="代码用例图" title="">                </div>                <div class="image-caption">代码用例图</div>            </figure><h3 id="系统功能结构图"><a href="#系统功能结构图" class="headerlink" title="系统功能结构图"></a>系统功能结构图</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/image22.png" alt="系统功能结构图" title="">                </div>                <div class="image-caption">系统功能结构图</div>            </figure><h3 id="E-R图"><a href="#E-R图" class="headerlink" title="E-R图"></a>E-R图</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/image25.png" alt="E-R图" title="">                </div>                <div class="image-caption">E-R图</div>            </figure><h3 id="活动图"><a href="#活动图" class="headerlink" title="活动图"></a>活动图</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/image30.png" alt="核心活动图" title="">                </div>                <div class="image-caption">核心活动图</div>            </figure><h3 id="评测流程图"><a href="#评测流程图" class="headerlink" title="评测流程图"></a>评测流程图</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/image31.png" alt="核心流程图" title="">                </div>                <div class="image-caption">核心流程图</div>            </figure><h3 id="延迟评测流程图"><a href="#延迟评测流程图" class="headerlink" title="延迟评测流程图"></a>延迟评测流程图</h3><p>当需要评测动画或是动态效果，需要等待一定时间再执行时，系统支持延迟一定时间再评测。</p><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/image34.png" alt="延迟评测核心流程图" title="">                </div>                <div class="image-caption">延迟评测核心流程图</div>            </figure><h3 id="界面相似度对比流程图"><a href="#界面相似度对比流程图" class="headerlink" title="界面相似度对比流程图"></a>界面相似度对比流程图</h3><figure class="image-bubble">                <div class="img-lightbox">                    <div class="overlay"></div>                    <img src="https://project256.oss-cn-beijing.aliyuncs.com/uPic/image38.png" alt="界面相似度对比流程图" title="">                </div>                <div class="image-caption">界面相似度对比流程图</div>            </figure><h2 id="说明"><a href="#说明" class="headerlink" title="说明"></a>说明</h2><h3 id="关于使用效果"><a href="#关于使用效果" class="headerlink" title="关于使用效果"></a>关于使用效果</h3><p>此系统为作者攻读硕士学位所设计，时间较为久远，暂不保证使用效果，可供代码参考。</p><h3 id="关于名字"><a href="#关于名字" class="headerlink" title="关于名字"></a>关于名字</h3><p>作者当时重玩Protal2上头，所以起了Caroline的名字作为项目代号，测试也用lab指代。更多请见代码和数据库结构导出文件。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;为了向前端开发者提供前端程序的在线评测服务，本文提出了一套面向前端开发者的在线评测系统，其能够在服务器环境中自动化渲染、执行、调度和评测前端程序的运行结果正确或错误。&lt;/p&gt;
&lt;p&gt;在线评测系统是一种通过执行测试数据，自动评测程序的运行结果正确或错误，为使用者提供提升程序设</summary>
      
    
    
    
    <category term="project" scheme="https://project256.com/categories/project/"/>
    
    
    <category term="创意工坊" scheme="https://project256.com/tags/%E5%88%9B%E6%84%8F%E5%B7%A5%E5%9D%8A/"/>
    
  </entry>
  
  <entry>
    <title>SSE(Server-Sent Event)实践与避坑</title>
    <link href="https://project256.com/JAVA/SSE-Server-Sent-Event-%E5%AE%9E%E8%B7%B5%E4%B8%8E%E9%81%BF%E5%9D%91/"/>
    <id>https://project256.com/JAVA/SSE-Server-Sent-Event-%E5%AE%9E%E8%B7%B5%E4%B8%8E%E9%81%BF%E5%9D%91/</id>
    <published>2024-02-23T14:38:05.000Z</published>
    <updated>2026-03-29T09:06:11.642Z</updated>
    
    <content type="html"><![CDATA[<p>最近搭了个大模型对话界面，但传统的请求方式只能等大模型全部输出之后再传输至前端，导致使用者以为界面或接口卡住了。</p><p>另外由于是微服务无状态，使用WebSocket做长连接还得考虑重连等逻辑，而且引入了复杂度。SSE默认支持断线重连，基于HTTP协议，传输文本信息轻量简单。</p><p>在实践过程中也遇到了一些坑，请阅读下方代码注释。</p><h2 id="JAVA"><a href="#JAVA" class="headerlink" title="JAVA"></a>JAVA</h2><p>遇到问题：</p><ul><li>Nginx缓存消息，导致消息无法流式输出。需要关闭Nginx缓存，直接推流。</li><li><code>new SseEmitter()</code>，默认30秒断连，需要指定<code>new SseEmitter(0L)</code>即不限制消息推送时间。</li><li>SSE只能传输单行数据，且无法识别空格和换行，因此需要将空格和回车替换为占位符。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ResponseBody</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> SseEmitter <span class="title">handleSseRequest</span><span class="params">(HttpServletRequest request, HttpServletResponse response)</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 关闭Nginx缓存，直接推流</span></span><br><span class="line">    response.setHeader(<span class="string">"Cache-Control"</span>, <span class="string">"no-cache"</span>);</span><br><span class="line">    response.setHeader(<span class="string">"X-Accel-Buffering"</span>, <span class="string">"no"</span>);</span><br><span class="line">    <span class="comment">// 流式事件流响应</span></span><br><span class="line">    response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE);</span><br><span class="line">  <span class="comment">// 默认30秒断连，改为0则不限制</span></span><br><span class="line">    SseEmitter emitter = <span class="keyword">new</span> SseEmitter(<span class="number">0L</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 每100毫秒推送一个消息，总共30个消息</span></span><br><span class="line">    Flowable.interval(<span class="number">100</span>, TimeUnit.MILLISECONDS)</span><br><span class="line">            .take(<span class="number">30</span>)</span><br><span class="line">            .map(i -&gt; <span class="string">"SSE COUNTER: "</span> + i + <span class="string">"&amp;#92n&amp;#92n"</span>)</span><br><span class="line">      .subscribe(</span><br><span class="line">              data -&gt; &#123;</span><br><span class="line">                  <span class="keyword">try</span> &#123;</span><br><span class="line">                      emitter.send(SseEmitter.event().data(data));</span><br><span class="line">                  &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                      emitter.completeWithError(e);</span><br><span class="line">                  &#125;</span><br><span class="line">              &#125;,</span><br><span class="line">              emitter::completeWithError,</span><br><span class="line">              emitter::complete</span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">return</span> emitter;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="前端"><a href="#前端" class="headerlink" title="前端"></a>前端</h2><p>遇到问题：</p><ul><li>由于EventSource只支持get方式，可以建立连接时通过字段传值。</li><li>SSE只能传输单行数据，且无法识别空格和换行，因此需要将空格和回车替换为占位符。</li><li>建议累加接收数据，并持续解析全量数据为HTML，实现ChatGPT即时解析，提升使用体感。</li></ul><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 由于EventSource只支持get方式，可以建立连接时通过字段传值</span></span><br><span class="line"><span class="keyword">let</span> eventSource = <span class="keyword">new</span> EventSource(target + <span class="string">"?content="</span> + <span class="built_in">encodeURIComponent</span>(content));</span><br><span class="line"><span class="keyword">let</span> sseContent = <span class="string">""</span></span><br><span class="line">eventSource.onmessage = <span class="function"><span class="keyword">function</span>(<span class="params">event</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> data = event.data</span><br><span class="line"></span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> ssedata = event.data</span><br><span class="line">    <span class="comment">// SSE只能传输单行数据，且无法识别空格和换行，因此需要将空格和回车替换为占位符</span></span><br><span class="line">    ssedata = ssedata.replaceAll(<span class="string">"&amp;#32;"</span>, <span class="string">" "</span>).replaceAll(<span class="string">"&amp;#92n"</span>, <span class="string">"\n"</span>)</span><br><span class="line">    <span class="comment">// 累加接收数据</span></span><br><span class="line">    sseContent += ssedata</span><br><span class="line">    <span class="comment">// 将Markdown数据持续解析为HTML，实现类似ChatGPT的返回实时解析，体感很好</span></span><br><span class="line">    <span class="built_in">document</span>.getElementById(<span class="string">"msg_"</span> + msgidx).innerHTML = convertMarkdownToHtml(sseContent)</span><br><span class="line">  &#125; <span class="keyword">catch</span>(e) &#123;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 锁定div滚动条到界面底端</span></span><br><span class="line">  <span class="keyword">let</span> objDiv = <span class="built_in">document</span>.getElementsByClassName(<span class="string">"lite-chatbox"</span>).item(<span class="number">0</span>);</span><br><span class="line">  objDiv.scrollTop = objDiv.scrollHeight;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">eventSource.onerror = <span class="function"><span class="keyword">function</span>(<span class="params">event</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 直接关闭即可，如网路异常则自动重连</span></span><br><span class="line">  eventSource.close()</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;最近搭了个大模型对话界面，但传统的请求方式只能等大模型全部输出之后再传输至前端，导致使用者以为界面或接口卡住了。&lt;/p&gt;
&lt;p&gt;另外由于是微服务无状态，使用WebSocket做长连接还得考虑重连等逻辑，而且引入了复杂度。SSE默认支持断线重连，基于HTTP协议，传输文本信息</summary>
      
    
    
    
    <category term="JAVA" scheme="https://project256.com/categories/JAVA/"/>
    
    
    <category term="JAVA" scheme="https://project256.com/tags/JAVA/"/>
    
  </entry>
  
</feed>
