包含关键字 typecho 的文章

在线工具网址:https://see-tool.com/date-calculator

工具截图:

一、核心功能设计

日期计算器包含四个独立模块:

  1. 日期间隔计算: 计算两个日期之间的天数、周数、月数、年数
  2. 日期加减计算: 在基准日期上加减指定时间单位
  3. 年龄计算: 精确计算年龄(年/月/日)
  4. 工作日计算: 统计工作日、周末天数

二、日期间隔计算实现

2.1 核心计算逻辑

const dateDiff = computed(() => {
  if (!startDate.value || !endDate.value) {
    return { days: 0, weeks: 0, months: 0, years: 0 }
  }

  const start = new Date(startDate.value)
  const end = new Date(endDate.value)

  // 确保开始日期小于结束日期(自动排序)
  const [earlierDate, laterDate] = start <= end ? [start, end] : [end, start]

  let diffTime = laterDate.getTime() - earlierDate.getTime()

  // 如果包含结束日期,增加一天
  if (includeEndDate.value) {
    diffTime += 24 * 60 * 60 * 1000
  }

  const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))

  // 计算精确的月数差异
  let months = (laterDate.getFullYear() - earlierDate.getFullYear()) * 12
  months += laterDate.getMonth() - earlierDate.getMonth()

  // 如果日期不足一个月,减去一个月
  if (laterDate.getDate() < earlierDate.getDate()) {
    months--
  }

  // 计算年数
  const years = Math.floor(months / 12)

  return {
    days: diffDays,
    weeks: Math.floor(diffDays / 7),
    months: Math.max(0, months),
    years: Math.max(0, years)
  }
})

关键点:

  1. 自动排序: 无论用户输入顺序,自动识别较早和较晚的日期
  2. 包含结束日期: 可选项,影响天数计算(+1天)
  3. 精确月数: 考虑日期不足一个月的情况
  4. 负数保护: 使用 Math.max(0, value) 防止负数

2.2 辅助工具函数

// 交换开始和结束日期
const swapDates = () => {
  const temp = startDate.value
  startDate.value = endDate.value
  endDate.value = temp
}

// 设置结束日期为今天
const setToday = (type) => {
  if (!process.client) return
  const today = new Date().toISOString().split('T')[0]
  if (type === 'diff') {
    endDate.value = today
  }
}

三、日期加减计算实现

3.1 核心计算逻辑

const calculatedDate = computed(() => {
  if (!baseDate.value) {
    return ''
  }

  if (!amount.value || amount.value === 0) {
    return baseDate.value
  }

  const base = new Date(baseDate.value)
  // 根据操作类型确定正负
  const value = operation.value === 'add' ? parseInt(amount.value) : -parseInt(amount.value)

  switch (unit.value) {
    case 'days':
      base.setDate(base.getDate() + value)
      break
    case 'weeks':
      base.setDate(base.getDate() + (value * 7))
      break
    case 'months':
      base.setMonth(base.getMonth() + value)
      break
    case 'years':
      base.setFullYear(base.getFullYear() + value)
      break
  }

  return base.toISOString().split('T')[0]
})

关键点:

  1. 操作符处理: 减法通过负数实现,统一使用加法逻辑
  2. 原生 Date API: 利用 setDate/setMonth/setFullYear 自动处理溢出
  3. 格式化输出: toISOString().split('T')[0] 获取 YYYY-MM-DD 格式

3.2 获取星期几

const getWeekday = (dateStr) => {
  if (!dateStr) return ''
  const weekdays = tm('dateCalculator.weekdays')
  if (!weekdays || !Array.isArray(weekdays)) return ''
  const date = new Date(dateStr)
  return weekdays[date.getDay()] || ''
}

说明:

  • getDay() 返回 0-6,其中 0 代表周日
  • 从国际化配置中读取星期名称数组

四、年龄计算实现

4.1 精确年龄计算

const age = computed(() => {
  if (!birthDate.value || !ageCalculateDate.value) {
    return { years: 0, months: 0, days: 0, totalDays: 0 }
  }

  const birth = new Date(birthDate.value)
  const calculate = new Date(ageCalculateDate.value)

  // 如果出生日期晚于计算日期,返回0
  if (birth > calculate) {
    return { years: 0, months: 0, days: 0, totalDays: 0 }
  }

  // 计算精确年龄
  let years = calculate.getFullYear() - birth.getFullYear()
  let months = calculate.getMonth() - birth.getMonth()
  let days = calculate.getDate() - birth.getDate()

  // 调整天数
  if (days < 0) {
    months--
    // 获取上个月的天数
    const lastMonth = new Date(calculate.getFullYear(), calculate.getMonth(), 0)
    days += lastMonth.getDate()
  }

  // 调整月数
  if (months < 0) {
    years--
    months += 12
  }

  // 计算总天数
  const totalDays = Math.floor((calculate.getTime() - birth.getTime()) / (1000 * 60 * 60 * 24))

  return {
    years: Math.max(0, years),
    months: Math.max(0, months),
    days: Math.max(0, days),
    totalDays: Math.max(0, totalDays)
  }
})

关键点:

  1. 逐级调整: 先调整天数,再调整月数,最后得到年数
  2. 借位逻辑: 天数不足时从月份借位,月份不足时从年份借位
  3. 上月天数: 使用 new Date(year, month, 0) 获取上月最后一天
  4. 总天数: 单独计算,用于显示"已活xx天"

4.2 派生数据计算

// 模板中使用
Math.floor(age.totalDays / 30.44)  // 总月数(平均每月30.44天)
Math.floor(age.totalDays / 7)      // 总周数
age.totalDays                      // 总天数

五、工作日计算实现

5.1 核心计算逻辑

const workDays = computed(() => {
  if (!workStartDate.value || !workEndDate.value) {
    return { total: 0, weekdays: 0, weekends: 0 }
  }

  const start = new Date(workStartDate.value)
  const end = new Date(workEndDate.value)

  // 确保开始日期不大于结束日期
  if (start > end) {
    return { total: 0, weekdays: 0, weekends: 0 }
  }

  let weekdays = 0
  let weekends = 0
  const current = new Date(start)

  // 包含开始和结束日期
  while (current <= end) {
    const dayOfWeek = current.getDay()
    if (dayOfWeek === 0 || dayOfWeek === 6) { // 周日=0, 周六=6
      weekends++
    } else {
      weekdays++
    }
    current.setDate(current.getDate() + 1)
  }

  return {
    total: weekdays + weekends,
    weekdays: excludeWeekends.value ? weekdays : weekdays + weekends,
    weekends
  }
})

关键点:

  1. 逐日遍历: 从开始日期循环到结束日期,逐日判断
  2. 星期判断: getDay() 返回 0(周日) 或 6(周六) 为周末
  3. 可选排除: 根据 excludeWeekends 决定是否排除周末
  4. 包含边界: 包含开始和结束日期

六、状态管理

6.1 响应式状态定义

// Tab 切换
const activeTab = ref('difference')

// 日期间隔计算
const startDate = ref('')
const endDate = ref('')
const includeEndDate = ref(false)

// 日期加减计算
const baseDate = ref('')
const operation = ref('add')      // 'add' | 'subtract'
const amount = ref(0)
const unit = ref('days')          // 'days' | 'weeks' | 'months' | 'years'

// 工作日计算
const workStartDate = ref('')
const workEndDate = ref('')
const excludeWeekends = ref(true)

// 年龄计算
const birthDate = ref('')
const ageCalculateDate = ref('')

6.2 初始化默认值

onMounted(() => {
  if (!process.client) return
  const today = new Date().toISOString().split('T')[0]
  startDate.value = today
  endDate.value = today
  baseDate.value = today
  workStartDate.value = today
  workEndDate.value = today
  birthDate.value = ''  // 不设置默认出生日期
  ageCalculateDate.value = today
})

说明:

  • 使用 process.client 判断避免 SSR 问题
  • 出生日期不设默认值,避免误导用户

七、日期处理技巧

7.1 Date 对象的自动溢出处理

// JavaScript 的 Date 会自动处理溢出
const date = new Date('2024-01-31')
date.setMonth(date.getMonth() + 1)  // 自动变为 2024-03-02(2月没有31日)

7.2 获取上月最后一天

// 将日期设为0,会自动回退到上月最后一天
const lastDayOfLastMonth = new Date(year, month, 0)

7.3 日期格式化

// ISO 格式转 YYYY-MM-DD
const dateStr = new Date().toISOString().split('T')[0]

八、核心算法总结

日期间隔计算:
  时间戳相减 → 转换为天数
  年月日逐级计算 → 处理借位

日期加减计算:
  原生 Date API → 自动处理溢出

年龄计算:
  年月日分别相减 → 逐级调整借位

工作日计算:
  逐日遍历 → 判断星期几 → 统计分类

核心原则:

  1. 利用原生 API: Date 对象的自动溢出处理
  2. 边界处理: 防止负数、空值、非法日期
  3. 精确计算: 考虑月份天数差异、闰年等特殊情况
  4. 用户友好: 自动排序、可选配置、实时计算

在 AI 技术日新月异的今天,AI Agent(智能体)正逐渐从概念走向落地。它不仅能进行对话,更具备了思考、规划和执行任务的能力。然而,构建一个成熟的 Agent 系统,并非简单的 API 调用,而是多种核心技术协同工作的结果。

在深入开发之前,理清这些基础概念,有助于我们更好地理解 AI 系统的底层运行逻辑。


一、 智能的内核:大语言模型与交互边界

1. LLM(大语言模型):通识大脑

LLM 是 Agent 的核心引擎。它拥有强大的语言理解能力,但它是一个“静态大脑”,其知识停留在训练截止的那一刻,无法感知企业内部的私有数据。

2. Context Window(上下文窗口):短期记忆

这是模型单次交互能处理的信息上限。

  • 局限: 即使窗口再大,也不能盲目塞入所有数据。正如在数学题中加入无关的干扰信息会降低准确率一样,过长的背景会导致模型“注意力不集中”,甚至产生幻觉。

3. Prompt Engineering(提示工程):沟通的艺术

  • Zero-shot(零样本): 不给示例,直接下指令。这要求指令必须高度具体(如:从“写个政策”优化为“写个 200 字符合 GDPR 标准的隐私政策”)。
  • Few-shot(少样本): 提供几个理想的问答示例,这能有效地规范 AI 输出的语气(Tone)和特定格式。
  • Chain of Thought(思维链): 引导 AI 展示推理步骤,强制模型分配更多计算资源在逻辑推导上,从而处理复杂问题。


二、 知识的扩展:从“翻书”到“记忆”

为了让 AI 访问私有数据,我们需要构建一套“外挂硬盘”。

4. 向量数据库 vs 传统数据库

传统的 SQL 数据库是基于值或关键词的匹配(如 LIKE %vacation%)。而向量数据库(如 ChromaDB, Pinecone)则是基于含义(Meaning)的匹配。即使搜索词不一致,只要语义接近,系统就能精准定位。

5. Embeddings 与数据预处理

  • 数据切分(Chunking): 我们不能将 500GB 的文档直接塞给 AI。必须将其切成小块。
  • 重叠(Overlap): 在切分时,通常会保留一定的文字重叠。这能防止上下文在切分处丢失,从而大幅提升检索的准确性。
  • Embeddings: 将切分好的文本块转化为高维数字向量,让计算机能够以数学方式计算语义的相关性。

6. RAG(检索增强生成):知识的补丁

RAG 是目前解决 AI 幻觉的最优方案。它通过“检索 -> 增强 -> 生成”的流程,让 AI 像是在参加开卷考试:先去数据库里“翻书”找到事实,再根据事实组织答案。


三、 行动的逻辑:框架、编排与协议

7. LangChain:开发的“胶水”层

LangChain 是一个强大的抽象层,旨在简化开发流程。

  • 核心价值: 它像管道一样将模型、提示词模板和向量库连接起来。有了它,你从 OpenAI 切换到 Google Gemini 可能只需要更改一行代码,极大地提高了系统的灵活性。

8. LangGraph:有状态的“总导演”

当任务需要循环和决策时,简单的线性管道就不够用了。

  • 节点与边: LangGraph 通过节点(步骤)和边(路径)构建工作流。
  • 共享状态(State): 这是它的核心。它维护着一个在各节点间传递的“字典”,记录着当前的文档、评分等信息。基于这个状态,系统可以执行复杂逻辑:例如“如果合规分数低于 75 分,则循环回退到搜索节点重新查阅”。

9. MCP(模型上下文协议):标准化的“USB 接口”

这是连接外部工具(如 GitHub、数据库)的通用标准。它让 AI 具备了“即插即用”的能力,开发者无需为每个工具编写特定的硬编码集成,只需符合 MCP 协议,Agent 就能自主调用。


四、 总结:各组件是如何协同工作的?

构建一个完整的 AI 系统,本质上是让这些组件各司其职、形成闭环:

  1. 准备: 文档经过切分与重叠处理,通过 Embeddings 存入向量数据库
  2. 触发: 用户提问,LangChain 调度 RAG 流程,根据语义意图找回知识。
  3. 决策: LangGraph 根据当前状态判断:是直接回答,还是需要循环修正?
  4. 执行: 如果需要实时数据,通过 MCP 协议调用外部工具。
  5. 产出: LLM 结合所有事实与逻辑推理,输出最终方案。

理清了这些基石,你就已经掌握了从“对话机器人”跨越到“全能 Agent”的底层蓝图。

本文由mdnice多平台发布

一、发布前必知:价值与前提

为什么要发布到 Maven 中央仓库?

  • 全局可访问:任何使用 Maven、Gradle 的开发者都能轻松引入,无需额外配置私有仓库
  • 标准化保障:遵循严格的发布规范,提升组件的可信度与安全性
  • 版本自动管理:中央仓库会妥善保存各版本,避免依赖冲突与版本混乱
  • 社区认可:开源共享是技术成长的重要途径,优质组件能获得更多反馈与迭代

发布前提

  • 组件非敏感信息:中央仓库所有内容公开,严禁发布企业私有代码或涉密逻辑
  • 遵守开源协议:推荐使用 Apache License 2.0 等主流开源协议,避免版权纠纷
  • 准备必要工具:已安装 Maven(配置好环境变量)、可访问 GitHub/Gitee 等代码仓库

二、核心步骤:从配置到发布全流程

第一步:Sonatype 平台配置(获取发布权限)

Maven 中央仓库由 Sonatype 维护,所有发布操作需通过其平台授权:

  1. 访问 Sonatype 官网注册或登录账号,建议绑定常用邮箱
    image.png
  2. 申请 Namespace:登录后进入 Publish 页面,点击 "Add Namespace",格式需遵循反向 DNS 规则
  • 有自有域名:如 www.example.com 对应 com.example
  • 无域名:使用代码仓库地址,GitHub 用户填 io.github. 用户名,Gitee 用户填 io.gitee. 用户名
    image.png
  1. 验证 Namespace:点击 "Verify Namespace",系统会生成验证密钥,需在对应代码仓库创建同名公开仓库,完成后点击 "Verify Namespace",显示 "Verified" 即通过
    image.png
    image.png
  2. 生成访问 Token:点击右上角用户名 →View Account→Generate User Token,复制生成的用户名和密码,后续用于 Maven 认证(仅显示一次,务必保存)
    image.png
    image.png
    image.png

第二步:GPG 密钥配置(保障代码安全)

为防止组件被篡改,中央仓库要求所有发布的文件必须经过 GPG 签名:

  1. 下载安装 GPG:前往 GnuPG 官网,根据系统选择对应版本(Windows 选 Gpg4win,Mac 选 Mac GPG)
    image.png
  2. 生成密钥对:打开终端 / 命令提示符,输入 gpg --gen-key,按提示填写真实姓名、邮箱(与 Sonatype 账号一致),设置密钥密码并牢记
    image.png
    image.png
  3. 记录密钥 ID:生成成功后,找到输出中 "pub" 行后的一串字符(如 519314C3477B2B3122A13EC8123FB84FB9BC06DE),这是你的公钥 ID
    image.png
  4. 上传公钥至公共服务器:执行 gpg --keyserver hkp://keyserver.ubuntu.com:11371 --send-keys 你的密钥ID,让中央仓库能验证签名合法性
  5. 验证 gpg 秘钥: 有两种方式可以验证秘钥,一种是通过 gpg 命令: gpg --keyserver keyserver.ubuntu.com --recv-keys xxxxxx;另外一种方式直接到 https://keyserver.ubuntu.com/秘钥平台查询:
    image.png
    image.png
    出现以上内容表明秘钥发布成功。

第三步:Maven 环境配置(关联认证信息)

打开 Maven 的 settings.xml 文件(通常在 conf 目录下),添加以下配置:

<!-- Sonatype访问权限配置 -->
<servers>
    <server>
        <id>central</id>
        <username>Sonatype生成的Token用户名</username>
        <password>Sonatype生成的Token密码</password>
    </server>
</servers>
<!-- GPG签名配置 -->
<profiles>
    <profile>
        <id>gpg</id>
        <properties>
            <gpg.executable>gpg</gpg.executable>
            <gpg.keyname>你的GPG绑定邮箱</gpg.keyname>
            <gpg.passphrase>你的GPG密钥密码</gpg.passphrase>
            <gpg.useagent>true</gpg.useagent>
        </properties>
    </profile>
</profiles>

第四步:项目 POM 文件配置(标准化组件信息)

修改待发布项目的 pom.xml,补充必要信息(直接复制替换占位符即可):

<project>
    <!-- 核心信息:GroupID需与验证通过的Namespace一致 -->
    <groupId>io.github.你的用户名</groupId>
    <artifactId>组件名称</artifactId>
    <version>1.0.0.RELEASE</version>
    <!-- 必须是正式版本,禁止SNAPSHOT -->
    <url>你的代码仓库地址(如https://github.com/用户名/仓库名)</url>
    <!-- 许可证信息(推荐Apache 2.0) -->
    <licenses>
        <license>
            <name>The Apache License, Version 2.0</name>
            <url>https://www.apache.org/licenses/LICENSE-2.0</url>
        </license>
    </licenses>
    <!-- 开发者信息 -->
    <developers>
        <developer>
            <name>你的姓名</name>
            <email>你的邮箱</email>
        </developer>
    </developers>
    <!-- 代码仓库信息 -->
    <scm>
        <connection>scm:git:你的仓库克隆地址(如https://github.com/用户名/仓库名.git)</connection>
        <developerConnection>scm:git:你的仓库SSH地址(如git@github.com:用户名/仓库名.git)</developerConnection>
        <url>你的仓库网页地址</url>
    </scm>
    <!-- 必要插件配置 -->
    <build>
        <plugins>
            <!-- 源码打包插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>3.3.0</version>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- Javadoc打包插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>2.9.1</version>
                <configuration>
                    <charset>UTF-8</charset>
                    <encoding>UTF-8</encoding>
                    <docencoding>UTF-8</docencoding>
                    <additionalparam>-Xdoclint:none</additionalparam>
                </configuration>
                <executions>
                    <execution>
                        <id>attach-javadocs</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- GPG签名插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-gpg-plugin</artifactId>
                <version>3.1.0</version>
                <executions>
                    <execution>
                        <id>sign-artifacts</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>sign</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- 中央仓库发布插件 -->
            <plugin>
                <groupId>org.sonatype.central</groupId>
                <artifactId>central-publishing-maven-plugin</artifactId>
                <version>0.4.0</version>
                <extensions>true</extensions>
                <configuration>
                    <publishingServerId>central</publishingServerId>
                    <tokenAuth>true</tokenAuth>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

第五步:打包上传与发布

  1. 推送项目代码:将配置好的项目推送到对应的 GitHub/Gitee 仓库(确保仓库公开)
  2. 执行部署命令:打开终端,进入项目根目录,执行 mvn clean deploy -Dmaven.test.skip=true,过程中会提示输入 GPG 密钥密码,输入后等待执行完成
  3. 等待 Sonatype 审核:登录 Sonatype 平台,在 Deployments 中可看到状态(PUBLISHING 为审核中),审核时间通常为几小时到 1 天,状态变为 PUBLISHED 即发布成功
    image.png

三、验证与使用:让别人轻松引入你的组件

发布成功后,可通过 Maven 中央仓库搜索页,输入 GroupID 或组件名称查询你的组件。其他开发者只需在 pom.xml 中添加以下依赖,即可直接使用:

<dependency>
    <groupId>io.github.你的用户名</groupId>
    <artifactId>组件名称</artifactId>
    <version>1.0.0.RELEASE</version>
</dependency>

四、注意事项与避坑指南

  1. 版本不可逆:发布后的版本无法删除或修改,务必做好测试再发布,建议遵循语义化版本规范
  2. 隐私保护:严禁发布包含密钥、敏感业务逻辑的组件,一旦发布无法撤回
  3. 常见错误处理:

    • 提示 "Namespace 不允许":检查 POM 文件的 GroupID 与 Sonatype 验证通过的 Namespace 完全一致
    • 提示 "SNAPSHOT 不被允许":将版本号改为正式版本(如 1.0.0.RELEASE),中央仓库不接受快照版本
    • 签名验证失败:确认 GPG 密钥已上传至公共服务器,且 settings.xml 中 GPG 配置信息正确

至此,你的组件就正式加入 Maven 中央仓库的生态了!从自己用的工具到全球开发者可复用的组件,只差这一套标准化的发布流程。如果遇到问题,可参考 Sonatype 官方文档或留言交流,祝你发布顺利 ~

RustFS 默认通过 9001 端口登录控制台,9000 端口使用 API,为了安全合规,通常采用启用 HTTPS、反向代理(诸如 nginx、traefik、caddy 等)的方式来更加安全的使用 RustFS。本文分享一种更加安全的方式,通过 Cloudflare tunnel 来访问你的 RustFS 实例。

安装 RustFS

RustFS 支持二进制、Docker 以及 Helm Chart 的安装方式,详细方法可以查看官网安装指南。将如下内容写入 docker-compose.yml 文件:

services:
  rustfs:
    image: rustfs/rustfs:latest
    container_name: rustfs
    hostname: rustfs
    environment:
      - RUSTFS_VOLUMES=/data/rustfs{1...4}
      - RUSTFS_ADDRESS=0.0.0.0:9000
      - RUSTFS_CONSOLE_ENABLE=true
      - RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
      - RUSTFS_ACCESS_KEY=rustfsadmin
      - RUSTFS_SECRET_KEY=rustfsadmin
      - RUSTFS_TLS_PATH=/opt/tls
    ports:
      - "9000:9000"  # API endpoint
      - "9001:9001"  # Console
    volumes:
      - data1:/data/rustfs1
      - data2:/data/rustfs2
      - data3:/data/rustfs3
      - data4:/data/rustfs4
      - ./certs:/opt/tls

    networks:
      - rustfs

networks:
  rustfs:
    driver: bridge
    name: rustfs

volumes:
  data1:
  data2:
  data3:
  data4:

运行如下命令

docker compose up -d

即可安装好一个 RustFS 实例:

docker compose ps
NAME      IMAGE                          COMMAND                  SERVICE   CREATED          STATUS          PORTS
rustfs    rustfs/rustfs:1.0.0-alpha.81   "/entrypoint.sh rust…"   rustfs    22 minutes ago   Up 22 minutes   0.0.0.0:9000-9001->9000-9001/tcp, [::]:9000-9001->9000-9001/tcp

配置 Cloudflare tunnel

配置 Cloudflare tunnel 大体分为 域名配置tunnel 配置 两部分。

域名配置

域名配置是为了后期能够更方便的访问 RustFS。

  • 使用 Cloudflare 账号登录 Cloudflare Domain 界面;
  • 在左侧导航栏,Account home,如果你已经有域名,则选择 Onboard a domain,否则可选择 Buy a domain
  • 如果选择 Onboard a domain,点击该选项后,在出现的界面中输入你的域名,然后继续往下走,直到在最后选择 Continue to activation
  • 如果一切顺利,可以在域名管理首页看到添加成功的域名,其 Status 会显示为 Active

image.png

tunnel 配置

  • 使用 Cloudflare 账号登录 Cloudflare Dashboard
  • 在左侧导航栏,选择 Networks -> Connectors,在右侧界面点击 Create a tunnel
  • 在 tunnel 类型中,选择 Select Clouflared
  • Install and run connectors 中,根据 RustFS 实例所在服务器的操作系统信息,选择相应的安装方式。安装完毕后,可以在服务器上查看 cloudflared 服务的状态。运行正常后点击 Next

    systemctl status cloudflared
    ● cloudflared.service - cloudflared
         Loaded: loaded (/etc/systemd/system/cloudflared.service; enabled; preset: enabled)
         Active: active (running) since Fri 2026-01-16 21:18:53 CST; 6 days ago
       Main PID: 2538004 (cloudflared)
          Tasks: 10 (limit: 4375)
         Memory: 31.3M (peak: 38.7M swap: 8.2M swap peak: 15.3M)
            CPU: 18min 16.159s
         CGroup: /system.slice/cloudflared.service
  • Route Traffic 中,配置 HostnameService 信息。

    • Hostname 中填写的域名可用于后续访问 RustFS 实例,可以在 Domain 字段中选择 域名配置 部分添加好的域名。如果想通过子域名访问,也可以在 Subdomain 字段中输入子域名名称。
    • Service 选择服务类型和 URL。对于上述安装的 RustFS 实例,Type 可以选择 HTTP/HTTPS(如果启用了 HTTPS,可选择 HTTPS,否则用 HTTP),URL 为 localhost:9001

    image.png

  • 点击 Complete setup 完成配置。

上述配置结束后,可以在 Connectors 界面看到添加好的 tunnel,如果一切顺利,则可以看到 Status 为绿色的 HEALTHY

在 Hostname 和 Service 设置页面的 Additional application settings 部分,点击 HTTP Settings,在 HTTP Host Header 部分,输入访问 RustFS 的域名,这是为了避免后续使用出现签名错误。

登录验证

恭喜你,如果你顺利完成了上述两部分的配置后,那么现在你就可以通过你配置好的域名来访问 RustFS 实例了。本文配置的域名为 rustfs.xiaomage.vip,所以在浏览器中输入 https://rustfs.xiaomage.vip 即可访问 RustFS 实例:

image.png

输入 rustfsadmin/rustfsadmin 即可登录。

接下来就可以通过多种方式来使用 RustFS 实例了,比如 mcrc 以及 rclone

通过 mc 使用 RustFS

mc 是 Minio 的专属客户端,由于 RustFS 是 S3 兼容的,而且是 Minio 的平替,所以可以用 mc 来操作 RustFS。

前提

mc --version
mc version RELEASE.2025-08-29T21-30-41Z (commit-id=f7560841be167a94b7014bf8a504e0820843247f)
Runtime: go1.24.6 darwin/arm64
Copyright (c) 2015-2025 MinIO, Inc.
MinIO Enterprise License

使用

# 添加 `alias`
mc alias set rustfs https://rustfs.xiaomage.vip rustfsadmin rustfsadmin

# 创建存储桶
mc mb rustfs/hello

# 列出存储桶
mc ls rustfs
[2026-01-23 21:39:36 CST]     0B hello/
[2026-01-23 20:12:59 CST]     0B test/

# 上传文件到存储桶
echo "123456" > 1.txt
mc cp 1.txt rustfs/hello
/tmp/1.txt:                         ██████████████████████████████████████████████████████████████████████████████████ 100.0% 7 B       1 B/s      

# 查看上传的文件
mc ls rustfs/hello
[2026-01-23 21:40:44 CST]     7B STANDARD 1.txt

更多用法可自行探索。

通过 rclone 使用 RustFS

rclone是一个命令行工具,可以对不同云提供商上的文件和目录进行同步。

前提

rclone --version
rclone v1.72.1
- os/version: ubuntu 24.04 (64 bit)
- os/kernel: 6.8.0-71-generic (x86_64)
- os/type: linux
- os/arch: amd64
- go/version: go1.25.5
- go/linking: static
- go/tags: none

使用

  • 配置 rclone

执行 rclone config 命令,根据 RustFS 实例信息,一步步进行配置。配置完成后,会生成一个 ~/.config/rclone/rclone.conf 文件,一般内容如下:

[rustfs]
type = s3
provider = Minio
access_key_id = rustfsadmin
secret_access_key = rustfsadmin
endpoint = https://rustfs.xiaomage.vip
region = us-east-1
force_path_style = true
由于目前 RustFS 还未向 rclone 官方提 PR 以增加 RustFS provider 信息,因此使用 Minio 作为 provider。
  • 开始使用
# 列出存储桶和对象

rclone ls rustfs: --s3-sign-accept-encoding=false
        7 hello/1.txt
    11792 test/1.log
   520512 test/123.mp3
     7394 test/2.log
   147240 test/321.mp3
   

# 查看某个对象内容
rclone cat rustfs:hello/1.txt --s3-sign-accept-encoding=false
123456 

对于其他用法,可以通过 rclone --help 来自行探索。

注意:添加 --s3-sign-accept-encoding=false 参数是因为 Cloudflare 会对 Accept-Encoding 参数进行修改,在 S3 协议中,这种变更会导致 SignatureDoesNotMatch 错误,详情可以查看 RustFS issue

通过 rc 使用 RustFS

rc 是 RustFS 的 Client,用来对 RustFS 进行操作。目前,刚发布 0.1.1。可以使用 cargo 或源码编译安装。

rc --version
rc 0.1.1

目前提供 aliaslsmbrb 等多种常规命令。使用方式和 mc 类似。

# 设置 alias
rc alias set rustfs https://rustfs.xiaomage.vip rustfsadmin rustfsadmin
✓ Alias 'rustfs' configured successfully.

# 列出存储桶
rc ls rustfs
[2026-01-23 13:39:36]         0B hello/
[2026-01-23 13:56:57]         0B rclone/
[2026-01-23 12:12:59]         0B test/

# 创建存储桶
rc mb rustfs/client
✓ Bucket 'rustfs/client' created successfully.

更多用法,可以通过 rc --help 进行查看并自行探索,使用过程中有任何问题,可以在 GitHub Issue中进行反馈。

引言:受够了 Word 排版?

马上就是金三银四招聘季了,相信很多兄弟已经开始准备简历了。

作为一名开发者,我最头疼的事情不是写 Bug ,而是更新简历。每次打开 Word ,都要在“内容”和“格式”之间反复横跳。调一下边距,整个文档炸了;换个字体,两页变成三页了。

更糟糕的是数据安全问题。一旦 PDF 发出去,你就失去了控制权。猎头会把你的简历存进库里,然后在即使你不找工作的时候也疯狂打电话骚扰你,甚至把你的简历倒卖给培训机构。

我就在想:为什么我们不能用写代码的方式写简历?

我们熟悉 Markdown ,我们喜欢 Git 的版本控制,我们崇尚 **"Content is King"**。
于是,趁着春节假期,我开发了 LinkCV —— 一个专为极客打造的 Markdown 简历平台。

👉 体验地址: link-cv.com


核心理念:Markdown + AI + Privacy

在这个项目中,我坚持了三个原则:

  1. Markdown First: 只关注内容。别管字体字号,样式交给 CSS ,排版交给系统。
  2. AI 注入灵魂: 接入 DeepSeek / Gemini 模型。不知道怎么吹自己的项目?写个大概,一键让 AI 帮你润色成 STAR 法则的专业描述。
  3. 隐私至上: 这是我最想做的功能 —— 阅后即焚


亮点功能介绍

1. 阅后即焚 (Burn After Reading) 🔥

这是最酷的功能。
以前发简历是发文件,现在你可以生成一个 加密链接
你可以设置:

  • 有效期: 比如“3 天后失效”。
  • 访问限制: 比如“限制访问 5 次”。

这意味着,你可以发给猎头一个链接。既保证了他们能看、能转给 HR ,又防止了你的简历被无限期留存和倒卖。一旦面试流程结束,链接自动 404 ,你的隐私重新回归你自己。

2. AI 智能润色与翻译 🤖

支持中、英、日多语言一键翻译。
针对程序员的简历,我专门微调了 Prompt ,效果提升显著。

👀 看看效果 (以 Elon Musk 为例):

马斯克英文简历

马斯克中文简历

(上面这两个链接就是用 LinkCV 生成的真实页面,点进去体验一下丝滑的加载速度)

3. 多端适配与完美 PDF 📄

虽然是网页版,但我花了很多精力处理 CSS @media print。直接在浏览器按 Ctrl + P (或点击导出),就能得到一份完美分页、不会截断的 A4 简历 PDF 。


技术栈 (Tech Stack)

为了保证最快的加载速度和最好的 SEO ,我选择了目前 Next.js 生态最新的技术栈:

  • Framework: Next.js 15 (App Router) - 全面拥抱 React Server Components 。
  • Database: PostgreSQL + Prisma - 数据安全存储。
  • Styling: Tailwind CSS - 极简主义设计。
  • AI: Google Gemini / DeepSeek V3 。


写在最后

这是一个开发者写给开发者的工具。

如果你也准备在三月份看机会,或者纯粹厌倦了 Word 排版,欢迎来试用 LinkCV 。工具目前完全免费(后续可能会有 Pro 版,但基础功能管够)。

祝大家在金三银四都能拿到满意的 Offer !🚀

📍 官网直达: link-cv.com
(PS: 也就是个独立开发作品,还在快速迭代中,有 Bug 轻喷,欢迎在评论区提建议!)

引言

在多模态AI系统中,图像处理链已成为一个新兴的安全漏洞点。Trail of Bits的安全研究人员最近揭示了一种巧妙的攻击方法:通过利用图像缩放算法,在高分辨率图像中嵌入隐藏的提示词。这些隐藏指令在图像被AI系统下采样时才会显现,从而触发提示注入,导致潜在的数据泄露或其他恶意行为。该技术已证明对谷歌的Gemini CLI、Vertex AI等生产级系统有效,尽管谷歌视其为默认配置下的非正式漏洞,但它暴露了AI图像预处理链的普遍弱点。

这项攻击源于2020年的图像缩放攻击理论,已被进一步武器化为针对LLM的间接提示注入工具。研究人员开源了Anamorpher框架,允许用户生成和测试此类攻击图像。本文档将从原理入手,逐步剖析攻击机制、工具实现、实际效果及防御策略,帮助读者全面理解这一威胁,并探讨其在AI安全领域的启示。

常见图像缩放算法

在机器学习和图像处理领域,图像缩放算法是预处理链中的关键组件,常用于调整输入图像尺寸以匹配模型要求。这些算法主要通过插值方法计算新像素值,尤其在下采样(缩小图像)时易受攻击影响。现代框架如OpenCV、Pillow(用于PyTorch)、tf.image(TensorFlow)和scikit-image支持多种算法,但实现细节(如抗锯齿选项或默认参数)可能导致跨库差异,从而要求攻击者进行针对性优化。

以下表格概述了常见算法的核心机制、优缺点,以及在ML库中的典型实现和攻击相关性(基于2025年最新实践,包括对多模态AI系统的潜在漏洞):

算法名称 核心机制 优缺点分析 ML库实现与攻击相关性
最近邻插值(Nearest Neighbor) 直接选取最近像素值作为输出像素,无需计算平均或多项式。 速度最快,但易产生锯齿和块状失真,适合实时应用。 Pillow和OpenCV默认支持,默认偏移参数(如Pillow的offset=2)易于精确操纵单个像素,常用于强攻击以最小扰动注入隐藏模式。
双线性插值(Bilinear) 使用2x2邻域像素进行线性加权平均,先水平后垂直插值。 平衡速度与质量,输出稍模糊,抗锯齿效果中等。 OpenCV和TensorFlow广泛使用,支持抗锯齿选项;权重矩阵简单(中心2x2区域),攻击需优化暗区像素以绕过检测。
双三次插值(Bicubic) 基于4x4邻域的三次多项式插值,使用更多像素计算平滑曲线。 输出更平滑、自然,但计算密集,速度较慢。 TensorFlow、OpenCV和scikit-image优化实现;滤波器参数差异大,攻击涉及复杂约束优化,但提供更高隐蔽性。
Lanczos 采用sinc函数对扩展邻域(通常8x8或更大)进行加权滤波。 高质量,减少振铃效应,但易受莫尔纹干扰,计算量大。 scikit-image和SciPy专用于专业处理;权重分布广,攻击需操纵更多像素,适用于弱攻击以最大化视觉差异。
区域平均(Area) 计算目标像素对应原始区域的像素平均值,类似于盒滤波。 简单高效,专用于下采样,避免锯齿,但细节丢失多。 Pillow优化用于图像缩小;平均化特性要求攻击分布扰动于整个区域,易于检测但在ML管道中常见。

这些算法在2025年的ML生态中(如PyTorch、TensorFlow)常与图像增强或超分辨率技术结合使用,例如结合深度学习模型(如ISR)来提升质量,但也增加了攻击表面。 选择算法时需考虑计算效率与视觉保真度,尤其在多模态AI系统中,下采样漏洞可能被利用注入恶意提示。

图像缩放攻击原理

图像缩放攻击是一种针对机器学习预处理阶段的对抗技术,主要利用下采样(图像缩小)过程中的像素丢弃和加权机制。通常,原始图像尺寸超过模型输入要求,因此系统会自动缩放,导致部分像素信息丢失。这一漏洞允许攻击者操纵输入图像,使其在人类眼中正常,但缩放后输出完全不同,从而误导下游AI模型或应用。

关键定义

  • 图像 S:原始源图像(大小 m×n),攻击者希望攻击图像在视觉上与之相似。
  • 图像 A:攻击输入图像(大小 m×n),作为缩放函数的输入,包含隐藏扰动。
  • 图像 D:缩放输出(大小 m'×n'),实际传递给模型的图像。
  • 图像 T:目标图像(大小 m'×n'),攻击者期望D与之匹配,通常嵌入恶意内容(如隐藏提示词)。

攻击目标

攻击有两个主要目标:

  1. 最小化扰动:对S施加最小修改生成A,确保A与S在人类感知中几乎相同(e.g., 使用L2范数量化视觉相似度)。
  2. 输出控制:确保缩放后的D与T高度相似(误差在阈值内),从而实现语义欺骗,如将羊图像缩放后变为狼以绕过分类器。

信号处理视角的解释

攻击根源于下采样与卷积的交互作用。现代缩放过程包括:

  1. 插值计算:使用卷积核(滤波器,如bilinear权重)对原始像素加权求和。
  2. 下采样:根据输出尺寸丢弃像素,仅保留部分信息。

缩放函数本质上是欠采样(surjective),多个输入可映射到同一输出。数学建模为: \text{ScaleFunc}(A) = CL \cdot A \cdot CR 其中CL和CR是基于插值算法的系数矩阵(e.g., bilinear的权重集中在中心区域)。攻击通过逆向求解这些矩阵(经验或源码分析),然后使用二次规划(QP)优化扰动:强攻击为凸优化,弱攻击为凹优化,可分解为行/列子问题以降低复杂度(从O(n²)到向量级)。像素值约束在[0, 255]内,确保A合法。

此原理使攻击独立于具体ML模型,影响框架(如Caffe、TensorFlow)、云服务和浏览器。检测方法包括随机像素移除或相似度度量(如余弦相似度<0.5表示攻击)。

利用Anamorpher进行攻击

该攻击分为两个核心步骤:

  1. 算法识别:使用指纹技术(如棋盘格图案测试)推断AI系统的缩放算法和库。
  2. 攻击图像生成:基于诱饵图像和提示词文本,创建A。开源工具Anamorpher简化此过程,支持4:1下采样比。

Anamorpher工具剖析

Anamorpher的攻击根植于图像缩放的信号处理本质。下采样过程涉及卷积核加权和像素丢弃,本质上是欠采样函数:多个高分辨率输入可映射到同一低分辨率输出。攻击者通过逆向优化,操纵高分辨率图像的特定像素(权重高的区域),确保下采样输出匹配目标payload(如包含提示词的文本图像)。

数学上,缩放函数可近似为矩阵形式: D = CL \cdot A \cdot CR 其中:

  • A 为攻击图像(高分辨率输入)。
  • D 为下采样输出(目标payload T 的近似)。
  • CL 和 CR 为基于插值算法的系数矩阵(e.g., Bilinear的权重集中在中心2x2区域)。

优化目标采用约束最小二乘法:

  • 最小化扰动 \|A - S\|^2(S 为原始图像,确保视觉隐蔽)。
  • 约束 \|D - T\|^2 < \epsilon(输出误差阈值)。
  • 额外约束像素值在[0, 255]内,并考虑伽马校正(sRGB到线性光转换)以匹配人类感知。

Anamorpher利用零空间扰动(null space perturbation)增强自然性:在保持均值和采样像素不变的前提下,添加随机噪声。工具针对4:1下采样比(e.g., 4x4块到1像素)优化,适用于生产AI系统如Gemini CLI和Vertex AI。

攻击算法与实现细节

Anamorpher聚焦三种主流下采样算法:Nearest Neighbor、Bilinear和Bicubic。每个算法的实现考虑了库差异(如OpenCV的BGR vs. Pillow的RGB),并在线性光空间操作以避免伽马失真。生成流程:将提示词文本渲染为目标图像T(4:1比例),然后优化诱饵图像S生成A。

下面以三类常见插值为主线,说明“缩放输出由哪些输入像素主导”。你可以把它理解为:插值核决定了局部像素的权重分配,从而决定了攻击优化的“着力点”。

Nearest Neighbor实现

Nearest Neighbor简单高效,仅选取最近像素。Anamorpher使用Pillow库,默认偏移offset=2(中心像素)。

实现步骤如下

  1. 空间转换:sRGB到线性光(伽马≈2.2)。
  2. 零空间求解:使用SVD分解约束矩阵C(采样像素不变 + 块均值不变),提取基B(14x16矩阵)。
  3. 块优化:遍历4x4块,计算diff(采样像素与T差异)。若lam≤0,直接修改采样像素;否则闭式解最小二乘:

    \min \| \delta \|^2 + \lambda^2 (\sum \delta)^2 \quad s.t. \quad \delta_k = diff

    解:\delta_k = diff,其他\delta_j = -diff \cdot \lambda^2 / (1 + 15\lambda^2)。
    4. 扰动添加:若eps>0,使用B添加零空间噪声。
    5. 伽马校正:对T应用gamma_target(默认1.0)调整亮度。

参数:

  • lam(默认0.25):均值权重,平衡边界可见性和嵌入效果。
  • eps:扰动强度,提升自然感。
  • gamma_target:亮度校正(>1增强对比,<1突出暗部)。
  • offset:采样位置。

此实现针对单像素操纵,隐蔽性高,但易产生块边界。

Bilinear实现

Bilinear使用2x2加权平均。Anamorpher以OpenCV为例,支持抗锯齿。

实现步骤如下

  1. 格式转换:BGR到sRGB,再到线性光。
  2. 权重矩阵:中心2x2区域(w1-w4)。
  3. 暗区掩码:基于dark-frac(默认0.3)限制编辑最暗30%像素,提升隐蔽。
  4. 约束优化:遍历4x4块,计算diff。最小二乘求解(类似Nearest,但约束可编辑像素和权重平方和)。
  5. 零空间扰动 + 更新
  6. 评估:下采样后计算MSE(越小越好)和PSNR(>30dB高质量)。

参数:

  • lam, eps, gamma。
  • dark-frac:编辑比例。
  • anti-alias:抗锯齿开关。

优化聚焦暗区,适用于复杂纹理图像。

Bicubic实现

Bicubic使用4x4三次多项式,平滑性强。实现类似Bilinear,但滤波器更复杂。Bicubic继承Bilinear框架,调整插值函数为三次多项式。Anamorpher强调自定义参数测试,结果因浮点优化而异(建议5次运行)。

参数:同Bilinear,支持额外滤波器调整。

三种实现共享线性空间操作和约束优化框架,但权重分布差异导致Nearest最简单(单像素)、Bicubic最复杂(16像素加权)。

实战攻击

我这里使用Anamorpher自带的示例图像,内含的提示词为从Google Calendar窃取数据并发送至攻击者邮箱,无需确认。 他的机制是payload嵌入高分辨率图像的暗区,利用bicubic插值,通过最小二乘优化调整像素(针对亮度通道的高权重像素),下采样后产生高对比红色背景文本;基于Nyquist-Shannon采样定理利用混叠效应。 我分别在genspark和gemini cli都进行测试,都能成功进行提示词注入。

genspark

image-20251017104506208

gemini cli

image-20251017104700964

防御方法

不要使用图像缩小,而是简单地限制上传尺寸。对于任何转换,尤其是如果需要缩小,最终用户应该始终能够看到模型实际看到的输入预览。

参考文章

https://github.com/trailofbits/anamorpher

https://www.usenix.org/conference/usenixsecurity19/presentation/xiao

题⽬描述

输⼊⼀个⻓度为 n 整数数组,数组⾥⾯可能含有相同的元素,实现⼀个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前⾯部分,所有的偶数位于数组的后⾯部分,对奇数和奇数,偶数和偶数之间的相对位置不做要求,但是时间复杂度和空间复杂度必须如下要求。

数据范围:0 ≤ n ≤ 50000,数组中每个数的值 0 ≤ val ≤ 10000

要求:时间复杂度 O(n),空间复杂度 O(1)

示例 1
输⼊:[1,2,3,4]
返回值:[1,3,2,4]
说明:[3,1,2,4]或者[3,1,4,2]也是正确答案

示例 2
输⼊:[1,3,5,6,7]
返回值:[1,3,5,7,6]
说明:[3,1,5,7,6]等也是正确答案

思路及解答

两次遍历

第一次遍历收集奇数,第二次遍历收集偶数

这种方法虽然简单易懂,但需要额外空间,不符合题目要求

public class Solution {
    public int[] reorderArray(int[] nums) {
        if (nums == null || nums.length == 0) {
            return new int[0];
        }
        
        int[] result = new int[nums.length];
        int index = 0;
        
        // 第一次遍历:收集所有奇数
        for (int num : nums) {
            if (num % 2 == 1) {
                result[index++] = num;
            }
        }
        
        // 第二次遍历:收集所有偶数
        for (int num : nums) {
            if (num % 2 == 0) {
                result[index++] = num;
            }
        }
        
        return result;
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

双指针交换(推荐)

这道题需要奇数在⼀半,偶数在另外⼀半就可以,并没有要求他们之间的顺序,那么就可以⽤双指针,⼀个指针在左边,⼀个指针在右边,⽐如 1,3,5,6,7 :

左指针往右遍历直到找到偶数,也就是 6 停下来,

右指针往左⾛,直到找到第⼀个奇数,也就是 7 停下来。

两者交换:

左指针继续往右边⾛,两个指针相遇,结束,这个时候其实偶数已经全部在右边了。

这个例⼦⾥⾯只经过⼀次交换,如果是多次交换,那么结束的条件同样也是两个指针相遇。

public class Solution {
    public int[] reorderArray(int[] nums) {
        if (nums == null || nums.length <= 1) {
            return nums;
        }
        
        int left = 0;                    // 左指针,从数组开头开始
        int right = nums.length - 1;     // 右指针,从数组末尾开始
        
        while (left < right) {
            // 左指针向右移动,直到找到偶数
            while (left < right && nums[left] % 2 == 1) {
                left++;
            }
            
            // 右指针向左移动,直到找到奇数
            while (left < right && nums[right] % 2 == 0) {
                right--;
            }
            
            // 如果左指针仍在右指针左边,交换奇偶数
            if (left < right) {
                int temp = nums[left];
                nums[left] = nums[right];
                nums[right] = temp;
                left++;
                right--;
            }
        }
        
        return nums;
    }
}
  • 时间复杂度:O(n),每个元素最多被访问一次
  • 空间复杂度:O(1),只使用了常数级别的额外空间

最近刷 X 乎时看到这样一个耐人寻味的的讨论话题,浏览量超 170w,参与讨论的同学也好多。

问题描述是这样的:

“为什么没人走后门当程序员?”

我认真浏览了一圈,心里五味杂陈。

在许多人眼中,程序员是一个高薪的职业。然而,即便程序员们拿着如此令人羡慕的高薪,尽管互联网行业如此火热,但却几乎很少听说有人说走后门想进去。

其实这事情一点也不难理解,这得先从程序员工作的本质说起。

因为程序员这个职业,从根子上来说压根就不靠后门吃饭。

而且程序员这行,恰恰是最混不了日子的,它要求你持续学习,跟上技术迭代,解决一个个具体而棘手的问题。

编程是一个实实在在的技术活,当你的代码运行不起来,它就是运行不起来,你写的系统有漏洞,它就会在某个深夜悄然崩溃,这种刚性特质就决定了程序员这个岗位无法容忍滥竽充数者。

而程序员的门槛,是技术,是能力,走后门也写不出一行能跑通的代码。

退一步说,哪怕就算你真靠后门挤进了公司,项目一上来,分分钟就会露馅。

那些想走后门的人,大概率是想找一个稳当、轻松、有人脉资源的工作。但反思程序员这行,是这样吗?好……好像哪个也不沾边吧……

所以没人走后门干程序员,不是因为这行没前途,而是因为它太实在、太透明、太难伪装。

这是一份必须用真本事去交换的职业,关系在这里,价值被迅速稀释到近乎为零。

另外大家往往有种误解或者说错觉,总觉得程序员赚得多就是香,而实际却忽略了这个高薪背后所付出的代价,这一切都是来源于高强度脑力劳动和长时间脑力付出所带来的回报。

再者,互联网行业的本质是工程化与扁平化。在这个体系里,你是谁、认识谁、从哪来,其实并不太重要,没人会关注你这个,英雄不问出处。

重要的是,你能不能解决问题,能不能为项目创造价值。

所以,当我们回过头来再看,为什么没人走后门干程序员这个问题,其实本身就蕴含着一种误解。它预设了程序员是一个好差事,一个可以让人躺着赚钱的美差。

但事实上,程序员是一份需要真才实学、持续奋斗、直面挑战的工作。你付出多少努力,掌握多少技能,最终都会在你的代码和收入上得到真实的反馈。

当然,这里还有一点需要反思的是:

该说不说,程序员行业的这种去关系化特质,其实某一角度来说也带来了一些副产品。

比方说,技术至上的工作文化有时会导致个体沟通能力的忽视,对硬技能的过度强调可能让软技能的发展有所滞后,另外代码世界的非黑即白有时候也会让人忽略了现实世界的复杂灰度。

这些其实都是程序员文化中值得反思和平衡的地方。

有一说一,其实很多代码之外的东西对现如今的生存也很重要,因为思维如果不开阔出来的话,路可能就会越走越窄了。

其实很多程序员在年龄大了之后越来越焦虑的一个重要原因就是因为生存技能太过单一了,所以千万不要给自己设限,不要把目光仅仅聚集在自己的一亩三分地上,还是要多培养一些其他方面的一些软实力,会很有帮助。

不知道大家有没有看过《软技能》那两本书,讲的就是代码之外的一些软技能和经验,里面提到了很多有关职场的分析,自我提高的一些路径,个人的持续学习和成长,甚至包括像理财、健身、时间管理、心态调整等等。

有意识地去关注这方面东西的原因在于可以帮助自己把思维给开阔出来,毕竟很多时候有必要跳出来看问题,这时候这些软技能往往就能发挥作用了。

另外,程序员作为一个有个性的创造性群体要专注精进技术这本身没错,但是职场毕竟也是一个充满人情世故的江湖,所以掌握一些通用的职场规则、沟通技巧,甚至是向上管理的艺术,这对于程序员来说也是十分有必要的。

仰望星空,脚踏实地,埋头赶路的同时也不要忘记时常抬头看看周围的环境和机会。

那关于这个问题,你的看法是什么呢,如果有不同的见解,也欢迎一起来分享交流~

注:本文在GitHub开源仓库「编程之路」 https://github.com/rd2coding/Road2Coding 中已经收录,里面有我整理的6大编程方向(岗位)的自学路线+知识点大梳理、面试考点、我的简历、几本硬核pdf笔记,以及程序员生活和感悟,欢迎star。

有这个问题是因为看最近的阮一峰博客里的文章,有感而发。我总觉得以目前 AI 的能力,只要有好的想法,起码不愁前期的实现。我其实认同这篇文章里的一部分说法,但是我又觉得虽然 AI 让开发变得容易,让更多人竞争,但是 AI 也赋予了我们超快速落地某个想法能力,让一个人也能完成真正的敏捷开发,这也是一种跨越式的进步。

我打算在过年假期选择一个方向,完成一个项目的最初版本,我想测试一下独立开发这条路现在还行不行的通。难道普通程序员就一定要给别人打工,然后被优化,然后不知所措。

做为一个今年 30 岁的人来说,我能清晰的感觉到我站在了一个特殊的时代节点上,我觉得我此刻选择可能会改变自己未来的时间线。

今天金价突破了 5000$/盎司,作为一个仅有余额宝、朝朝宝理财经验的小白,我入局了......

由于第一次投资,我真的忍不住隔几分钟刷一次手机,但是上班又不太好.....于是做了一个 JetBrains IDE 的实时金价显示插件。

功能特性

如下图,如果没进行配置的话仅仅能够显示国际金价,如果想显示国内金价可以配置一个免费的 API KEY(每天可以免费调 50 次)

  • 状态栏实时显示金价
  • 自动定时更新(默认 15 分钟)
  • 可配置更新间隔
  • 点击状态栏可手动刷新
  • 鼠标悬浮显示详细金价信息
  • 支持免费 API 接入真实国内金价
  • 持仓收益计算(累计收益、今日收益)

安装

目前我已经将插件上传到 Jetbrains 的商店中心了,但是还没有审核通过。。。如果 IDEA 插件中心大家搜索不到,也可以自行到 github 下载

今日金价 github-release

  1. 打开 JetBrains IDE ( IntelliJ IDEA 、GoLand 、PyCharm 等)
  2. 进入 Settings/PreferencesPlugins
  3. 点击齿轮图标 → Install Plugin from Disk...
  4. 选择 observe-gold-jetbrains-1.0.0.zip
  5. 重启 IDE

配置

进入 Settings/PreferencesTools今日金价

基础配置

  • 金价类型
    • XAU(国际金价):默认选项,显示美元/盎司价格
    • AU9999(国内金价):需要配置 API Key ,显示人民币/克价格
  • 更新间隔:设置自动更新的时间间隔(分钟),默认 15 分钟

配置国内金价及计算收益

支持免费 API 接入真实国内金价,可以配置持仓克重和成本用于计算你的 历史收益和今日收益

配置国内金价步骤

步骤 1:获取免费 API Key

  1. 访问 聚合数据 - 黄金价格 API
  2. 注册账号(免费)
  3. 申请"黄金价格"API (每天 50 次免费调用)
  4. 复制你的 API Key

步骤 2:配置插件

  1. 打开 Settings/PreferencesTools今日金价
  2. 在"金价类型"下拉框中选择 AU9999
  3. 在"聚合数据 API Key"输入框中粘贴你的 API Key
  4. 点击 Apply 保存设置

步骤 3:验证

  • 重启 IDE 或点击状态栏金价手动刷新
  • 状态栏应显示:AU9999: 1143.05 ▲ 2.95%
  • 鼠标悬浮可查看详细信息

配置持仓收益(可选)

如果你持有黄金,可以配置持仓信息来实时查看收益情况:

配置步骤:

  1. 打开 Settings/PreferencesTools今日金价
  2. 在"持仓克重"输入框中输入你的黄金持仓克数(例如:100 )
  3. 在"成本均价(元/克)"输入框中输入你的平均买入价格(例如:520.50 )
  4. 点击 Apply 保存设置

收益计算说明:

  • 累计收益 = (当前价格 - 成本均价) × 持仓克重
  • 今日收益 = 今日涨跌额 × 持仓克重

配置完成后,鼠标悬停在状态栏金价上,会在详细信息中显示:

  • 持仓信息(克重和成本价)
  • 累计收益(红色表示盈利,绿色表示亏损)
  • 今日收益(红色表示盈利,绿色表示亏损)

数据来源

  • 国际金价( XAU ):新浪财经 COMEX 黄金期货实时数据
  • 国内金价( AU9999 ):上海黄金交易所 Au99.99 实时数据(需配置 API Key )

今天金价突破了 5000$/盎司,作为一个仅有余额宝、朝朝宝理财经验的小白,我入局了......

由于第一次投资,我真的忍不住隔几分钟刷一次手机,但是上班又不太好.....于是做了一个 JetBrains IDE 的实时金价显示插件。

目前我已经将插件上传到 Jetbrains 的商店中心了,但是还没有审核通过。。。如果 IDEA 插件中心大家搜索不到,也可以自行到 github 下载

今日金价 github-release

¥25 开了黄金会员只是免了开头的 120 秒广告

还有:

  • 暂停广告(视频被自动缩小)
  • 内嵌在剧里面的广告(几秒)
  • 时不时的弹窗广告(很小,每集出现几次,持续几秒)

搜了下还不能关mental_boom

于是下载了国际版,发现不用会员没有任何片头广告,也没有上述任何一种广告,主页也基本没有

合着逮着自己人使劲恶心呗 🤮

有没有大佬指条明路,只想省心,愿意花钱,只求不被恶心

Smarty 模板压缩 HTML,去除 HTML 中的回车换行空白注释等

方法 1

在创建对象时使用 registerFilter 绑定匿名函数

$smarty = new Smarty();
// 压缩HTML
$smarty->registerFilter("output", function ($html) {
    $html = preg_replace(':\s+//.*?\n:', '', $html);
    $html = preg_replace('/<!--\s*[^[][^!][^&lt;].*?-->/s', '', $html);
    $html = preg_replace('/\/\*.*?\*\//s', '', $html);
    $html = preg_replace('/&gt;\s*&lt;/s', '&gt;&lt;', $html);
    $html = preg_replace('/(\s)+/s', ' ', $html);
    return trim($html);
});

方法 2

修改文件 sysplugins/smarty_template_source.php 中的方法:public function getContent()

public function getContent()
{
    // return isset($this->content) ? $this->content : $this->handler->getContent($this);

    // 压缩HTML
    $html = isset($this->content) ? $this->content : $this->handler->getContent($this);
    $html = preg_replace(':\s+//.*?\n:', '', $html);
    $html = preg_replace('/<!--\s*[^[][^!][^&lt;].*?-->/s', '', $html);
    $html = preg_replace('/\/\*.*?\*\//s', '', $html);
    $html = preg_replace('/&gt;\s*&lt;/s', '&gt;&lt;', $html);
    $html = preg_replace('/(\s)+/s', ' ', $html);
    return trim($html);
}

如果设置了模板缓存,需删除缓存文件后才生效

点赞 + 关注 + 收藏 = 学会了

整理了一个n8n小专栏,有兴趣的工友可以关注一下 👉 《n8n修炼手册》

用 n8n 做自动化工作流时,可能会遇到一个头疼的问题:想调用豆包、千问、文心一言、Kimi 这些常用国产大模型,却发现 n8n 默认节点里根本找不到它们。

别方!n8n 虽然没自带这些节点,但它支持“自定义扩展”。

本文提供3个解决方案,你看看哪个适合你。

社区节点

n8n有个“社区节点”功能,相当于一个“节点市场”,里面有很多开发者已经做好的节点,如果能找到模型提供商提供的节点(也许你的需求不是使用大模型,但一般也能找到功能相似的节点),我们直接安装就能用,不用自己动手配置。

在 n8n 的设置页面,里面有一个「Community nodes」面板,在这里可以下载第三方节点。

通过HTTP节点和大模型交互

如果社区节点没搜到你要的大模型节点,可以用「HTTP节点」是 n8n 的“万能节点”,只要这些大模型有公开的 API 就能用它接入。

我在《『n8n』通过接入DeepSeek了解HTTP节点》 里详细讲解了如何使用「HTTP节点」跟 DeepSeek 交互。

兼容 OpenAI 节点的大模型

从2022年底AI大模型开始在民间流行起来到2025年,OpenAI 都是行业龙头。虽然现在被 Gemini 反超了,但 OpenAI 已成为行业标准。

本文标题提到的几个国产大模型,以及 DeepSeek 都提供了兼容 OpenAI 的接口(这是前提!!!如果不兼容 OpenAI 规范的是不能使用这套方案的!!!)

简单来说,就是在 n8n 里用「OpenAI 节点」,但服务地址和模型都是用其他家提供的😁

我以 Kimi 为例对接一下。

打开 Kimi 后台申请一个 API Key 👉 https://platform.moonshot.cn/console/api-keys

⚠️⚠️⚠️

注意!这个 Key 只展示一次,复制保存好以免弄丢了。同时不要泄露给陌生人,不要上传到公开仓库,以免产生不必要的损失!!!

⚠️⚠️⚠️

来到 n8n 这边,添加模型时使用「OpenAI Chat Model 节点」

“Credential to connect with”这项选择“Create new credential”,创建一个新的凭证(如果你之前没对接过接下来要使用的大模型服务的话)

这个凭证最好改一下名字,以免自己以后看不懂。

API Key 填入刚刚在 Kimi 申请的 Key。

Base URL 填入 https://api.moonshot.cn/v1,这是 Kimi 文档提供的。

填入这几项后,点击弹窗右上角橙色的保存按钮(Save),它会自动测试能不能联通这个服务。如果出现上图绿色提示框(Connection tested successfully)的话就证明服务通了。

回到模型配置这边,选择刚刚创建好的凭证,在 Model 里就能看到 Kimi 提供的一系列可调用的大模型了。

这个节点也可以根据所调用的模型改一下名字。这么做的好处,等过两天再回来看你自己的工作流时你就知道了。

“Use Responses API”这项也要关掉!!!

回到工作流,打开对话窗口就可以开始和 Kimi 聊天了。


以上就是本文的全部内容啦,想了解更多n8n玩法欢迎关注《n8n修炼手册》👏

如果你有 NAS,我非常建议你在 NAS 上部署一套 n8n,搞搞副业也好,帮你完成工作任务也好 《『NAS』不止娱乐,NAS也是生产力,在绿联部署AI工作流工具-n8n》

点赞 + 关注 + 收藏 = 学会了

萌翻是一款针对多语言学习和使用者的 AI 综合应用。

内测已经结束,免费额度减少,为感谢社区支持,给大家发一个福利:

发福利啦,兑换码:5F4DCC3B

50 次/天免费翻译额度,有效期 2026 年 12 月 31 日,限额 100 人,兑完为止。

兑换方式:注册网站后,我的-设置-权益兑换

AI 翻译:萌翻免费 AI 引擎、以及 Deepseek 、通义千问 Qwen 、智谱 GLM 等高级引擎

https://translate.cuteslator.com/

AI 词典:AI 词典:中文、英语、法语、西班牙语、韩语、日语词典,一站覆盖

https://www.cuteslator.com/

浏览器插件:划词翻译、AI 生词本

吸收了大家的意见,目前已经把浏览器翻译插件做出来,得到了 V2EX 技术同学的帮忙。

https://translate.cuteslator.com/extension

请兑换成功同学在评论区留言,方便统计

1.抢票之前要先挑好抢哪趟车次。
2.最好抢始发站到终点站的车次。
3.提前在手机 12306APP 上找到哪一天有票。
4.选择有票的那一天,先点击预定,进入信息填写页面,选择好乘车人和座位信息。
5.点击上面的日历进入选择日期页面。
6.等到开票的那一瞬间选择你要乘车的日期。
7.下单。


最近几年我都是靠上面的步骤抢到票的。只有一次没抢到,然后立马换了另一个又贵又慢的车次抢到了。

补充下开票时间(这两个地址是刚刚找到的,准确性待验证,明天可以验证下):
https://www.12306.cn/mormhweb/zxdt/201411/t20141126_2316.html
https://www.12306.cn/index/view/infos/sale_time.html

构建过 AI agent 的人大概都遇到过这种情况:LLM 返回的数据"差不多"是你要的但又不完全对。比如会遇到字段名拼错了数据类型不对,或者干脆多了几个莫名其妙的 key。

这是问题出在哪?当前主流的 agentic AI 系统处理输出的方式太原始了,比如说脆弱的 JSON 解析、基于 prompt 的 schema 约束、各种后处理 hack。这套东西在 demo 里能跑通,到了生产环境就是定时炸弹。

PydanticAI 提供了一个根本性的解决方案:类型安全的 LLM 响应。它能把 AI 输出直接转换成经过验证的 Python 对象,配合 CrewAI 这类 agent 框架使用效果是相当不错的。

本文会介绍 PydanticAI 的核心概念,解释为什么类型化响应对 agent 系统如此重要并给出与 CrewAI 集成的实际代码示例。

LLM 输出的核心问题

Agentic 框架功能很强,但在最基础的环节:数据契约上,表现得相当糟糕。

典型的 agent 开发流程是这样的:先让 LLM 返回 JSON,然后祈祷它遵循你定义的 schema,不行就加重试逻辑,最后发现还是得手写验证器。这套流程走下来,agent 变得不稳定,失败时没有任何提示,调试起来痛苦万分。

类型化系统正是为了解决这个问题而存在的。

PydanticAI 是什么


PydanticAI 把 LLM、Python 类型系统和 Pydantic 模型组合在一起。核心理念很简单:LLM 响应必须符合预定义的 Python 类型,不符合就直接报错。

没有残缺数据,没有静默失败,没有靠猜。

为什么 CrewAI 需要这个

CrewAI 的强项在于多 agent 协调、角色分配和任务分解。但 agent 之间的数据传递、工具调用、记忆持久化,都需要结构化输出作为基础。这正是 PydanticAI 填补的空白——它提供了一个可靠的契约层。

安装

 pip install pydantic-ai crewai openai

设置 OpenAI API key:

 export OPENAI_API_KEY="your-key"

第一个示例:类型化响应

从最简单的场景开始。

定义一个响应模型:

 from pydantic import BaseModel  
   
 class Summary(BaseModel):  
     title: str  
     key_points: list[str]  
     confidence: float

这不是注释或文档,这是硬性契约。

创建 agent:

 from pydantic_ai import Agent  
from pydantic_ai.models.openai import OpenAIModel  

model = OpenAIModel("gpt-5-mini")  

agent = Agent(  
    model=model,  
    result_type=Summary  
 )

运行:

 result = agent.run_sync(  
     "Summarize the benefits of typed AI agents"  
 )  
   
 print(result.title)  
 print(result.key_points)  
 print(result.confidence)

这里发生了什么?LLM 被强制返回符合 Summary 结构的数据,验证自动进行,输出不合法会触发重试或直接失败。这才是可以上生产的 LLM 输出。

Agent 间的数据契约

来看一个更实际的例子:两个 agent 协作。

研究 agent:

 class ResearchResult(BaseModel):  
    topic: str  
    findings: list[str]  

research_agent = Agent(  
    model=model,  
    result_type=ResearchResult  
 )

写作 agent,负责消费研究 agent 的输出:

 class BlogDraft(BaseModel):  
    headline: str  
    sections: list[str]  

writer_agent = Agent(  
    model=model,  
    result_type=BlogDraft  
 )

协作流程:

 research = research_agent.run_sync(  
     "Research typed LLM outputs in AI agents"  
 )  
   
 draft = writer_agent.run_sync(  
     f"Write a blog using these findings: {research.findings}"  
 )

整个过程没有 JSON 解析,不用猜测 schema,Python 对象在 agent 之间直接流转。

与 CrewAI 集成

CrewAI 负责编排,PydanticAI 负责类型正确性,这种组合越来越常见。

 from crewai import Agent as CrewAgent, Task  

analysis_agent = CrewAgent(  
    role="Analyst",  
    goal="Generate structured insights"  
)  

task = Task(  
    description="Analyze market trends in AI tooling",  
    agent=analysis_agent  
 )

加入类型化执行层:

 typed_agent=Agent(  
     model=model,  
     result_type=ResearchResult  
 )  
   
 result=typed_agent.run_sync(task.description)

CrewAI 处理 agent 的角色和任务分配,PydanticAI 保证输出的结构正确。

类型化如何改变可靠性

没有类型约束的 agent 系统会出现各种问题:agent 凭空生成不存在的 key,下游步骤因为数据格式错误而静默失败,排查问题时无从下手。

用了 PydanticAI 之后,无效输出会被立即拒绝,重试自动触发,这样bug 在早期就会暴露出来。这其实是软件工程领域早就有的实践:API 用 schema 约束,数据库用约束条件,编译器做类型检查,Agentic AI 只不过是终于跟上了这个标准。

生产环境用例

PydanticAI 加 CrewAI 的组合适合这些场景:研究类 agent、内容生成流水线、数据提取任务、业务流程自动化、AI 辅助决策系统。只要你的应用对输出结构有要求,这套方案就值得考虑。

不过有几个做法应该避免:让 agent 返回原始字符串然后自己解析,用 eval() 处理 JSON(安全隐患太大),盲目相信"格式良好"的 prompt 能约束输出,在 agent 之间传递未经验证的数据。

类型化不是额外负担,是风险控制。

总结

Agentic AI 发展很快,但速度如果没有结构做支撑,系统就会变得脆弱。PydanticAI 把软件工程的类型规范带入了 LLM 系统,让 agent 更安全、更可预测、更容易扩展。

当 AI 输出变成真正的 Python 对象,agent 就不再只是 demo,而是可以正式投入使用的系统。

https://avoid.overfit.cn/post/2a20c5c4c1394c92a252a04388f8e26e

作者:Er.Muruganantham

2026年1月,我实操后最推荐的6个AI开源项目(上)

不是n8n,不是langchain,不是dify。这6个项目是我陆陆续续在一两周的时间里,从十几个项目中筛出来的——解决真实痛点、上手门槛低、社区活跃。

为什么我要写这篇"非主流"推荐

打开任何一个AI技术社区,你都能看到铺天盖地的教程:n8n工作流搭建、langchain入门、dify部署指南……

这些项目当然好。但说实话,它们太"烂大街"了。

不是说用的人多就不好,而是:当一个工具变成"标配",你用它已经不算优势,只是及格线。

我在过去一段时间,常常带着一个问题去GitHub和Hacker News上翻项目:有没有那种"知道的人不多,但用过的人都说好"的AI开源项目?

翻了十几个,最后留下了6个。它们的共同特点:

解决一个明确的痛点,不是"有了更好",而是"没有不行"

上手门槛低,基本pip install就能跑,环境配置很简单

社区活跃,issues会有人关注并回复,且迭代频繁

平常业务太忙,先抽时间写了这一篇讲前3个,下一篇我们讲后3个,欢迎关注。

第一个:Browser-Use(让AI操作浏览器的"手")

场景:我需要自动化填写表单、抓取动态渲染的页面、模拟用户登录。传统爬虫要么被反爬拦住,要么一改页面结构就废了。

Browser-Use解决的问题很直接:让LLM直接操作浏览器,像人一样点击、输入、导航。

其实算是个manus的开源小平替。

你给它一个任务,比如"去某个网站搜索XX,把前10条结果的标题和链接存下来",它会自己打开浏览器、输入搜索词、翻页、提取内容。不需要你写XPath,不需要分析网页结构。

数据:76k stars,283位贡献者,几乎每天都有更新。

适用场景

需要模拟用户操作的自动化任务

动态渲染页面的数据采集

需要登录、点击、填表的流程自动化

局限:对延迟敏感的场景不适合(毕竟要启动浏览器);而且反爬特别严格的网站可能还是会被拦。

规避动作:先小规模测试;考虑云端沙箱方案。

第二个:Mem0(给AI装上"长期记忆")

场景:大模型的长上下文场景下效果差算是个老生常谈了。对话一长就"失忆",或者对需求不明晰,每次都要重复上下文。用户说"我上周跟你说过我喜欢简洁的回答",它一脸茫然。

这是所有做AI产品的人都遇到过的问题:上下文窗口是短期记忆,但用户需要的是长期记忆。

Mem0就是解决这个问题的。它给Agent加了一层持久化的记忆层,能跨会话记住用户的偏好、历史信息、重要事实。

技术上,它不是简单地把对话存数据库。它会自动提取"值得记住的信息",做去重、更新、关联。你可以理解为:如果上下文窗口是便签纸,Mem0就是一个会自动整理的笔记本。

官方数据:集成Mem0后,Agent的回答准确率提升26%,响应速度快91%(因为不用每次都塞一大段历史上下文)。

数据:45.8k stars,YC S24孵化,2025年底刚发布1.0正式版。

适用场景

需要跨会话记忆的AI助手

个性化推荐、用户画像

多轮对话的复杂任务

局限:对实时性要求极高的场景还是会有一定延迟;数据隐私敏感的场景需要评估本地部署选项。

规避动作:评估本地部署选项;敏感数据做脱敏。

第三个:PageIndex(不用向量数据库的RAG)

场景:我用传统RAG做文档问答,发现一个痛点:"相似"不等于"相关"。用户问"公司去年的利润是多少",向量检索可能返回"公司今年的收入"——相似度很高,但答非所问。

PageIndex的思路完全不同:不用向量数据库,不做文档切片,用推理代替检索。

它的做法是:先让LLM理解整个文档的结构,建立一个"内容索引"。用户提问时,不是去算向量相似度,而是让LLM"推理"应该看哪些页面。

打个比方:传统RAG像关键词搜索,PageIndex像请了一个读过整本书的专家帮你翻页。

我尝试用它处理一份80页的财务报告,问了10个问题,准确率明显比传统RAG高。

官方在FinanceBench基准测试上跑出了98.7%的准确率。

数据:6.3k stars,增长很快,FinanceBench榜单第一。

适用场景

长文档、复杂文档的问答

对准确率要求高的场景(财务、法律、医疗)

文档结构复杂、切片效果差的场景

局限:需要实时更新的文档不太适合(索引建立需要时间);超大规模文档集可能成本较高。

规避动作:与传统RAG混合使用——热数据用向量库,冷数据用PageIndex。

写在最后:本篇小结

这3个项目分别解决了:

Browser-Use:AI不能操作浏览器 → 让LLM像人一样点击、输入

Mem0:AI没有长期记忆 → 跨会话的持久化记忆层

PageIndex:RAG检索"相似但不相关" → 用推理代替向量检索

下一篇我会继续介绍后3个项目,都是围绕"上下文工程"的:

MarkItDown:把各种文档转成LLM能读的Markdown

Instructor:让LLM返回结构化数据

Semantic Router:10ms级别的意图路由

明天我会抽时间更新下一篇,讲另外3个项目:

Unsloth(让微调快2倍、省70%显存)

Pathway(实时流处理+LLM管道)

Agent-Lightning(用RL训练任何Agent)。

届时也会更新在同一个合集里,关注我不错过更新~

我是Carl,大厂研发裸辞的AI创业者,只讲能落地的AI干货。

更多AI趋势与实战,我们下期见!

最近全网很火的 clawbot,懒猫商店可以一键安装使用了,真正的 7 X 24 小时个人 AI 助手,在你睡觉时帮你整理收件箱、在你喝咖啡时帮你调研竞品、在你灵感枯竭时帮你搜集素材...购买懒猫微服,即可体验。

本次新春活动再送一台 创始珍藏版懒猫微服 给大家!!!
祝大家新的一年顺风顺水,阖家欢乐,事业有成!!!

历史抽奖贴一,490+参加

历史抽奖贴二,500+参加

历史抽奖贴三,1000+参加

🔗 官方阵地

官方网站: lazycat.cloud

创始人博客: manateelazycat

社交媒体: X @manateelazycat

🎁 活动奖品总价值约 15399 元

  • 一等奖:价值 5399 元「懒猫微服」 LC-02 ( 16G+2T )* 1 (创始珍藏版!
  • 二等奖:价值 299 元「 CHERRY MX1.1 茶轴机械键盘」+ 300 元 懒猫微服 LC03 新年购机券 * 5
  • 三等奖:价值 700 元 懒猫微服 LC02 新年购机券 * 10 (最后 10 台 LC-02 了)


📅 活动共计两轮,活动时间:1 月 26 日 - 1 月 30 日

第一轮

  • 活动时间1 月 26 日 - 1 月 28 日
  • 开奖时间周二( 1.28 )晚上 20:00
  • 本轮奖品
    • 二等奖:价值 299 元「 CHERRY MX1.1 茶轴机械键盘」+ 300 元 懒猫微服 LC03 新年购机券 * 3
    • 三等奖:价值 700 元 懒猫微服 LC02 新年购机券 * 5

第二轮

  • 活动时间1 月 28 日 - 1 月 30 日
  • 开奖时间周五( 1.30 )晚上 20:00
  • 本轮奖品
    • 一等奖:价值 5399 元「懒猫微服」 LC-02 ( 16G+2T )* 1
    • 二等奖:价值 299 元「 CHERRY MX1.1 茶轴机械键盘」+ 懒猫微服 300 元 优惠券 * 2
    • 三等奖:价值 700 元 懒猫微服 LC02 新年购机券 * 5


⚠️ 如何参加(必读)

本次活动主要基于推特联动及微信群公示,请按以下步骤操作:

  1. 点赞、评论并收藏本条推文

  2. 本贴留言并写上口号: 懒猫微服:自带内网穿透和沙箱 100% 隔离,超级安全,更适合

  3. 扫码或加评论区微信进交流群


📢 开奖与领奖

  • 抽奖方式:本次抽奖依据推特 @ID 名单统计,使用抽奖工具开奖,名单打乱,公平公正,结果第一时间公布在微信群。(需扫码或添加评论区微信进群
  • 领奖方式:通过微信群联系我们的工作人员领奖,中奖后进群无效,参加抽奖的大佬进我们的任意交流群即可,以前参加活动已经进群的大佬不用重复进群。

🚨 特别提醒

  1. 大佬一定要 进我们的交流群 才能获得中奖资格哦。
  2. 中奖用户需要把“V2ex 参与活动截图以及 V2EX 主页”作为凭证发给工作人员验证。
  3. 活动奖品和优惠券 不能折现,不与其他活动叠加。
  4. 中奖后请在 三个工作日内 领取奖品,过期不候。


🛒 购买渠道和优惠

  1. 添加评论区工作人员微信备注“新年优惠”获取最新优惠资格。
  2. 全球包邮,UPS 红牌快递,3 天到您家

购买链接:京东购买 | 海外购买

专属小程序咨询:17820700354, 17612774028, 18627819480, 18627819427

注:名单统计以 V2EX ID 为准,每人限中奖一次。

Hacker Flag 5pqX5Y+3OiBEb24ndCBQYW5pY++8jOWKoDE3ODIwNzAwMzU077yM5Lqr5Y+X5pu05aSn5LyY5oOgCg==

产品资料

TinyProTinyEngine 是 OpenTiny 开源生态的重要组成部分:

  • TinyPro 提供企业级后台系统模板
  • TinyEngine 提供灵活强大的低代码引擎

本项目在 TinyPro 中深度集成了基于 TinyEngine 的低代码设计器,通过 插件化架构 构建出可扩展的低代码开发平台。

借助它,你只需在可视化设计器中完成页面设计,就能一键导入 TinyPro,并自动生成菜单、权限及国际化配置,实现真正的 “所见即所得” 式开发体验。

整体架构

lowcode-designer/
├── src/
│   ├── main.js              # 应用入口
│   ├── composable/          # 可组合逻辑
│   ├── configurators/       # 配置器
├── registry.js              # 插件注册表
├── engine.config.js         # 引擎配置
└── vite.config.js          # 构建配置

image.png

核心组成部分

  1. TinyEngine 核心:提供低代码设计器的基础能力
  2. 插件系统:通过插件扩展功能
  3. 注册表机制:统一管理插件和服务
  4. 配置器系统:自定义组件属性配置

核心特性

  • 智能代码生成:基于可视化设计自动生成符合 TinyPro 规范的 Vue 3 + TypeScript 代码
  • 🔐 自动认证管理:智能获取和管理 API Token,支持多种认证方式
  • 🎯 一键集成:自动创建菜单、配置权限、添加国际化词条
  • 🛠️ 代码转换:将 TinyEngine 生成的代码自动转换为 TinyPro 项目兼容格式
  • 💾 本地保存:支持将生成的文件保存到本地文件系统
  • 🎨 可视化配置:提供友好的 UI 界面进行菜单和路由配置

快速开始

安装

使用 TinyCli 可以快速初始化 TinyPro 模版

tiny init pro 

image 1.png

启动低代码设计器

cd lowcode-designer
pnpm install
pnpm dev

启动前端与后端

cd web
pnpm install
pnpm start

cd nestJs
pnpm install
pnpm start

启动完成后,访问 👉 http://localhost:8090 即可体验低代码设计器。

使用流程

image 2.png

设计页面:在 TinyEngine 可视化编辑器中设计页面

image 3.png

点击出码按钮:点击工具栏中的”出码”按钮

image 4.png

配置菜单信息:在弹出的对话框中填写菜单配置信息

生成预览:点击”生成预览”查看将要生成的文件

image 5.png

完成集成:点击”完成集成”自动创建菜单、分配权限并保存文件

image 6.png

接下来我们就可以直接去 TinyPro 直接看到页面效果

image 7.png

TinyPro Generate Code 插件解析

插件目录结构

generate-code-tinypro/
├── package.json              # 插件包配置
├── src/
│   ├── index.js             # 插件入口
│   ├── meta.js              # 元数据定义
│   ├── Main.vue             # 主组件
│   ├── SystemIntegration.vue # 功能组件
│   ├── components/          # 通用组件
│   │   ├── ToolbarBase.vue
│   │   ├── ToolbarBaseButton.vue
│   │   └── ToolbarBaseIcon.vue
│   ├── composable/          # 可组合逻辑
│   │   ├── index.js
│   │   └── useSaveLocal.js
│   └── http.js              # HTTP 服务
├── vite.config.js           # 构建配置
└── README.md                # 文档

代码生成流程

const generatePreview = async () => {
  // 1. 获取当前页面的 Schema
  const currentSchema = getSchema();

  // 2. 获取应用元数据(i18n、dataSource、utils等)
  const metaData = await fetchMetaData(params);

  // 3. 获取页面列表和区块信息
  const pageList = await fetchPageList(appId);
  const blockSchema = await getAllNestedBlocksSchema();

  // 4. 调用代码生成引擎
  const result = await generateAppCode(appSchema);

  // 5. 过滤和转换生成的代码
  const transformedFiles = filteredFiles.map((file) => ({
    ...file,
    fileContent: transformForTinyPro(file.fileContent),
  }));
};

TinyPro 与 TinyEngine 通信

当用户在低代码设计器中点击“完成集成”时,插件首先通过 Token Manager 向认证接口 /api/auth/api-token 请求并获取访问凭证(Token),随后利用该 Token 调用一系列后台接口,包括国际化 API、菜单 API 和角色 API。插件通过这些接口自动完成 页面国际化词条创建、菜单注册、角色查询与权限分配 等步骤。整个过程中,HTTP Client 统一负责与后端通信,而返回的数据(菜单信息、角色信息、权限配置等)会实时更新到本地,最终实现了从页面设计到系统集成的一键闭环,使 TinyEngine 生成的页面能无缝接入 TinyPro 系统。

image 8.png

总结

通过 TinyPro 与 TinyEngine 的深度融合,我们实现了从「可视化设计」到「系统集成」的完整闭环,让不会写代码的用户也能轻松构建出高质量的前端页面

用户只需拖拽组件、填写配置、点击“出码”,插件便会自动生成符合 TinyPro 标准的代码,并完成菜单、权限、国际化等系统级配置。

这一过程无需手动修改代码或后台配置,就能一键完成页面创建、接口绑定与权限分配,实现真正意义上的「低门槛、高效率、可扩展」的前端开发体验。

关于OpenTiny

欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~
OpenTiny 官网:https://opentiny.design
OpenTiny 代码仓库:https://github.com/opentiny
TinyPro 源码:https://github.com/opentiny/tiny-pro
TinyEngine 源码: https://github.com/opentiny/tiny-engine

欢迎进入代码仓库 Star🌟TinyPro、TinyEngine、TinyVue、TinyNG、TinyCLI、TinyEditor~
如果你也想要共建,可以进入代码仓库,找到 good first issue 标签,一起参与开源贡献~