告别千篇一律的 AI 输出,让每份内容都有独特的视觉风格 先回顾一下 v1.3 我们带来了短链接、ZIP 网站包、版本历史、标签收藏分类、多用户管理——即页解决了「分享之后怎么管理」的问题。 但很快,新的痛点浮出水面: AI 生成的页面长得都一样:同样的白底黑字,同样的默认样式——看起来就像「 AI 生成的」 想要特定风格?只能手动调 CSS:深色仪表板、学术报告、GitHub 风格……每次都要从头写 文件越来越多找不到:50 份文件靠翻列表,想找一份特定的数据分析报告?大海捞针 链接发出去了,不知道谁看过:分享出去的短链接,完全没有访问数据——发了个寂寞 AI 不会学习你的风格:每次生成内容都是全新开始,不会参考你之前满意的样式 v1.4 ,一次性解决这些问题。 🚀 五大核心更新 1️⃣ 内容模板市场:AI 生成的风格指南 这是 v1.4 最重磅的功能。内容模板市场让 AI 学会「参考风格」,而不是从零开始。 9 种场景分类,覆盖常见需求 : 场景 用途 📊 仪表板 数据可视化、监控面板 📝 报告 周报、分析报告、总结 📄 简历 个人简历、作品集 🎯 落地页 产品介绍、活动推广 📓 笔记 技术笔记、会议纪要 🎤 演示 演示文稿、PPT 风格 🃏 卡片 海报、信息卡片 ✉️ 邮件 邮件模板、Newsletter 📌 其他 不分类的内容 3 个系统内置模板 ,开箱即用: 深色数据仪表板 — 深色主题 + CSS Grid 布局 + 统计卡片 + 柱状图 项目周报 — 结构化 Markdown 周报模板 极简落地页 — 渐变 Hero + 特性卡片 + CTA 按钮 完整的模板管理 : 上传自定义模板:把你满意的作品变成模板,供下次参考 模板预览:HTML 直接 iframe 渲染,Markdown 实时预览 使用热度排序:最常用的模板排在最前 关键词搜索 + 场景筛选:3 秒找到目标模板 权限控制:私有模板仅自己可见,公开模板全员共享 MCP 协议深度集成 : AI 工具(如 Claude Code )可以直接调用模板市场: list_content_templates — 按场景/关键词搜索模板列表 get_content_template — 获取模板完整样例内容 AI 拿到模板后的工作流: 学习风格 → 生成风格一致但内容全新的作品 → 上传到即页 。 以前:AI 每次生成白底黑字的默认样式,手动调 CSS 花半小时 现在:指定模板风格,AI 自动学习配色和布局,一次生成到位 2️⃣ Markdown 渲染模板:4 种风格,随心切换 v1.4 引入了 Markdown 渲染模板系统,同一份 Markdown 可以用不同的视觉风格渲染: 模板 风格特点 默认模板 Inter 字体 + 深色背景,现代简洁 GitHub 风格 经典 GitHub Markdown 样式,自动适配深色/浅色模式 学术风格 Crimson Pro + Noto Serif SC ,衬线字体,适合论文和技术文档 深色专业 深色主题,高对比度,适合代码和技术内容 每个文件可以独立指定渲染模板——同一份 Markdown ,用学术模板渲染给领导看,用 GitHub 风格渲染给开发团队看。 以前:Markdown 渲染千篇一律,想换个风格?自己写 CSS 现在:选择模板,一键切换,4 种风格覆盖主流需求 3️⃣ 全文搜索:文件再也不怕找不到 基于 SQLite FTS5 的全文搜索引擎,让文件检索从「翻列表」进化到「搜内容」: 文件名搜索 :按文件名模糊匹配 内容搜索 :深入文件内容全文检索,支持中英文分词 组合筛选 :配合标签、分类一起使用,精准定位 以前:50 份文件,找到那份「 Q3 销售分析报告」靠肉眼 现在:搜「 Q3 销售」,瞬间定位,不管文件名叫什么 4️⃣ 链接访问统计:分享不再是「黑洞」 每次短链接被访问,自动记录统计数据: 访问量追踪 :每个文件的累计浏览次数 访问记录 :IP 哈希(隐私保护)+ User Agent + 访问时间 实时统计 :文件列表直接显示浏览量 以前:链接发出去了,到底有没有人看?不知道 现在:浏览量一目了然,访问记录随时可查 5️⃣ 健康检查端点 + 内部重构 GET /health 无鉴权探活端点 ,方便运维和容器编排: { "status": "ok", "db": "connected", "disk": "writable", "uptime": 86400, "version": "1.4.0" } 检查数据库连通性 + uploads 目录可写性 异常时返回 503 ,适合负载均衡健康检查 运行时长、版本信息一目了然 内部重构 : fs.sync → fs.promises 全面异步化,减少事件循环阻塞 now() 统一北京时间,告别 UTC 时区混乱 前端 app.js 从 1872 行拆分为 8 个 ES Module 文件,可维护性大幅提升 📊 MCP 协议扩展( v1.4 ) MCP Tools 从 15 个扩展到 17 个 ,新增: 工具 功能 list_content_templates 按场景/关键词搜索模板列表 get_content_template 获取模板完整样例内容,自动记录使用次数 同时 list_content_templates MCP tool 也已暴露给 AI 调用——这意味着 Claude Code 等 AI 工具可以直接查询模板市场,选择风格参考。 🎯 实战场景:模板市场改变工作流 场景一:产品经理 — 一句话生成风格统一的仪表板 「帮我做一份 Q3 用户增长数据仪表板,参考深色仪表板模板」 Claude Code 调用 get_content_template 获取深色仪表板样例 → 学习深色主题配色和 Grid 布局 → 生成全新的 Q3 数据仪表板 → 自动上传即页 → 短链接分享到管理群。 风格统一,内容全新。不是模板套用,是风格学习。 场景二:技术写作者 — 学术风格的技术报告 「写一份微服务架构设计方案,用学术模板风格」 AI 获取学术模板 → 学习衬线字体和论文排版 → 生成架构设计文档 → Markdown 渲染选择「学术风格」模板 → 公式用 KaTeX 完美展示,时序图用 Mermaid 渲染。 发给 CTO 的报告,看起来像正式论文,不是博客草稿。 场景三:市场团队 — 落地页秒出,不再等设计师 「做一个新产品发布落地页,参考极简落地页模板」 AI 学习渐变 Hero + 特性卡片布局 → 生成全新产品落地页 → ZIP 打包上传( HTML + CSS + 图片) → 短链接发给市场总监。 30 秒从想法到可预览页面,设计师只需在 AI 输出基础上微调。 场景四:用户上传自己的模板 — 让 AI 学会你的风格 你已经有一份满意的季度报告 HTML ?上传到模板市场,标记为「报告」场景。下次让 AI 生成报告时,它会自动参考你的风格——配色、字体、布局、组件样式,全部学习。 你的风格,就是 AI 的风格。 🌟 谁最适合升级? 如果你… v1.4 带给你的价值 AI 生成的页面风格单一 模板市场让 AI 学会风格参考,告别千篇一律 经常写 Markdown 文档 4 种渲染模板一键切换,不同场景不同风格 文件越来越多 全文搜索 + 访问统计,找文件不再靠翻列表 分享链接给客户/领导 访问统计让你知道内容有没有被看到 用 MCP 对接 AI 工具 17 个 Tools ,模板市场深度集成 运维即页服务 /health 端点 + 全面异步化,更稳定更可靠 📊 与常见方案对比( v1.4 更新版) 维度 即页 v1.4 GitHub Gist 语雀/Notion Vercel HTML 完整渲染 ✅ ❌ 仅代码 ❌ 仅嵌入 ✅ Markdown 渲染模板 ✅ 4 种风格 ❌ ⚠️ 固定 ❌ 内容模板市场 ✅ 9 种场景 ❌ ⚠️ 有限 ❌ 全文搜索 ✅ FTS5 ⚠️ 文件名 ✅ ⚠️ 文件名 访问统计 ✅ ❌ ⚠️ 有限 ⚠️ 需接入 自托管 ✅ Docker 一键 ❌ ❌ ❌ MCP 原生支持 ✅ 17 个 Tools ❌ ❌ ❌ ZIP 网站包上传 ✅ ❌ ❌ ⚠️ 需仓库 版本历史 ✅ ✅ Git 版本 ✅ ✅ Git 版本 短链接 ✅ /s/:key ✅ ✅ ⚠️ 需配置 上手门槛 🟢 极低 🟡 中 🟡 中 🔴 高 🏗️ 升级指南 已经部署了即页?升级非常简单: cd jpage git pull docker-compose up -d --build Migration Runner 会自动处理数据库升级,新增 content_templates 表、 templates 表、 file_contents_fts 虚拟表、 link_visits 表。 无需手动操作。 🔮 下一步规划 数据统计仪表盘 :存储使用、文件分布、上传趋势、访问热力图可视化 模板市场扩展 :更多内置模板,社区模板分享 实时协作预览 :多人同时查看同一文件 更多 AI 集成 :智能摘要、自动标签推荐、风格迁移 🎁 开源免费,MIT 协议 即页采用 MIT 开源协议, 免费商用、无功能限制 。 GitHub 仓库: https://github.com/code2rich/jpage 如果你也觉得「 AI 生成的精美内容值得被完整呈现」,欢迎 Star ⭐、提 Issue 、贡献代码。 v1.1 ,解决了「能不能分享」的问题。 v1.2 ,解决了「分享得好不好看」的问题。 v1.3 ,解决了「分享之后怎么管理」的问题。 v1.4 ,解决了「 AI 生成的内容怎么有风格」的问题。 内容模板市场让 AI 学会风格参考,渲染模板让同一份文档呈现不同面貌,全文搜索让文件触手可及,访问统计让分享不再盲目。 即页,从「即用即管理」进化为「即用即风格化」。 拖入文件,即刻成页。选择模板,即刻有风格。 即页,让 AI 生成的内容也有审美。 微信公众号: https://mp.weixin.qq.com/s/_iecRafkB0gOHzfDjgv3tA github: https://github.com/code2rich/jpage 即页 v1.0- v1.4 进化之路: http://36.138.227.105:8858/s/S-lddBMl
全文手打!希望佬们看的爽 许可: CC BY-NC-ND 4.0 我想说 正直六月,今天是高考的第三天,也是高考最后的一天。想到四年前我也走进了考场,心里有一种冲动,想给从高中毕业进入大学的同学写一些东西。于是我便写下了这篇不算文章的文章。本篇作为礼物送给大家。愿大家在大学与生活中不再迷茫。 由于没有人指导,我的大学生活其实算得上比较失败了。 通过不断的摸索,才慢慢有了个样。 见解浅薄,望海涵。 现在将时间拨回 2022 年。疫情几乎贯穿了我整个高中生涯。同时,新高考的政策也打了 22 届考生一个措手不及。新高考一卷让许多考生无法释怀。 不过这些对我来说并没有太大的影响。感谢我的高中母校,提供了海外本硕连读的机会,也感谢我的家庭的资金支持。 对我来说,其实在国内或者说在国外读没有什么差别。本质上只是换了一个新环境,可能也许有不适应的吧,但是也就只是语言和习惯不同。毕竟有些地方的方言也很难听懂。 可能国内的大学和国外的大学不太一样,但迷茫和痛苦都是一脉相承的。 先谈谈学校相关的 学会自由 可能我们从小听到大的,就是读了好的大学就好了。但事实真的是这样吗?大学真的自由了吗? 看起来像是的。对于我的学校来说:选课可以自己选,上课的时间也挺自由。如果没有考勤,或者说考试的话,也可以想不去就不去。或者翘课也不是不行。 同样,由于我们远离家庭,所以我们也很难被限制住。我们可以翘课,视 GPA 于无物;也可以参加竞赛和考证。在平时的日常中,我们也可以选择自己感兴趣的社团,买自己喜欢的东西,这些都没有太大的限制。 但不管我们是自由的堕落,还是痛苦的奋斗,这些都是我们自己可以选择的。 所以在选择之前,不妨先问问自己,到底想要成为什么样的人。 人都得为自己的行为负责。 学会提问 问自己 你想要成为什么样的人? 我不确定你们有没有想过,但在我高中的时候才开始真正的开始思考这个问题。 小时候,一些老师问我们想要成为什么样的人,我们可能回答作家、科学家等等。 但这些想法真的是经过我们思考后得出来的吗? 还是说我们只是从众,跟随大众的心理来为自己贴上一个标签呢? 这里我并不能给出建议。但问题的答案应该在你之后的人生中自行探索。 探索的过程就是逐步提问并进行解答的过程。 所以我们先来谈谈提问。 提出一个好的问题,能让你的生活和学习轻松很多很多。 原因 其实从小到大,我们都被要求提问。到了大学更是如此。在大学中可能会遇到教授,当一节课快结束后,他会问你有没有问题?真实的情况大概是整个班级都鸦雀无声,你也希望教授不点你。当教授看着沉默的整个班级后说:“没有问题,下课”之后,是不是整个人都放松了。 不会提问,大概也就是这几个原因。首先就是怕自己丢脸,觉得自己的问题可能大家都懂,你提出来,如果不会的话,别人会笑话你。还有一点就是,在高中的生活中,我们习惯于服从权威,而不是进行追问、质疑和讨论。而久而之,我们就养成了你说什么就是对的这个结果。当然还有一点就是不会提问。 解决办法 第一点其实很好解决,不要脸就可以了。事实上,当一节课下了之后,大部分的人是不会关心你在上课之中提了什么问题,说了什么的。你上学是为了自己而上,反正你不尴尬,尴尬的就是别人。或者你会想,他人知道,我不知道,我是不是无知啊?但上学就是为了解决这个问题的,知道别人不知道的东西。如果什么都知道,那为什么还要上学呢?保持一颗虚心求教的心。 第二点的话就比较困难了。由于我们在低年级的时候没有提问的环境。当到了大学课堂中,我们可能会因为害羞或者社恐导致提不出问题。这时只能靠自发的勇敢进行提问,也许这样能够解决一些问题。 第三点其实是最难解决的。但也不是没有解药。读书是一种方法,一些有用的文章、书籍可能会给我们答案。 可以参考 在这里我分享一下有用的材料吧。不说都是经典,但总归有点用处。 这个篇是我朋友好多年前分享给我看的——作为一个 hacker 应该如何提问? 原文是英文,这里找了个中文翻译的出来。 How-To-Ask-Questions-The-Smart-Way 简单来说,提问是需要智慧的。提问前,我们需要尽己所能的解答这个问题。当你穷尽了你的办法后,再转向他人提问。再就是需要考虑提问的地方。比如 Linux.do 上就更多的可以讨论 AI 相关的内容。在提问时,尽量问聪明的问题,包括你尝试过解决的办法,还有你现在的具体情况,告诉别人你知道的有什么。只有详细和诚恳,人们才会更乐意帮助你。 由于篇幅原因,在这里就不过多详细地讲解文章的内容了,有需要的可以自行观看。 提问与思考相辅相成,所以我在这里推荐一些书,可以看看。当然,尽信书不如无书。 不好看的就不看,没人规定书就是要读完。后面也会讲讲如何读书。 清晰思考 学会提问(原书第12版) 思考,快与慢 学会自学 困境 大学的学习大多都需要落到自学上。不管教授在课堂上讲得再好,我们的思路也是被牵着走的。即使我们在课堂上记了笔记,听了课,但这种毫不费力的方式并不能等于我们学会了。 同样国内的一些教授照着 PPT 进行练稿式的上课。这种行为很受学生所诟病。我们也很难改变什么,所以比较好的解决方式就是学会自学。 在《写作是门手艺》这本书里面用了一个例子,提到了听 100 门杀猪课能不能学会杀猪。 可能有些天赋异禀的人可以做到,但如果我们不靠实践和反馈,仅仅只依靠旁观来学习的话,是做不到的。 只看不做是很难真正学会的。 确认学什么 正如我之前提到过要学会提问,那么如何自学也可以进行分解。首先,我们想清楚我们要学什么。 然后将其分为一些清楚的问题。 举个例子吧,比如现在 AI 时代,我们都想要学习 AI。那么如何学习 AI 呢? 这个问题其实不是一个清楚有用的问题。如果是想做数据处理的话,可以这么提问:我如何使用 AI 生成 Python 代码,帮我将一份数据进行汇总整理,并输出一份有用的分析报告? 通过小而明确的问题,给出自己具体而可以执行的目标。而这些目标都成为了可以验证的动作。只有这样,你才能明确知道你想要学的是什么。 当然,首先你可以学习一下这个课程,学会如何学习。 如何学习:学习困难科目的实用思维方法 或者这本英文书 The science of learning 哪里学和怎么学 事实上,互联网已经有大量的资源供我们学习了,不管是 MIT 的公开课还是海内外各种的教程,都是免费给我们观看的。 Coursera | Courses, Professional Certificates, and Degrees Online MIT OpenCourseWare | Free Online Course Materials 反正我们感兴趣哪个方面就可以看哪个方面的东西,而且不一定说要完全看完。 如果真的要学习某一个具体的课程,我建议: 找一个入门的地图,对这整块的知识有一个梳理; 找一套核心的教材或者课程,用于深度挖掘其中的内涵; 做一些练习或者项目,来进行实践或操作; 最后,与他人多交流。 当我们对这本书或者你想学的这块知识有一个了解之后,你就可以进行逐步的探索。而在探索的过程中,我们应该记录自己的想法和一些问题,并进行汇总和思考。AI 可以与你进行对话和沟通,但不要一上来就直接扔给 AI,然后让其为你讲解。 总结是一个深入的脑力活动。它用于将你见过的内容和你的经验生活结合起来,并产生自己的想法。在这个过程中,我们锻炼了自己的思维和思考方式,并逐步的提升了我们的能力。 当我们进行总结和解释的时候,就是在训练把一件事情讲清楚的能力。 更进一步 学习的过程其实就是将知识内化的过程。但是如果没有输出,其实并不能体现自己真正学会了。除了知道知识,我们还要能清晰明确地表达出来。 这里的表达也没说非要我们表达得特别好。但是我们的能力会逐步地进步和发展,只有我们将过程、思考和想法记录下来,我们才会产生长期的记忆。 同样,就像 AI 有 human in the loop 一样。我们人也需要及时地获取外界的反馈和他人的指导。我们写作,然后发给他人看,他人会给我们一些评价。这些评价都是有益的反馈,这让我们能及时地调整动作,并持续地更新下去。 吾日三省吾身。其实一周自省一次就已经很了不起了。 学会相处与沟通 还有一个复杂的就是现实中的人际关系:我们有各自的室友,也有朋友,可能会谈恋爱,或者有一些小组合作,以及与教授的沟通等等。 关于说话的一切 这些在大学之前的生涯中其实很少见。我们在之前几乎是被要求要怎么样,而大学相当于一个小型的社会。我们在其中会遇到形形色色的人。可能性格不同,习惯不同,立场也不同,所以我们需要了解如何与他们相处。 愚蠢之人脸上还带着微笑,仿佛在做着世界上最自然的事情,他突然出现,粉碎了你的计划,破坏了你的平静,使你的工作和生活都变得错综复杂,让你失去金钱、时间、幽默、胃口以及生产力,而这一切都没有恶意,没有悔恨,也没有理由,单纯只是愚蠢。 人类愚蠢基本定律 看见他人,也看见自己。 如何了解一个人 很简单的一句忠告就是不要委屈自己。不喜欢的就是不喜欢,超出能力的事情就不办。没必要委曲求全,我们已经成为大人了,我们只需要对自己所做的事情负责就够了。 我们表达自己,同时尊重他人。 人有二三知己就已经很不错了,没必要和所有的人交朋友,点头之交也未尝不好。笔者的朋友都不多,但遇到事情是真上啊。 探索未来 当我们学会提问,可以进行自学,并能与他人友好地沟通和相处后,我们便可以着手大学的主线任务,也就是探索自己的未来。 入学时,你们可能不太清楚以后自己想要做什么,也不知道前路会怎么样。但在这里我想说的是,选择的专业不代表人生的方向确定了。真正感兴趣的东西,是在实习项目或者有趣的经历中慢慢意识到的。大学这几年,是为了给我们一个不断修正和尝试的机会。 当我们听完各行各业的讲座,参加过各种社团竞赛、科研项目,以及进行实习兼职等真正的社会活动后,我们才能了解到自己喜不喜欢,或者说擅不擅长。 大学给我们的是一个平台,我们没必要焦虑。我们没必要在几年中规划好接下来几十年的目标,我们只是需要掌握一种方向感。 比如我喜欢写一写代码,或者记录一下内容,我就参加了一些 GitHub 上的开源项目,同时也和朋友玩过 3D 建模等东西。在这些过程中,我会不断向自己发问:我对什么事情有兴趣?我愿意在哪些事情上投入?我现在的能力够不够? 在这些一次次的反问和行动中了解自己,继续提出问题并尝试寻找答案,然后探索自己的未来。 关于 计算机科学 计算机工程 电子工程 的一些资源 本人学的是 CS,CE 还有 EE,所以只有这一方面的资源,也简单讲一讲。 有能力的话,还是直接看英文原版书。有的翻译过来并不是翻译得很好,或者说不太适合英文下的语境。 CS 自学指南,这个也是经久不衰的一个自学的仓库,里面很多东西都写得特别好。但是我们未必有耐心完整地学完,如果你有兴趣的话,可以看一看。 CS自学指南 生活 学校的基本上就讲完了,也没什么要注意的。学习也好,考试也罢,都只是人生中不起眼的一环。学校提供的仅仅只是一个平台和一块敲门砖。我们应该将其视作为垫脚石,而真正的能力是由我们自身的身高所触及的。 大学不仅仅只是学习专业知识的地方。到了 18 岁之后,我们就得为自己的行为负责了。我们需要决定很多事情,我们可以选择努力,也可以选择摆烂;可以规律的生活,亦或是昼夜颠倒;我们可以把大学四年糊弄过去,或者说认真地对待它。 虽然看起来生活不那么重要,毕竟还是在学校,但我们怎么过的今天,我们的明天也会怎么过。 阅读与见识 读书。很痛苦。或者说看自己不喜欢的书会很痛苦。在前面提自学的时候,我讲到了要读书,但那个读书是关于学校的课本。而现在,他不应该仅仅只是为了考试,或者说是证书。 读书的作用是为了让我们见识更大的世界,见到更多的人。 学校的生活是一种评价体系,也就是说考虑成绩、绩点、排名、竞赛等等。这些内容当然也重要,但我们不能仅仅只考虑这些。仅仅只考虑这些,眼界就变得狭窄了。我们用这一套标准衡量自己,也去用同一套标准衡量别人。 我们的人生,不应该仅仅只是简历上的那几行字。 读书的意义之一是让我们知道世界上有很多种活法,也可以以各种的方式对一个问题进行思考。文学作品中。我们可以体会到他人的痛苦和爱;历史告诉我们,这个事情并不是今天才第一次发生。 同样,如果你感兴趣,也可以读社会学、心理学、哲学等等,这些体现了更多人和世界之间的复杂关系。 当然,书也没有那么神圣,有经典,也许是被吹捧的,也有不起眼,但是能让你受益良多的。我们只是增长一下自己的见识。不好看的书可以不看,读不下去的书也可以放一放再读,或者不读。我们读书也不是为了完成某一种任务而去读它,而是因为我们想要读它。 除了读书,我们还可以看各种不同风格的电影、播客、纪录片,或者说听一下一些教授的讲座,然后与朋友一起旅行,和不认识的人短暂地聊一聊天。这些都是扩大见识的方式。 我们也没必要比个见识的高低,不懂就虚心请教,下一次懂了就行,毕竟问题不是考题。 问题和人生没有标准答案,我们要做的只是尽量让它不至于只剩下一种答案。 身体健康 身体对于工科来说还是很重要的,毕竟一坐就是一天。现在我们的身体确实可以熬夜不运动,好像也没有太大的关系。第二天醒来的时候,也感觉还好。 但是我们不能持续地损耗它。大学中一种很常见的状态就是晚上不睡,早上不起。我们能点外卖就点外卖,能坐着就不站着,能躺着就不坐着。 这种情况看起来是生活习惯的问题,但是同样它影响着个人的精神状态、学习效率和情绪的稳定性。 同样也不是告诉你们一定要自律。比如早上每天 6 点钟就起床跑步。这种要求也不太现实。 但是我想说的是: 尽量按时吃饭,少熬夜,偶尔进行运动。 有体检的话就定期体检。 累了就睡,身体有问题就及时反应。 虽然这部分看起来是废话,也说的东西不多,但是它真正能影响我们的身体和生活。 身体和健康是第一位,如果没有了一个好的身体和健康,学习也好,生活也罢,后续都是空中楼阁。 金钱理财 然后再就是消费和理财。到了大学,基本上每个月会收到一笔家长的生活费。如果比较困难的学生,还需要出去兼职。但我想说的是,这是自由,但是也是一种责任。 我们可以在月初进行冲动消费,然后月底啃泡面吃土。虽然买东西的时候很快乐,但是买完过了一段时间后又开始后悔。同样在大学中,你可能会遇到比较喜欢攀比的人,爱慕虚荣的人。这是我们需要自省,也不要像某一些人一样去碰那些不该碰的借贷。 合理的消费,明确自己的需求是很重要的。作为一个智人,我们每天的任务只需要找到 2000 千卡,并找到一个遮风挡雨的位置就可以了。好吧,这你说得太无欲无求了。 我也知道,有时候看见别人有,自己也想有,看见别人买,自己也想买。这种想法。但是这种消费本身并不可耻,为我们自己喜欢的东西消费也是没有问题的。但我们首先需要明确一点:它是不是我真正需要的,还是我用来缓解一些焦虑的? 所以在这里我推荐一本书。 制造消费者 因此,看似是人们选择着商品、商品给人们带来愉悦,但实际上这一切都服从着一种集体的社会逻辑,人并不是真的因为内在需求而消费,他们是被符号牵着鼻子走。为了维持自己的地位、为了守住他所属的阶级,他必须遵守这门消费的法则。 如果有时间,也可以试着记账,哪怕是简单地记录一下,了解一下自己的钱到底是开销在哪儿了,比如吃饭、娱乐等等。这也比完全没有概念要好得多。当你进行记录之后,你就可以收到反馈,了解到自己真正哪些是花得值得的,哪些是不太值得的。 我们不是说不花钱,但我们需要有着一个比较基本的金钱的意识。卖东西是为自己而买。 情绪管理 现代社会,不管是 ADHD 还是 FOMO,这种类型的症状已经屡见不鲜,我们还是需要关心一下自己的身心健康。 也许我们进大学之后会发现自己没有想象中的那么快乐,毕竟我们高中时期以为自己上了大学就好了,但是到了大学我们又会有新的焦虑和问题。 首先一点就是学习嘛,绩点的焦虑。 选一个好专业的焦虑。 与他人社交的焦虑。 再就是快毕业了就业的焦虑。 还有的一些就是我们迷茫,看见他人好像都在进步,我们自己也不知道干什么。 有时候自己是一个人,但是别人有很多朋友。或者说,看见别人拿奖,有实习科研,自己却不知道今天在干什么。 不过这些都是正常现象,上大学哪有不疯的。 人不可能从高中走到大学一瞬间就变得成熟,我们只是换了一个方法和一个年纪来面对新问题。 而且,一次的成功与失败不代表整个人生的失败。就算结束了一段关系,也不代表怎么样了。 在这里建议的处理方法是:找个本子或者电子工具记录下来,写下自己为什么难受,这个事情到底是什么,以及有没有解决办法… 不行就吃一顿好的,然后睡一觉,或者出去走走。 学会独处 接下来就是学会独处。我们在大学中会遇到各种各样的人,教授或者说社会上的一些人。尽管我们认识的人会很多,但是大学可能是我们第一次感受到真正孤独的地方。 在大学中,你会发现很多事情不是都有人陪着你做。有可能你要一个人吃饭,一个人上课,一个人去图书馆,一个人回宿舍,因为你各自身边的人都有他自己的安排,不会就围着你转。 你可能会不习惯,也可能会认为自己是不合群,但一个人他并不丢人。 独处只是生活的一个小部分,一个人吃饭是不奇怪的,我一个人看电影也是没问题的,一个人散步、读书、学习或者发呆都没什么问题,也许还舒服一些。 我们也没必要显得自己很合群,强迫自己融入自己不应该融入或者融入得不舒服的关系,也不需要为了避免孤独而强行的抓住一个人和他同行。 一个良好的关系就应该是让他人感到舒服,和他让你也感到舒服,而不是感觉到疲惫。学会独处的情况应该是学会与自己相处。在独处中,我们能了解到自己的喜好,能忍受什么,不能接受什么,同时也有机会探索到自己未来的一些答案。 有二三知己已经很好。 家庭关系 再说家庭。 当离开家到了大学之后,我们和父母之间的关系会发生变化。正所谓距离产生美。一些矛盾可能由于距离而逐渐的减少,但同样也会产生更多新的问题。 一些家长可能仍然想知道你每天做了什么,你却觉得自己已经是成年人了,不想被什么都管。或者说报喜不报忧。你不想让家里的人担心,遇到的困难你也不说,仅仅只是在要钱的时候联系家里。 这个过程需要找到一个平衡。虽然我们一定需要独立,而且大学生已经不是小孩子了,很多事情应该我们自己去行动,一些决定也要为自己的行为负责。但独立不代表断开联系,也不代表什么都不说。 我们可以听听家长的意见,但是最后仍是我们自己的决定。 无论如何,我们都要学会爱家人,也要学会成为自己。 认识自己 不管怎么说,最后还是要回到自己。 前面已经谈到很多,比如阅读、见识、身体、金钱、情绪、家庭、独处等等等等。这些看起来是生活的不同方面,但是其实本质的问题只是一个。 我们要如何与自己相处,又要如何过好自己的生活? 你要知道, 在大学中我们会听到各种不同的声音,有人会告诉你要考研,有人会让你就业,有人会告诉你要去大厂,有人告诉你要去读博、考公等等等等,还有人会觉得你学的专业没有前途,或者说某个方向更为赚钱。 或者我这篇文章说你要怎么怎么样。 这些声音都可以听,但是不能全信。 我们的人生是自己的,我们最后过的这一生也是为自己而活。 这些需要我们慢慢地进行探索和向自己发问。Human in the loop! AI AI 这个是离不开的话题,单开一个大主题来说一下吧。 2022 年末的 ChatGPT 出现,好歹我还有一学期没有使用过 AI,所以还是很幸运的。 为什么我这么说?因为用过 AI 和没有用 AI 的那个思路,它是完全不一致的。保留一些习惯,能让我在没有 AI 的时候也能深入地进行思考。 现在的 AI 已经进入了学习、写作、编程、翻译、检索等一系列甚至很多日常的生活场景,而学术不端和 AI 有着密不可分的关系。 AI 的作用很大,它能: 帮你解释一个不懂的概念; 整理一篇文章的结构; 帮你写一些脚本的代码; 查一下、检测一下你的英文表达; 在你没有思路的时候给你一些启发。 确实这些都减少了时间。但有一个问题,我们把节省的时间又拿去做什么呢? 在 AI 的能力已经很不错了,当我们把作业丢给 AI,它也能替我们完成 90% 甚至 100%。你把文章给 AI,它也能替你思考。我们跳过了学习的过程,这是很危险的。 不过也情有可原,毕竟我们在大学之前的学习生活中,追求的只是一个答案,或者说追求高分。而生活和学习,它不仅仅是为了一个答案。我们形成了自己的判断力,才能知道 AI 给的建议是不是符合你的情况,它用了什么逻辑。还有,你想得到一个什么样的结果。 所以我认为 AI 它仅仅只是一个工具,它是陪练,而不能将其看为代打。 说到底,AI 用得好不好,也取决于我们会不会提问,会不会进行一些判断,还有会不会在过程中进行学习。 一个不会提问的人,我们用 AI 回答的往往也是很浅薄的答案。如果一个人没有判断,我们也很容易把错误的结论当成正确的。 用与不用,向自己发问吧? 多问,多想是好事。 最后 最后想说,生活不是简历,人生也不是绩点。 愿,大家好好吃饭,好好睡觉,好好看看这个世界。 7 个帖子 - 5 位参与者 阅读完整话题
doi是10.1123/ijsc.2025-0064 标题是Online Public Opinion Assessment for Sport Events Using Text Sentiment Analysis: A Case Study of Chinese Weibo Responses to the Summer Olympic Games in Tokyo 找半天找不到 4 个帖子 - 3 位参与者 阅读完整话题
公告全文如下: 尊敬的客户: 感谢您一直以来对长桥的支持与信任。为落实中国证监会 2 年集中整治期的相关行业监管要求,推动中国大陆跨境证券业务规范发展,长桥将对存量投资者的账户在中国大陆境内的服务进行相应调整。 现为您说明如下: 在中国大陆境内交易服务:暂停股票等所有品种的新开仓、加仓交易,仅支持卖出、平仓操作; 在中国大陆境内资金划转服务:暂停资金转入,转出功能保持正常,全力保障您的资金安全。 以上安排自北京时间 2026 年 6 月 12 日起生效。请您放心,本次调整不会影响为存量投资者在中国大陆境外提供服务,也不影响全体客户现有资产安全,客户可正常查询账户、持有及卖出已有持仓。 感谢您的理解与支持,如有任何疑问,欢迎随时联系长桥客服团队。 长桥 2026 年 6 月 3 日 wallstreetcn.com 长桥证券:将调整存量投资者的账户在中国大陆境内的服务 长桥证券发布通知称,为落实中国证监会2年集中整治期的相关行业监管要求,推动中国大陆跨境证券业务规范发展,长桥将对存量投资者的账户在中国大陆境内的服务进行相应调整。现为您说明如下: 1.在中国大陆境内交易服务:暂停股票等所有品种的新开仓、加仓交易,仅支持卖出、平仓操作; 2.在中国大陆境内资金划转服务:暂停资金转入,转出功能保持正常,全力保障您的资金安全。 longbridge.cn 长桥 (Longbridge) 转自encmasuta 1 个帖子 - 1 位参与者 阅读完整话题
我们的团队做了一个论文阅读软件 EasyReader: 这款工具的亮点: 1 ,全文翻译,不是普通机翻,是 llm 大模型 AI 翻译,对学术语言风格进行了调校。可以选择你的学科,这样翻译更准确! 2 ,排版还原,公式图表不乱码;基本上( 90%)常见的论文的排版都是完美还原。 3 ,大模型辅助阅读:一键生成思维导图、论文导读、提取 LaTeX 公式等 4 ,跨库搜文献 网址: https://www.easyreader.com.cn/ 目前我们正在不断完善软件,还有些 bug 。欢迎提供对于软件的使用建议。 另外,从 1.1.9 开始,已经支持 macos 系统 分享资源: 目前注册后会有一个免费的使用额度。如果您愿意对软件进行测评,请给我发消息,我们会赠送跟多的资源包。
测试机器为自费购买,全文无AFF,请放心食用 今天测BV的NAT-KVM系列,定位应该算是低价v6机,本次机器可用区选在SG 测试配置 NAT-1024-KVM 起价 $14.00 USD/每年 2 CPU Core(s) (Fair Share) 1024MB RAM 12GB SSD 20 IPv4 NAT Ports,IPv4 is blocked by GFW by defaults 1 /64 |德国/土耳其机房: /80 IPv6 Addresses 750GB @500Mbps per month KVM Virtualization 3 Snapshots 1 Backup 可用区: 新加坡 香港 日本东京(Standard) 台湾 德国萨克森州 土耳其伊斯坦布尔 巴基斯坦伊斯兰堡 尼日利亚拉各斯 荷兰德龙滕市 乌克兰文尼察 意大利米兰 网络质量 V4只有移动快乐,电信联通基本300ms+,v6的话电信移动快乐,联通200ms左右,略好于v4,国际方向还行,SG → SG/HK/TKO/悉尼基本接收都是0重传,但是HK、SG的发送重传很严重,v6的话SG本地是可以跑满500Mbps的,HK/TKO也有400Mbps左右,但是v4到HK和SG重传很严重 至于到欧美之类的话,延迟180ms左右,而且速度基本只到120Mbps左右,但是一分钱一分货,这种价格也不奢求什么了。500Mbps口子,750GB也够用了 IP质量 v4显然是被玩烂了才给NAT鸡用的,但是比预想中好很多, ipapi还说是家宽 常见库基本都被标记了代理,但是没被标记VPN,感觉有点奇怪的样子,解锁基本全解,但是大部分是DNS解锁 v6标记比较少,但是解锁还不如v4,除了reddit和ChatGPT是DNS解锁,其它全掉 机器性能 CPU是AMD的EPYC 7R12,性能只能说拉完了,估计是大大的超售。还是那句话, 一分钱一分货 。磁盘小块读写表现比较差,大块读写还行。硬盘给了12G,基本就是拿来探针点亮全球和代理池/富强机器了。不过CPU FairShare,磁盘一样,不要长时间CPU高占用或者高IO,否则: 网络质量 BGP路由 V4回程 V6回程 V4质量 V6质量 1 个帖子 - 1 位参与者 阅读完整话题
游记全文: https://victor42.eth.limo/post/trip-to-xi-an/
游记全文: https://victor42.eth.limo/post/trip-to-xi-an/
游记全文: https://victor42.eth.limo/post/trip-to-xi-an/
游记全文: https://victor42.eth.limo/post/trip-to-xi-an/
实在懒得一个个点单词学英语了,借助plus东风,想着chrome网页全文翻译很好用,就做了一个类似的,目前支持epub格式,支持上游各种语言,下游可以范围为中文,还有文言文翻译为大白话,支持接入第三方API,自定义模型,实时页面翻译。完全按照我的需求来做的,源码在这: GitHub - lad6mntzezhbll4d-dev/boox-ai-reader · GitHub 1 个帖子 - 1 位参与者 阅读完整话题
pinyin.hookapp.top 拼音首字母全文检索 是从我自己的需求触发来做的,比如有不知道的缩写或者变量名反查意义 2 个帖子 - 1 位参与者 阅读完整话题
输入拼音简写就会显示所有可能的中文词汇 包含 500 多万的中文词汇 可以用来查不知道的缩写或者反查拼音变量名 网站地址: https://pinyin.hookapp.top/
输入拼音简写就会显示所有可能的中文词汇 包含 500 多万的中文词汇 可以用来查不知道的缩写或者反查拼音变量名 网站地址: https://pinyin.hookapp.top/
自己折腾了一个 Tampermonkey 脚本,功能比较实用: 划词后出现 红点 ,点击直接翻译选中文字 旁边有 蓝点 ,点击可 全文翻译 全文翻译是 直接替换页面文本节点 支持 恢复原文 支持 多个 API 自动故障回退 点页面其它地方,红蓝点会自动消失 适合拿来自定义接第三方翻译/LLM API,用来看英文网页挺方便。 希望对大家有用,不用折腾沉浸式翻译了,这个就是沉浸式,简单方便好用。好用点赞啊。 // ==UserScript== // @name 划词翻译 + 全文翻译(红点/蓝点/恢复原文/自动回退) // @name:en Selection Translate + Full Page Translate // @namespace http://tampermonkey.net/ // @version 3.2 // @description 红点翻译划词,蓝点全文翻译。直接替换页面文本节点,支持恢复原文,支持多API自动故障回退。 // @author AI Assistant // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @connect * // @license MIT // ==/UserScript== (function () { 'use strict'; if (window.__AI_TRANSLATOR_FULLPAGE_RUNNING__) { console.log('翻译脚本已在运行,本次加载被阻止。'); return; } window.__AI_TRANSLATOR_FULLPAGE_RUNNING__ = true; const API_PROVIDERS = [ { endpoint: 'https://YOUR_API_ENDPOINT_1/chat/completions', key: 'YOUR_API_KEY_1', model: 'gemini-translate-pro', prompt: '{text}', batchPrompt: `Translate the following multiple text segments into simplified Chinese. Rules: 1. Keep the numbering markers EXACTLY as they are. 2. Do not add explanations. 3. Do not omit any item. 4. Preserve formatting as much as possible. 5. Output ONLY the translated result in the same marker format. {text}` }, { endpoint: 'https://YOUR_API_ENDPOINT_2/chat/completions', key: 'YOUR_API_KEY_2', model: 'gemini-2.5-flash', prompt: `Translate the following text into simplified Chinese, keeping the original formatting as much as possible. Provide only the translated content, without any extra explanations or introductory phrases.\n\nText to translate:\n"""\n{text}\n"""`, batchPrompt: `Translate the following multiple text segments into simplified Chinese. Rules: 1. Keep the numbering markers EXACTLY as they are. 2. Do not add explanations. 3. Do not omit any item. 4. Preserve formatting as much as possible. 5. Output ONLY the translated result in the same marker format. {text}` } ]; const REQUEST_TIMEOUT = 45000; const BATCH_MAX_ITEMS = 12; const BATCH_MAX_CHARS = 3200; const MIN_TEXT_LENGTH = 2; const MAIN_CONTENT_SELECTORS = [ 'article', 'main', '[role="main"]', '.article', '.post', '.entry-content', '.content', '.markdown-body', '.doc-content', '.main-content' ]; const EXCLUDED_TAGS = new Set([ 'SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'OPTION', 'CODE', 'PRE', 'KBD', 'SAMP', 'SVG', 'CANVAS' ]); const INLINE_UI_EXCLUDE_SELECTORS = [ '#ai-translator-btn', '#ai-fullpage-btn', '#ai-translator-popup', '#ai-translate-toolbar' ]; const isConfigIncomplete = API_PROVIDERS.some(p => p.endpoint.includes('YOUR_API_ENDPOINT') || p.key.includes('YOUR_API_KEY') ); if (isConfigIncomplete) { alert('【划词/全文翻译脚本】\n\n请先在脚本顶部“用户配置区”填入你的 API endpoint 和 API key。'); return; } GM_addStyle(` #ai-translator-btn, #ai-fullpage-btn { position: absolute; width: 10px; height: 10px; border-radius: 50%; cursor: pointer; z-index: 999999; box-shadow: 0 2px 6px rgba(0,0,0,0.25); border: 2px solid white; transform: translate(-50%, -50%); opacity: 0.72; transition: transform 0.2s ease, opacity 0.2s ease; } #ai-translator-btn:hover, #ai-fullpage-btn:hover { transform: translate(-50%, -50%) scale(1.2); opacity: 1; } #ai-translator-btn { background-color: #ff4757; } #ai-fullpage-btn { background-color: #1e90ff; } #ai-translator-popup { position: absolute; z-index: 999998; background-color: #FFFBEF; border: 1px solid #EAEAEA; border-radius: 8px; box-shadow: 0 5px 16px rgba(0, 0, 0, 0.14); padding: 14px 16px; max-width: 460px; min-width: 220px; font-size: 14px; line-height: 1.65; color: #333; text-align: left; white-space: pre-wrap; word-wrap: break-word; transform: translate(-50%, -50%); box-sizing: border-box; } #ai-translate-toolbar { position: fixed; right: 16px; bottom: 18px; z-index: 999999; display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: rgba(28, 28, 32, 0.92); color: #fff; border-radius: 10px; box-shadow: 0 6px 24px rgba(0,0,0,0.28); font-size: 12px; user-select: none; backdrop-filter: blur(6px); } #ai-translate-toolbar button { border: none; border-radius: 8px; padding: 6px 10px; cursor: pointer; color: #fff; font-size: 12px; line-height: 1; } #ai-translate-toolbar button:hover { opacity: 0.9; } #ai-restore-btn { background: #ff6b6b; } #ai-retranslate-btn { background: #4dabf7; } #ai-toolbar-close-btn { background: #666; } #ai-translate-status { max-width: 220px; color: #f1f3f5; opacity: 0.95; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } `); let translateBtn = null; let fullPageBtn = null; let translatePopup = null; let toolbar = null; let statusTextEl = null; let isFullPageTranslating = false; let pageTranslated = false; const originalTextMap = new WeakMap(); function cleanupDotsAndPopup() { if (translateBtn) { translateBtn.remove(); translateBtn = null; } if (fullPageBtn) { fullPageBtn.remove(); fullPageBtn = null; } if (translatePopup) { translatePopup.remove(); translatePopup = null; } } function clearSelection() { const selection = window.getSelection(); if (selection) { selection.removeAllRanges(); } } function ensureToolbar() { if (toolbar) return; toolbar = document.createElement('div'); toolbar.id = 'ai-translate-toolbar'; toolbar.innerHTML = ` <button id="ai-restore-btn" title="恢复原文">恢复原文</button> <button id="ai-retranslate-btn" title="翻译新增内容">翻译新增</button> <span id="ai-translate-status">未翻译</span> <button id="ai-toolbar-close-btn" title="关闭">×</button> `; document.body.appendChild(toolbar); statusTextEl = toolbar.querySelector('#ai-translate-status'); toolbar.querySelector('#ai-restore-btn').addEventListener('click', () => { restoreOriginalPage(); }); toolbar.querySelector('#ai-retranslate-btn').addEventListener('click', async () => { if (isFullPageTranslating) return; try { await translateEntirePage({ onlyUntranslated: true }); } catch (e) { setStatus('翻译新增失败:' + e.message); } }); toolbar.querySelector('#ai-toolbar-close-btn').addEventListener('click', () => { restoreOriginalPage(); toolbar.remove(); toolbar = null; statusTextEl = null; }); } function setStatus(text) { ensureToolbar(); if (statusTextEl) statusTextEl.textContent = text; console.log('[AI翻译]', text); } function showPopup(text, top, left) { if (translatePopup) translatePopup.remove(); translatePopup = document.createElement('div'); translatePopup.id = 'ai-translator-popup'; translatePopup.textContent = text; document.body.appendChild(translatePopup); translatePopup.style.top = `${top}px`; translatePopup.style.left = `${left}px`; } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function chunkByRules(items, maxItems, maxChars) { const batches = []; let current = []; let currentChars = 0; for (const item of items) { const len = item.text.length; if ( current.length >= maxItems || (current.length > 0 && currentChars + len > maxChars) ) { batches.push(current); current = []; currentChars = 0; } current.push(item); currentChars += len; } if (current.length) batches.push(current); return batches; } function isElementVisible(el) { if (!el || !el.isConnected) return false; const style = window.getComputedStyle(el); if (style.display === 'none' || style.visibility === 'hidden') return false; return true; } function isInsideExcludedUI(node) { let el = node.parentElement; while (el) { for (const selector of INLINE_UI_EXCLUDE_SELECTORS) { if (el.matches && el.matches(selector)) return true; } el = el.parentElement; } return false; } function hasMeaningfulLatinText(text) { return /[A-Za-z\u00C0-\u024F]/.test(text); } function getMainContentRoot() { for (const selector of MAIN_CONTENT_SELECTORS) { const el = document.querySelector(selector); if (el && el.innerText && el.innerText.trim().length > 100) { return el; } } return document.body; } function extractCoreText(raw) { const leading = raw.match(/^\s*/)?.[0] || ''; const trailing = raw.match(/\s*$/)?.[0] || ''; const core = raw.trim(); return { leading, core, trailing }; } function getTranslatableTextNodes(root, onlyUntranslated = true) { const walker = document.createTreeWalker( root, NodeFilter.SHOW_TEXT, { acceptNode(node) { if (!node || !node.nodeValue) return NodeFilter.FILTER_REJECT; if (onlyUntranslated && originalTextMap.has(node)) return NodeFilter.FILTER_REJECT; const raw = node.nodeValue; const trimmed = raw.trim(); if (!trimmed || trimmed.length < MIN_TEXT_LENGTH) { return NodeFilter.FILTER_REJECT; } const parent = node.parentElement; if (!parent) return NodeFilter.FILTER_REJECT; if (EXCLUDED_TAGS.has(parent.tagName)) { return NodeFilter.FILTER_REJECT; } if (isInsideExcludedUI(node)) { return NodeFilter.FILTER_REJECT; } if (!isElementVisible(parent)) { return NodeFilter.FILTER_REJECT; } if (!/[^\d\s\p{P}]/u.test(trimmed) && !hasMeaningfulLatinText(trimmed)) { return NodeFilter.FILTER_REJECT; } if (trimmed.length < MIN_TEXT_LENGTH) { return NodeFilter.FILTER_REJECT; } if (!hasMeaningfulLatinText(trimmed)) { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; } } ); const nodes = []; let current; while ((current = walker.nextNode())) { nodes.push(current); } return nodes; } function requestChatCompletion(provider, promptText) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: provider.endpoint, timeout: REQUEST_TIMEOUT, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${provider.key}` }, data: JSON.stringify({ model: provider.model, messages: [{ role: 'user', content: promptText }], temperature: 0.1, stream: false }), onload: function (response) { try { const data = JSON.parse(response.responseText); const content = data?.choices?.[0]?.message?.content?.trim(); if (content) { resolve(content); } else { reject(new Error(`无有效返回内容,HTTP ${response.status}`)); } } catch (e) { reject(new Error('响应解析失败')); } }, onerror: function (response) { reject(new Error(`网络错误 ${response?.status || ''}`)); }, ontimeout: function () { reject(new Error('请求超时')); } }); }); } function fillTemplate(template, text) { return (template || '{text}').replace('{text}', text); } async function translateSingleText(text) { let lastErr = null; for (let i = 0; i < API_PROVIDERS.length; i++) { const provider = API_PROVIDERS[i]; const promptText = fillTemplate(provider.prompt, text); try { return await requestChatCompletion(provider, promptText); } catch (e) { console.warn(`单条翻译 API[${i}] 失败:`, e.message); lastErr = e; } } throw lastErr || new Error('单条翻译失败:所有API均不可用'); } function buildBatchPayload(items) { return items.map((item, idx) => { return `<<<ITEM_${idx + 1}>>>\n${item.text}`; }).join('\n\n'); } function parseBatchResponse(responseText, expectedCount) { const results = new Array(expectedCount).fill(''); const regex = /<<<ITEM_(\d+)>>>\s*([\s\S]*?)(?=\n\s*<<<ITEM_\d+>>>|$)/g; let match; while ((match = regex.exec(responseText)) !== null) { const index = parseInt(match[1], 10) - 1; if (index >= 0 && index < expectedCount) { results[index] = (match[2] || '').trim(); } } return results; } async function translateBatch(batchItems) { const batchText = buildBatchPayload(batchItems); let lastErr = null; for (let i = 0; i < API_PROVIDERS.length; i++) { const provider = API_PROVIDERS[i]; const prompt = fillTemplate(provider.batchPrompt, batchText); try { const response = await requestChatCompletion(provider, prompt); const parsed = parseBatchResponse(response, batchItems.length); const validCount = parsed.filter(Boolean).length; if (validCount < Math.ceil(batchItems.length * 0.7)) { throw new Error('批量解析质量过低,转下一个API或单条翻译'); } return parsed.map((t, idx) => t || batchItems[idx].text); } catch (e) { console.warn(`批量翻译 API[${i}] 失败:`, e.message); lastErr = e; } } console.warn('批量翻译全部失败,降级为单条翻译:', lastErr?.message || ''); const results = []; for (const item of batchItems) { try { const one = await translateSingleText(item.text); results.push(one || item.text); await sleep(120); } catch { results.push(item.text); } } return results; } async function handleSelectionTranslate(text, top, left) { showPopup('翻译中...', top, left); try { const translated = await translateSingleText(text); if (translatePopup) translatePopup.textContent = translated || '翻译失败'; } catch (e) { if (translatePopup) translatePopup.textContent = '翻译失败:' + e.message; } } async function translateEntirePage({ onlyUntranslated = true } = {}) { if (isFullPageTranslating) { setStatus('正在翻译中,请稍候...'); return; } isFullPageTranslating = true; ensureToolbar(); try { const root = getMainContentRoot(); const nodes = getTranslatableTextNodes(root, onlyUntranslated); if (!nodes.length) { setStatus(onlyUntranslated ? '没有发现新的可翻译内容' : '未找到可翻译文本'); return; } const items = nodes.map(node => { const raw = node.nodeValue; const { leading, core, trailing } = extractCoreText(raw); return { node, raw, leading, core, trailing, text: core }; }).filter(item => item.text && item.text.length >= MIN_TEXT_LENGTH); if (!items.length) { setStatus('未找到有效文本'); return; } const batches = chunkByRules(items, BATCH_MAX_ITEMS, BATCH_MAX_CHARS); setStatus(`准备翻译:${items.length} 段文本,${batches.length} 批`); let done = 0; for (let i = 0; i < batches.length; i++) { const batch = batches[i]; setStatus(`翻译中:第 ${i + 1}/${batches.length} 批`); const translatedTexts = await translateBatch(batch); batch.forEach((item, idx) => { const translated = translatedTexts[idx]; if (!translated) return; if (!originalTextMap.has(item.node)) { originalTextMap.set(item.node, item.raw); } item.node.nodeValue = item.leading + translated + item.trailing; done++; }); await sleep(150); } pageTranslated = true; setStatus(`全文翻译完成:已替换 ${done} 段文本`); } catch (e) { setStatus('全文翻译失败:' + e.message); throw e; } finally { isFullPageTranslating = false; } } function restoreOriginalPage() { const root = getMainContentRoot(); const walker = document.createTreeWalker( root, NodeFilter.SHOW_TEXT, null ); let count = 0; let node; while ((node = walker.nextNode())) { if (originalTextMap.has(node)) { node.nodeValue = originalTextMap.get(node); count++; } } pageTranslated = false; setStatus(`已恢复原文:${count} 处`); } document.addEventListener('mouseup', function (e) { if ( e.target?.id === 'ai-translator-btn' || e.target?.id === 'ai-fullpage-btn' || (translatePopup && translatePopup.contains(e.target)) || (toolbar && toolbar.contains(e.target)) ) { return; } cleanupDotsAndPopup(); const selection = window.getSelection(); if (!selection || selection.isCollapsed || !selection.toString().trim()) { return; } const selectedText = selection.toString().trim(); let range; try { range = selection.getRangeAt(0); } catch { return; } const rect = range.getBoundingClientRect(); if (!rect) return; const centerX = window.scrollX + rect.left + rect.width / 2; const centerY = window.scrollY + rect.top + rect.height / 2; translateBtn = document.createElement('div'); translateBtn.id = 'ai-translator-btn'; document.body.appendChild(translateBtn); translateBtn.style.top = `${centerY}px`; translateBtn.style.left = `${centerX}px`; fullPageBtn = document.createElement('div'); fullPageBtn.id = 'ai-fullpage-btn'; document.body.appendChild(fullPageBtn); fullPageBtn.style.top = `${centerY}px`; fullPageBtn.style.left = `${centerX + 18}px`; translateBtn.addEventListener('click', function (event) { event.stopPropagation(); handleSelectionTranslate(selectedText, centerY + 24, centerX); if (translateBtn) translateBtn.style.display = 'none'; if (fullPageBtn) fullPageBtn.style.display = 'none'; }); fullPageBtn.addEventListener('click', async function (event) { event.stopPropagation(); showPopup('全文翻译中,请稍候...', centerY + 24, centerX + 60); if (translateBtn) translateBtn.style.display = 'none'; if (fullPageBtn) fullPageBtn.style.display = 'none'; try { await translateEntirePage({ onlyUntranslated: true }); if (translatePopup) { translatePopup.textContent = '全文翻译完成'; setTimeout(() => { if (translatePopup) { translatePopup.remove(); translatePopup = null; } }, 1200); } } catch (e) { if (translatePopup) translatePopup.textContent = '全文翻译失败:' + e.message; } }); }); document.addEventListener('mousedown', function (e) { if ( (translatePopup && translatePopup.contains(e.target)) || (toolbar && toolbar.contains(e.target)) || e.target?.id === 'ai-translator-btn' || e.target?.id === 'ai-fullpage-btn' ) { return; } cleanupDotsAndPopup(); clearSelection(); }); })(); 2 个帖子 - 2 位参与者 阅读完整话题
写在前面,全文由 ANY大善人的opus4.7主导 IPV4 NEWAPI IPV6NEW直连 佬友们好。分享一下我这边的家宽双栈对外方案,脱敏整理出来给有同样需求的同学参考。 我家这边情况大概是: 广东移动运营商给了公网 IPv6,但 IPv4 是大内网 NAT1,没固定公网 IPv4 想把家里 5 个 Web 服务(api / cpa / yx / code / claw)和 1 个 VPN 挂到公网 要兼顾 公司 IPv4 (办公网、IPv4-only 环境)和 IPv6 用户 不想为这事单租 VPS 折腾完之后稳定跑了一阵,整理出来发上来。所有真实域名、公网 IP、动态端口、密钥都用 <...> 占位符替换了,自己用的时候换成真值即可。 整套方案的免费成本:Cloudflare DNS + 307 Redirect Rule 在免费计划够用,Lucky 是开源的。零 VPS 开支。 一、整体思路 核心三招: 双域名分离 :入口域名(橙云)和落地域名(灰云)分开。入口只做 307 重定向,不直连后端;落地域名灰云直接 DNS 解析到家里。 双栈分流 :IPv4 客户端被 307 重定向到 service.stun.<ROOT_DOMAIN>:<STUN_TCP4_PORT> (家宽 IPv4 不能直 443,只能走 Lucky STUN 拿到的动态端口);IPv6 客户端被 307 重定向到 service.stun.<ROOT_DOMAIN> (默认 443,直连 OpenWrt 公网 IPv6)。 VPN 独立 :VPN 不走 307,因为 WireGuard 不理解 HTTP 重定向。VPN 用独立的灰云 AAAA 直连。 二、主链路图 ┌────────────────────────────────────┐ │ Client / Browser / App │ │ service.<ROOT_DOMAIN> │ └──────────────────┬─────────────────┘ │ ┌──────────────────▼─────────────────┐ │ Cloudflare │ │ proxied entry records │ │ Dynamic Redirect, HTTP 307 │ └──────────────────┬─────────────────┘ │ ┌────────────────────────────────────────────────┼────────────────────────────────────────────────┐ │ │ │ ┌───────▼────────────────────┐ ┌──────────▼──────────────────┐ ┌──────────▼──────────────────┐ │ IPv4 client │ │ IPv6 client │ │ VPN client │ │ target should include port │ │ target uses default 443 │ │ no HTTP redirect │ └───────┬────────────────────┘ └──────────┬──────────────────┘ └──────────┬──────────────────┘ │ 307 Location: │ 307 Location: │ │ https://service.stun.<ROOT_DOMAIN>:<STUN_PORT>/path │ https://service.stun.<ROOT_DOMAIN>/path │ │ │ │ ┌───────▼────────────────────┐ ┌──────────▼──────────────────┐ ┌──────────▼──────────────────┐ │ DNS-only A │ │ DNS-only AAAA │ │ DNS-only AAAA │ │ service.stun.<ROOT_DOMAIN> │ │ service.stun.<ROOT_DOMAIN> │ │ vpn.<ROOT_DOMAIN> │ │ -> STUN public IPv4 │ │ -> OpenWrt public IPv6 │ │ -> OpenWrt public IPv6 │ └───────┬────────────────────┘ └──────────┬──────────────────┘ └──────────┬──────────────────┘ │ │ │ ┌───────▼────────────────────┐ ┌──────────▼──────────────────┐ ┌──────────▼──────────────────┐ │ Lucky STUN tcp4 │ │ OpenWrt │ │ OpenWrt VPN service │ │ public IPv4:<STUN_PORT> │ │ Lucky HTTPS :443 │ │ WireGuard/OpenVPN/etc. │ │ -> Lucky HTTPS :18443 │ │ cert *.stun.<ROOT_DOMAIN> │ │ protocol-specific port │ └───────┬────────────────────┘ └──────────┬──────────────────┘ └─────────────────────────────┘ │ │ └───────────────────────────────┬────────────────┘ │ ┌──────────────▼───────────────┐ │ OpenWrt Lucky reverse proxy │ │ same Host rules on 443/18443 │ │ service.stun.<ROOT_DOMAIN> │ │ -> backend │ └──────────────┬───────────────┘ │ ┌───────────────────────────────┼───────────────────────────────┬───────────────────────────────┐ │ │ │ │ ┌───────▼────────────────────┐ ┌────────▼───────────────────┐ ┌────────▼───────────────────┐ ┌────────▼───────────────────┐ │ api.stun.<ROOT_DOMAIN> │ │ cpa.stun.<ROOT_DOMAIN> │ │ yx.stun.<ROOT_DOMAIN> │ │ code.stun.<ROOT_DOMAIN> │ │ -> <APP_NODE_A>:8881 │ │ -> <APP_NODE_A>:8317 │ │ -> <APP_NODE_A>:5001 │ │ -> <APP_NODE_B>:19080 │ │ NewAPI │ │ CPA / Proxy API │ │ YX Web │ │ code-server │ └────────────────────────────┘ └────────────────────────────┘ └────────────────────────────┘ └────────────────────────────┘ │ ┌──────────────▼───────────────┐ │ claw.stun.<ROOT_DOMAIN> │ │ -> <APP_NODE_B>:18789 │ │ OpenClaw Gateway │ └──────────────────────────────┘ 解读: 客户端先打入口域名(橙云),被 Cloudflare 307 到对应的 stun 落地域名 IPv4 客户端拿到 https://service.stun.<ROOT_DOMAIN>:<STUN_TCP4_PORT>/... ,解析到 Lucky STUN 公网 IPv4:动态端口,进 Lucky 反代 IPv6 客户端拿到 https://service.stun.<ROOT_DOMAIN>/... (443),解析到 OpenWrt 公网 IPv6,进 Lucky 反代 两条路最后都汇到 OpenWrt Lucky 反代,按 Host 把请求转给内网真正的后端服务 VPN 走自己那条路,跟 HTTP 没关系 三、DNS 怎么设 入口域名(5 个)—— 橙云 api.<ROOT_DOMAIN> A proxied=true TTL=auto cpa.<ROOT_DOMAIN> A proxied=true TTL=auto yx.<ROOT_DOMAIN> A proxied=true TTL=auto code.<ROOT_DOMAIN> A proxied=true TTL=auto claw.<ROOT_DOMAIN> A proxied=true TTL=auto 为什么橙云:必须橙云,Cloudflare 只有收到 HTTP 请求后才能执行 Redirect Rule。灰云的话客户端绕过 CF 直接打到 A 记录,Redirect Rule 根本触发不了。入口的 A content 没那么重要(反正是 CF 边缘 IP 收的),不需要指向家里。 落地域名(5 个 + 1 个 VPN)—— 灰云 api.stun.<ROOT_DOMAIN> A=<STUN_PUBLIC_IPV4> AAAA=<OPENWRT_PUBLIC_IPV6> proxied=false TTL=60 cpa.stun.<ROOT_DOMAIN> A=<STUN_PUBLIC_IPV4> AAAA=<OPENWRT_PUBLIC_IPV6> proxied=false TTL=60 yx.stun.<ROOT_DOMAIN> A=<STUN_PUBLIC_IPV4> AAAA=<OPENWRT_PUBLIC_IPV6> proxied=false TTL=60 code.stun.<ROOT_DOMAIN> A=<STUN_PUBLIC_IPV4> AAAA=<OPENWRT_PUBLIC_IPV6> proxied=false TTL=60 claw.stun.<ROOT_DOMAIN> A=<STUN_PUBLIC_IPV4> AAAA=<OPENWRT_PUBLIC_IPV6> proxied=false TTL=60 vpn.<ROOT_DOMAIN> AAAA=<OPENWRT_PUBLIC_IPV6> proxied=false TTL=60 为什么灰云 + TTL 60:307 之后客户端要直连家里 Lucky,必须灰云让 DNS 真实解析过去。TTL 60 是为了 STUN 公网地址变化时尽快收敛(用 LuckyDDNS 自动更新 DNS A 记录)。5 个落地域名的 A 都指向 Lucky STUN 探出来的公网 IPv4,AAAA 都指向 OpenWrt 公网 IPv6,这样一张通配证书就能盖住。 四、Cloudflare Redirect Rule Cloudflare 控制台 → Rules → Redirect Rules,建一个 Dynamic Redirect Ruleset,phase 是 http_request_dynamic_redirect 。整体设置: status_code: 307 preserve_query_string: true 为什么 307:307 会保留 HTTP 方法(POST/PUT/PATCH),API 调用、表单提交、code-server 的 PUT 都不会被吃掉。301/302 在某些客户端会把 POST 改成 GET,直接坑死。 为什么 preserve_query_string:不开的话 ?token=... 、 ?folder=... 这种查询参数全丢,API 和 code-server 直接报错。 下面三条规则,按顺序放。 规则 1:cpa 根路径补全 match: http.host == "cpa.<ROOT_DOMAIN>" and path == "/" target: https://cpa.<ROOT_DOMAIN>/management.html 为什么这么设:cpa 的根路径默认不会跳到管理页,先把 / 补成 /management.html ,后面通用规则继续接管把它跳到 stun 落地域名。 规则 2:IPv4 入口加 STUN 端口 match: http.host in {api.<ROOT_DOMAIN>, cpa.<ROOT_DOMAIN>, yx.<ROOT_DOMAIN>, code.<ROOT_DOMAIN>, claw.<ROOT_DOMAIN>} and ip.src in 0.0.0.0/0 target expression: wildcard_replace( http.request.full_uri, "*://*.<ROOT_DOMAIN>/*", "https://${2}.stun.<ROOT_DOMAIN>:<STUN_TCP4_PORT>/${3}" ) 为什么这么设: ip.src in 0.0.0.0/0 是 IPv4-only 匹配。家宽 IPv4 走不了 443,必须把 Lucky STUN 当前公网端口写到 Location 里。 <STUN_TCP4_PORT> 是 动态值 ,要靠脚本或 Webhook 同步过来(见第九节坑 2)。 规则 3:IPv6 入口直接 443 match: http.host in {api.<ROOT_DOMAIN>, cpa.<ROOT_DOMAIN>, yx.<ROOT_DOMAIN>, code.<ROOT_DOMAIN>, claw.<ROOT_DOMAIN>} and not ip.src in 0.0.0.0/0 target expression: wildcard_replace( http.request.full_uri, "*://*.<ROOT_DOMAIN>/*", "https://${2}.stun.<ROOT_DOMAIN>/${3}" ) 为什么这么设:IPv6 客户端可以直达家里 OpenWrt 公网 IPv6 的 443,不需要动态端口,省事。 五、Lucky 配置 STUN tcp4 类型: tcp4 Target: <OPENWRT_LAN_IP>:18443 (指向 Lucky 自己的 HTTPS 监听口,不是 443) PublicAddr (Lucky 探出来): <STUN_PUBLIC_IPV4>:<STUN_TCP4_PORT> 为什么 Target 是 18443 不是 443:家宽 IPv4/443 实测不通,IPv4 数据面只能走 Lucky 自己单独起的 HTTPS 监听口 18443。443 留给 IPv6 直连用。 端口同步:STUN 端口是运营商分配的, 动态值 。Lucky 探到新端口后必须同步到 Cloudflare Redirect Rule 的规则 2。两种方式: Lucky Webhook → 调用 Cloudflare API PATCH Ruleset 外部脚本轮询 Lucky API → PATCH Ruleset 同步完后必须用 curl -4 -I 从外部复测 Location(见第九节坑 3,API 和边缘可能短暂不一致)。 HTTPS 双监听 Lucky 反代要同时挂两个 HTTPS 监听口: 监听 1: <OPENWRT_PUBLIC_IPV6>:443 给 IPv6 客户端直连 监听 2: <OPENWRT_LAN_IP>:18443 给 STUN tcp4 把 IPv4 流量打进来 两个口共用同一组反代规则,因为 IPv4 和 IPv6 路径最后到 Lucky 时 Host 都是 *.stun.<ROOT_DOMAIN> 。 通配证书 类型: 通配证书 *.stun.<ROOT_DOMAIN> 签发方式: DNS-01 DNS Provider: Cloudflare (用 API Token,只给 Zone DNS Edit 权限) 为什么 DNS-01:HTTP-01 要 80/443 开放给 ACME,家宽 80/443 路径本来就不全通,DNS-01 不依赖入站端口,能自动续签。一张通配盖住 5 个 stun 落地域名。 反代规则 每个 stun 落地 Host 对一个内网后端: api.stun.<ROOT_DOMAIN> -> http://<APP_NODE_A>:8881 (NewAPI) cpa.stun.<ROOT_DOMAIN> -> http://<APP_NODE_A>:8317 (CPA / Proxy API) yx.stun.<ROOT_DOMAIN> -> http://<APP_NODE_A>:5001 (YX Web) code.stun.<ROOT_DOMAIN> -> http://<APP_NODE_B>:19080 (code-server) claw.stun.<ROOT_DOMAIN> -> http://<APP_NODE_B>:18789 (OpenClaw Gateway) 公共选项: 前端协议: https 后端协议: http (TLS 在 Lucky 终止,后端走明文省事,不用每个后端搞证书) WebSocket: 开 (code-server 这种长连接工具必须开) 自动反代重定向: 关 (避免后端 Location 头被二次改写,排障痛苦) 访问日志: 开 (出问题方便看) 为什么 TLS 在 Lucky 终止:证书、SNI、续签集中维护一处,后端跑明文 HTTP 反而干净。 六、认证保护 公网第一道认证放在 Lucky,不放在 Cloudflare 入口层(CF 只做 307 不挡): api.stun.<ROOT_DOMAIN> Lucky Basic Auth: 关 后端自己有登录页 (NewAPI) cpa.stun.<ROOT_DOMAIN> Lucky Basic Auth: 开 后端通过 Basic Auth 后到 CPA API yx.stun.<ROOT_DOMAIN> Lucky Basic Auth: 开 后端通过 Basic Auth 后跳 /login code.stun.<ROOT_DOMAIN> Lucky Basic Auth: 开 code-server 后端 auth=none claw.stun.<ROOT_DOMAIN> Lucky Basic Auth: 开 后端是 OpenClaw Gateway UI 为什么 api 不开:NewAPI 自带账号系统,再叠一层 Basic Auth 反而碍事。 为什么其他都开:cpa/yx 是私有管理类入口,code-server 直接是命令执行入口( 裸露公网 = 送服务器 ),claw 是网关 UI。统一拿 Lucky Basic Auth 当公网第一道挡板,简单粗暴。 为什么 code-server 设 auth=none :code-server 自带密码方案不够灵活,统一交给前置 Basic Auth 处理。 前提是 Lucky Basic Auth 必须开 ,否则 code-server 在公网完全没保护。 顺嘴提一下:我账号下还有 2 个 Cloudflare Access SaaS/OIDC App,但那是给 Lucky 自己做第三方登录用的回调端点, 不是 业务入口拦截页。业务入口前没有 CF Access。 七、VPN 为什么不能套 307 vpn.<ROOT_DOMAIN> AAAA -> <OPENWRT_PUBLIC_IPV6> 灰云 直接灰云 AAAA 指向 OpenWrt 公网 IPv6,按 VPN 自己协议端口(WireGuard / OpenVPN / IPsec)连。 为什么不能走 307:307 是 HTTP 状态码,只有浏览器/curl 会跟随 Location。WireGuard/OpenVPN 客户端根本不解析 HTTP,把 VPN 域名扔进 Cloudflare 橙云 → 客户端连不上 → 超时。VPN、SSH、其他非 HTTP 协议都不要套 Cloudflare 307。 八、验收 curl 每条命令独立可复制粘贴,把 <...> 换成真值即可。 # 入口域名是不是返回 307 curl -I https://api.<ROOT_DOMAIN>/some-path # IPv4 Location 必须带 :<STUN_TCP4_PORT> curl -4 -I https://api.<ROOT_DOMAIN>/some-path # IPv6 Location 应该不带端口 curl -6 -I https://api.<ROOT_DOMAIN>/some-path # IPv4 STUN 落地是否可用 curl -4 -k -I https://api.stun.<ROOT_DOMAIN>:<STUN_TCP4_PORT>/ # IPv6 443 落地是否可用 curl -6 -k -I https://api.stun.<ROOT_DOMAIN>/ # Lucky Host 规则是否命中 (LAN 内测) curl -k -H "Host: api.stun.<ROOT_DOMAIN>" https://<OPENWRT_LAN_IP>:18443/ # Basic Auth 是否生效 (期望 401 + WWW-Authenticate) curl -I https://code.stun.<ROOT_DOMAIN>:<STUN_TCP4_PORT>/ 九、踩过的坑 坑 1:IPv4 走不了 443 家宽 IPv4 是大内网 NAT,公网 443 不通。一开始以为 IPv4 也能走 443,折腾半天 timeout。最后老老实实用 Lucky STUN tcp4 拿一个动态公网端口,把 Cloudflare Redirect 的 Location 写成 :<STUN_TCP4_PORT> 才通。 坑 2:STUN 端口是动态的 Lucky STUN PublicAddr 的端口运营商会换。如果 Cloudflare Redirect Rule 里写死旧端口,IPv4 用户会被重定向到失效端口,表现就是"网页打不开"。必须做端口同步:Lucky Webhook 或外部脚本 PATCH Cloudflare Ruleset,同步完用 curl -4 -I 验证 Location 是否真的带新端口。 坑 3:Cloudflare API 配置和边缘行为可能短暂不一致 PATCH 完 Ruleset,Cloudflare API 返回的 target_expression 显示带端口,但边缘 HTTPS 实际返回的 Location 没带端口,IPv4 用户继续 timeout。这种漂移可能持续几分钟。 别只看 API 返回值,必须从外部 curl -4 -I 看最终 Location 才算数。 坑 4:Lucky 404 不一定是后端挂了 直接访问 Lucky 监听口或 Host 不匹配会返回 Lucky 自己的 404/Warning 页。第一反应别去重启后端,先确认 Host 头是不是命中了反代规则: curl -k -H "Host: api.stun.<ROOT_DOMAIN>" https://<OPENWRT_LAN_IP>:18443/ 加上正确 Host 才能命中规则。 坑 5:VPN 不能套 Cloudflare 307 前面讲过:VPN 客户端不理解 HTTP 重定向,必须独立灰云 AAAA 直连,按 VPN 自己协议处理。 坑 6:排障先分清 timeout / 404 / 401 timeout :网络层问题(端口没开、IPv4/443 路径、NAT 失效、防火墙) 404 :Host 没命中 Lucky 反代规则(多半是 Cloudflare Redirect 写错了 stun 域名,或者直接打了 Lucky 监听口没带 Host) 401 :规则命中了,Lucky Basic Auth 在工作,这是 正常表现 别把 401 当成"挂了"去重启服务。 十、完整脱敏抽象架构图 ┌────────────────────┐ │ client │ └─────────┬──────────┘ │ ┌─────────▼──────────┐ │ Cloudflare orange │ │ HTTP 307 only │ └─────────┬──────────┘ │ ┌───────────────────────┴───────────────────────┐ │ │ ┌─────────▼──────────┐ ┌──────────▼─────────┐ │ IPv4 path │ │ IPv6 path │ │ stun host + port │ │ stun host + 443 │ └─────────┬──────────┘ └──────────┬─────────┘ │ │ ┌─────────▼──────────┐ ┌──────────▼─────────┐ │ Lucky STUN tcp4 │ │ OpenWrt IPv6 │ │ public:<port> │ │ Lucky HTTPS 443 │ └─────────┬──────────┘ └──────────┬─────────┘ │ │ └───────────────────────┬───────────────────────┘ │ ┌─────────▼──────────┐ │ Lucky reverse proxy│ │ Host based routing │ └─────────┬──────────┘ │ ┌────────────────────────────┼────────────────────────────┐ │ │ │ ┌───────▼────────┐ ┌────────▼───────┐ ┌─────────▼──────┐ │ service A │ │ service B │ │ service C │ │ node A:port │ │ node A:port │ │ node B:port │ └────────────────┘ └────────────────┘ └────────────────┘ 写在最后 整套方案核心就这几条: 入口必须橙云,落地必须灰云 IPv4 走 STUN 端口,IPv6 直接 443,别假设 IPv4 能走 443 307 + preserve_query_string,API 和长连接才不挂 VPN 独立,别套 307 认证放在 Lucky,不放在 CF 入口 STUN 端口动态值,必须同步 + 外部复测 所有 <...> 占位符替换成真实值再用。 真实域名、公网 IP、STUN 端口、Cloudflare Zone ID、API Token、Lucky 管理凭据、Basic Auth 账号密码、TOTP 种子、origin 旁路密钥这类敏感信息 绝对不要贴论坛 ,包括截图里也要打码。 有问题评论区交流,佬友们如果有更优雅的 STUN 端口同步方案也欢迎贴出来。 2 个帖子 - 2 位参与者 阅读完整话题
看py论坛发现,discourse支持在帖子链接末尾添加 /print 来查看帖子全文,没有热加载,可以方便的使用ctrl+F搜索关键词。 将帖子的 t/topic 替换为 raw 可把帖子转为markdown格式; 3 个帖子 - 2 位参与者 阅读完整话题
以前好像有一些可以免费看海外新闻网站全文的脚本或者插件,这些网站很多新闻或者评论设置了付费阅读,但是可能只需要前端解锁就能查看全文。大家有什么推荐吗?现在还能用的。 3 个帖子 - 3 位参与者 阅读完整话题
我们的团队做了一个论文阅读软件 EasyReader: 这款工具的亮点: 1 ,全文翻译,不是普通机翻,是 llm 大模型 AI 翻译,对学术语言风格进行了调校。可以选择你的学科,这样翻译更准确! 2 ,排版还原,公式图表不乱码;基本上( 90%)常见的论文的排版都是完美还原。 3 ,大模型辅助阅读:一键生成思维导图、论文导读、提取 LaTeX 公式等 4 ,跨库搜文献 网址: https://www.easyreader.com.cn/ 目前我们正在不断完善软件,还有些 bug 。欢迎提供对于软件的使用建议。 版本 1.1.8 发布日期: 2026 年 5 月 8 日 优化功能:论文详情、论文属性信息中增加 DOI 信息 优化功能:导出论文导读时可选择文件格式:文本,md 格式,或者 word 文档格式 优化功能:增加快捷键,可以中英文试图切换 优化界面:文件列表页面按钮优化 版本 1.1.7 发布日期: 2026 年 4 月 28 日 新增功能:批量检查论文撤稿信息 优化功能:论文周选加入筛选( arxiv/pubmed )功能 优化功能:排版解析算法优化 版本 1.1.6 发布日期: 2026 年 4 月 15 日 新增功能:增加“生成引用”功能(文件列表界面),支持国标 GB/T 7714 、APA 、MLA 等多种常见格式 优化功能:点击 pdf 中的外部链接后通过系统默认浏览器打开 优化性能:稍微提高翻译速度 紧急修复:1.1.5 版本某些页面卡死的 bug 优化界面:文件列表显示优化 优化: 翻译后排版 分享资源: 目前注册后会有一个免费的使用额度。如果您愿意对软件进行测评,请给我发消息,我们会赠送跟多的资源包。
我们的团队做了一个论文阅读软件 EasyReader: 这款工具的亮点: 1 ,全文翻译,不是普通机翻,是 llm 大模型 AI 翻译,对学术语言风格进行了调校。可以选择你的学科,这样翻译更准确! 2 ,排版还原,公式图表不乱码;基本上( 90%)常见的论文的排版都是完美还原。 3 ,大模型辅助阅读:一键生成思维导图、论文导读、提取 LaTeX 公式等 4 ,跨库搜文献 网址: https://www.easyreader.com.cn/ 目前我们正在不断完善软件,还有些 bug 。欢迎提供对于软件的使用建议。 版本 1.1.8 发布日期: 2026 年 5 月 8 日 优化功能:论文详情、论文属性信息中增加 DOI 信息 优化功能:导出论文导读时可选择文件格式:文本,md 格式,或者 word 文档格式 优化功能:增加快捷键,可以中英文试图切换 优化界面:文件列表页面按钮优化 版本 1.1.7 发布日期: 2026 年 4 月 28 日 新增功能:批量检查论文撤稿信息 优化功能:论文周选加入筛选( arxiv/pubmed )功能 优化功能:排版解析算法优化 版本 1.1.6 发布日期: 2026 年 4 月 15 日 新增功能:增加“生成引用”功能(文件列表界面),支持国标 GB/T 7714 、APA 、MLA 等多种常见格式 优化功能:点击 pdf 中的外部链接后通过系统默认浏览器打开 优化性能:稍微提高翻译速度 紧急修复:1.1.5 版本某些页面卡死的 bug 优化界面:文件列表显示优化 优化: 翻译后排版 分享资源: 目前注册后会有一个免费的使用额度。如果您愿意对软件进行测评,请给我发消息,我们会赠送跟多的资源包。