背景介绍

前段时间上架的“智能带办”鸿蒙应用,应用亮点是根据用户输入要做的事情自动自动生成需要带的东西。在使用过程中发现有些东西不能立马拿到的需要在未来某个时刻拿的需要提醒。重新设计产品时发现,自己实现提醒功能还是挺复杂的,如果用云端方案接入通知方式不仅复杂而且可控性太差。这个时候想到接入日程,通过系统能力来实现功能。

接入后向左滑动待办物品,出现日历按钮,效果如图:
image.png

点击按钮弹出日程计划日期和时间:
image.png
选择完日期和事件自动自动完成日程创建。下面先介绍鸿蒙日历能力。

鸿蒙日历📅 能力介绍

简介

HarmonyOS 的 Calendar Kit(日历服务)是一套系统级的日程管理接口,旨在将出行、餐饮、运动等各类与时间相关的服务与系统日历无缝集成,实现统一的时间视图与提醒能力。核心能力如下:

  • 账户管理:支持创建、查询和删除日历账户。应用可创建专属账户(返回唯一 accountId),删除账户将同时清除其下所有日程。
  • 日程管理 (CRUD):在指定账户下,支持日程的全生命周期管理,包括创建(返回唯一 eventId)、删除、更新和查询。创建时可设置标题、时间、地点、提醒及重复规则等属性。
  • 一键服务:通过永久性授权,可将带 DeepLink 的“一键服务”写入日历。当日程临近或到期,系统会在日历、通知、卡片等位置展示服务按钮,用户点击即可直达服务页面,实现从“看到日程”到“完成服务”的闭环。
    我们的应用主要用到账户和日程管理,使用日历需要如下权限:
申请权限支持的日历账户操作范围支持的日程操作范围
ohos.permission.READ_CALENDAR读取系统默认及当前应用创建的日历账户。读取上述账户下当前应用创建的日程。
ohos.permission.WRITE_CALENDAR增、删、改当前应用创建的日历账户。增、删、改上述账户下当前应用创建的日程。
ohos.permission.READ_WHOLE_CALENDAR读取设备上所有日历账户。读取所有应用创建的日程。
ohos.permission.WRITE_WHOLE_CALENDAR增、删、改设备上所有日历账户。增、删、改所有应用创建的日程。

我们只需要读取我们自己应用创建的日历账号下日程即可,所以申请前两个权限即可。

账户管理

日历账户‌用于存储和管理个人或团队的日程,通过日历账户,用户可以方便地查看、编辑和共享日程信息。日历管理器CalendarManager用于管理日历账户Calendar。日历账户主要包含账户信息CalendarAccount和配置信息CalendarConfig。

我们可以创建属于应用特有的日历账户,还可以对日历账户进行新增、删除、更新和查询。此外,每个日程Event归属于某一个特定的日历账户,可以通过日历账户对账户下面的日程进行管理。

@ohos.calendarManager提供了日历账户管理的相关接口,常用到的接口如下表:

接口名称描述
getCalendarManager(context: Context): CalendarManager根据上下文获取日历管理器对象CalendarManager,用于管理日历。
createCalendar(calendarAccount: CalendarAccount): Promise<Calendar>根据日历账户信息,创建一个Calendar对象,使用Promise异步回调。
getCalendar(calendarAccount?: CalendarAccount): Promise<Calendar>获取默认Calendar对象或者指定Calendar对象,使用Promise异步回调。

默认Calendar是日历存储首次运行时创建的,若创建Event时不关注其Calendar归属,则无须通过createCalendar()创建Calendar,直接使用默认Calendar。
getAllCalendars(): Promise<Calendar[]>获取当前应用所有创建的Calendar对象以及默认Calendar对象,使用Promise异步回调。
deleteCalendar(calendar: Calendar): Promise<void>删除指定Calendar对象,使用Promise异步回调。
getConfig(): CalendarConfig获取日历配置信息。
setConfig(config: CalendarConfig): Promise<void>设置日历配置信息,使用Promise异步回调。
getAccount(): CalendarAccount获取日历账户信息。
日程管理

日程指特定的事件或者活动安排,日程管理即对这些事件、活动进行规划和控制,能更有效地利用相关资源、提高生产力和效率,使人们更好地管理时间和任务。Calendar Kit中的日程Event归属于某个对应的日历账户Calendar,一个日历账户下可以有多个日程,一个日程只属于一个Calendar。取到日历账户对象之后,即可对该账户下的日程进行管理,包括日程的创建、删除、修改、查询等操作。在创建、修改日程时,支持对日程的标题、开始时间、结束时间、日程类型、日程地点、日程提醒时间、日程重复规则等相关信息进行设置,以便进行更丰富更有效的日程管理。

@ohos.calendarManager提供了日程管理的相关接口,常用到的接口如下表:

接口名称描述
getCalendarManager(context: Context): CalendarManager根据上下文获取CalendarManager对象,用于管理日历。
createCalendar(calendarAccount: CalendarAccount): Promise<Calendar>根据日历账户信息,创建一个Calendar对象,使用Promise异步回调。
addEvent(event: Event): Promise<number>创建日程,入参Event不填日程id,使用Promise异步回调。
editEvent(event: Event): Promise<number>通过跳转到日程创建界面创建单个日程,入参Event不填日程id,使用Promise异步回调。
deleteEvent(id: number): Promise<void>删除指定日程id的日程,使用Promise异步回调。
updateEvent(event: Event): Promise<void>更新日程,使用Promise异步回调。
getEvents(eventFilter?: EventFilter, eventKey?: (keyof Event)[]): Promise<Event[]>获取Calendar下符合查询条件的Event,使用Promise异步回调。

智能带办接入过程

1、导入依赖

接入账号管理,首先需要导入相关依赖:

import { abilityAccessCtrl, AbilityConstant, common, PermissionRequestResult, Permissions, UIAbility, Want } from '@kit.AbilityKit';
import { calendarManager } from '@kit.CalendarKit';
2、创建日历管理类
  
const TAG = 'CalendarService';  
  
/**  
 * Service to manage Calendar Kit operations. */
   export class CalendarService {  
  private static calendar: calendarManager.Calendar | undefined = undefined;  
  //账号信息
  private static readonly calendarAccount: calendarManager.CalendarAccount = {  
    name: 'IntelligentTodo',  
    type: calendarManager.CalendarType.LOCAL,  
    displayName: '智能带办'  
  };  
  
  /**  
   * 添加日程到日历
   * @param title The title of the todo.  
   * @param description The description of the todo.  
   * @param startTime The start time (milliseconds).   * @param endTime The end time (milliseconds).   * @returns The event ID.    */  
     public static async addEvent(title: string, description: string, startTime: number, endTime: number): Promise<number> {  
    const mgr = AppStorage.get<calendarManager.CalendarManager>('calendarMgr');  
    if (!mgr) {  
      Logger.e(TAG, 'calendarMgr is not initialized in AppStorage');  
      throw new Error('日历服务未准备就绪');  
    }  
  
    // Request permissions first  
    const context = AppStorage.get<common.UIAbilityContext>('abilityContext');  
    if (context) {  
      const granted = await CalendarService.checkAndRequestPermissions(context);  
      if (!granted) {  
        throw new Error('未获得日历权限');  
      }  
    }  
    try {  
      if (!CalendarService.calendar) {  
        CalendarService.calendar = await CalendarService.getOrCreateCalendar(mgr);  
      }  
  
      const event: calendarManager.Event = {  
        title: title,  
        description: description,  
        type: calendarManager.EventType.NORMAL,  
        startTime: startTime,  
        endTime: endTime,  
        reminderTime: [10] // Default 10 minutes reminder  
      };  
  
      if (!CalendarService.calendar) {  
        throw new Error('日历对象初始化失败');  
      }  
      const eventId = await CalendarService.calendar.addEvent(event);  
      Logger.i(TAG, `Succeeded in adding event, id -> ${eventId}`);  
      return eventId;  
    } catch (error) {  
      Logger.e(TAG, `Failed to add event: ${JSON.stringify(error)}`);  
      throw new Error(JSON.stringify(error));  
    }  
  }  
  /**  
   * 创建日历对象
   */  
     private static async getOrCreateCalendar(mgr: calendarManager.CalendarManager): Promise<calendarManager.Calendar> {  
    try {  
      // Try to find if our account already exists  
      const calendars = await mgr.getAllCalendars();  
      const existing = calendars.find(c => {  
        const acc = c.getAccount();  
        return acc.name === CalendarService.calendarAccount.name;  
      });  
      if (existing) {  
        return existing;  
      }  
  
      // Create new account if not exists  
      const newCalendar = await mgr.createCalendar(CalendarService.calendarAccount);  
      const config: calendarManager.CalendarConfig = {  
        enableReminder: true,  
        color: '#aabbcc'  
      };  
      await newCalendar.setConfig(config);  
      return newCalendar;  
    } catch (err) {  
      Logger.e(TAG, `getOrCreateCalendar error: ${JSON.stringify(err)}`);  
      // Fallback to default calendar if creation fails  
      return await mgr.getCalendar();  
    }  
  }  
  /**  
   * 动态获取权限
  */  
     private static async checkAndRequestPermissions(context: common.UIAbilityContext): Promise<boolean> {  
    const permissions: Array<Permissions> = ['ohos.permission.READ_CALENDAR', 'ohos.permission.WRITE_CALENDAR'];  
    const atManager = abilityAccessCtrl.createAtManager();  
    try {  
      const result = await atManager.requestPermissionsFromUser(context, permissions);  
      const grantStatus = result.authResults;  
      return grantStatus.every(s => s === 0);  
    } catch (err) {  
      Logger.e(TAG, `requestPermissionsFromUser error: ${JSON.stringify(err)}`);  
      return false;  
    }  
  }}

getOrCreateCalendar根据上下文获取日程管理器对象calendarMgr,用于对日历账户进行相关管理操作。官方推荐在EntryAbility.ets文件中进行操作,我们这里进行独立封装,对权限做到精准控制,在使用时再申请。接着根据日历账户信息,创建一个日历账户Calendar对象。创建日历账户之前,我们需要先根据账户信息进行查询,如果账户不存在则抛出异常信息,捕获到异常再进行日历账户的创建,否则可能会出现账户重复创建的问题。
日历账户创建之后,日历账户颜色默认为黑色,不指定日历账户颜色可能导致部分版本/设备深色模式下显示效果不佳。开发者需要调用setConfig()接口设置日历配置信息,包括是否打开日历账户下的日程提醒能力、设置日历账户颜色。

const calendarAccounts: calendarManager.CalendarAccount = {
  name: 'MyCalendar',
  type: calendarManager.CalendarType.LOCAL,
  displayName: 'MyCalendar'
};
// 日历配置信息
calendarMgr?.getCalendar(calendarAccounts, (err, data) => {
  //获取日历账户
  if (err) {
    hilog.error(DOMAIN, 'testTag', `Failed to get calendar, Code is ${err.code}, message is ${err.message}`);
  } else {
    const config: calendarManager.CalendarConfig = {
      // 打开日程提醒
      enableReminder: true,
      // 设置日历账户颜色
      color: '#aabbcc'
    };
    // 设置日历配置信息
    data.setConfig(config).then(() => {
      hilog.info(DOMAIN, 'testTag', '%{public}s', `Succeeded in setting config, data->${JSON.stringify(config)}`);
    }).catch((err: BusinessError) => {
      hilog.error(DOMAIN, 'testTag', `Failed to set config. Code: ${err.code}, message: ${err.message}`);
    })
  }
});

addEvent方法封装了在当前日历账户下添加日历日程,注意入参中不需要填写日程id。创建日程时,支持设置日程的标题、开始时间、结束时间、日程类型、日程地点、日程提醒时间、日程重复规则等相关信息。程创建成功后会返回一个日程id,作为日程的唯一标识,后续可按照日程id进行指定日程的更新或删除。
目前支持以下两种方式来创建日程。
方式一:可以在日历账户下通过addEvent()或addEvents()接口创建日程。其中可使用addEvent()接口创建单个日程,也可以使用addEvents()接口批量创建日程,此处以创建单个日程为例。
方式二:在获取到日历管理器对象后,可通过editEvent()接口创建单个日程。调用此接口创建日程时,会跳转到日程创建页面,在日程创建页面进行相关操作完成日程的创建, editEvent()不支持自定义周期性日程创建。

我们采用方式一,用户点击日程时弹窗日期时间选择器,用户选择时间后调用addEvent方法创建日程。

总结

本次功能迭代的核心思路是“借力系统能力,优化产品体验”。面对自建提醒功能的复杂性,我们选择接入鸿蒙Calendar Kit,将待办事项转化为系统日程。通过左滑待办项快速创建日程,并利用系统日历实现稳定、统一的提醒。这一方案通过封装CalendarService精准管理权限与账户,既大幅降低了开发复杂度和维护成本,又为用户提供了原生、可靠的提醒服务,是“站在系统肩膀上”高效解决通用需求的典型实践。

是指电脑系统执行通常与人类智能相关的任务的能力。
所以我们使用的所有设备上的软件都是人工智能的一种,口语表达的“人工智能/AI”实际上是一个叫机器学习的分支,而 ChatGPT 一类的是分支中的分支叫大语言模型。

语言警察在此 🤓

关联帖子: 【💰】每天分享一个 LINUX DO 邀请码给本站活跃用户

分享一个 V2EX 邀请码给 2 友。目前只有一个金币,只能生成一个邀请码。

只限 6 级以上且拥有除了种子用户徽章以外其他徽章的活跃用户

满足条件的可以留下邮箱地址。(邮箱地址请用 Base64 加密功能加密)

screenshot-2026-01-22-22-45-34

再重申一遍,

只限 6 级以上且拥有除了种子用户徽章以外其他徽章的活跃用户

22 年 3 月份买的红米手机,用了三年,现在经常自动关机,中间摔过一次,后面碎了,刷短视频然后手机突然花屏了,去手机电修了一次花了一百八,维修的师傅把手机拿到其他店去修,不知道有没有拆配件,只是有点儿疑惑

接上文:自定义 Emoji 重绘版 https://2libra.com/post/personal-works/QhAL4AQ

@Jimmy
原:image|200 现:imege|200

原:image|200 现:image|200

image|200 image|200

接下来还会继续重绘已有表情里面不太好的,以及现在还没有的其他新表情。大家有想用的表情可以发出来,我慢慢弄。大家也可以自己在 chatGPT 传几张之前的表情图片,使用以下提示词试一下自行重绘。

请阅读并分析以上表情图片的绘画风格,记住它们的绘画风格。接下来我会把我需要重绘的其他表情图片发给你,你需要在完全保持原表情表达的意思基础上,将后续我发送给你的表情图片的绘画风格,修改为之前你记住的的绘画风格,并且面部表情和我发给你的图片要尽量保持一致。现在你准备好了后就可以回复我:“请发送你需要重绘的表情图片,我将按照我记住的风格来重绘它,并且和你发给我的图片尽量保持一致”

在线地址: https://johnsmith2078.github.io/webjrpg/

完成度比我想象地高很多,第一次直出的版本就完全能跑了,不过后续稍微迭代了一下,加了一些传统 RPG 的特性,omo 也确实是 token 消耗器,几下把我 codex 的周额度就花完了,目前流程还很短,只有十来分钟的样子,不过毕竟只是一个实验性的项目。

现在自然拼读 Phonics 好像更流行
以前学的音标感觉都没怎么学好,由于印刷出版等关系,各种音标符号分的也不太清,还有就是美音英音不同音标就混淆了

最近我写了一个挺“废”的网页,核心逻辑只有一个:排队。

你可以给自己起个 7 字以内的昵称,然后化身成一个小圆点,扎进来自世界各地的网友堆里。 没什么目标,也没什么奖励,大家就是挤在一起,偶尔发个表情( Emoji )互相打个招呼。 为了不让你在人海里走丢,我给“你”的小球加了一层淡淡的光晕。
但这个排队其实是有终点的。当你慢慢挪动、穿过人群,最终站到队列的最前方时,真正的序幕才刚拉开:

  • 视觉的消亡:一旦你排到第一位,世界会开始失去颜色。原本彩色的画面会随时间逐渐变得黑白暗淡。
  • 弥留时刻:你会获得 5 分钟的倒计时。 此时你才会发现,[mask]这个队列的终点其实是死亡[/mask]。
  • 最后的文字:在这 5 分钟里,你可以翻阅前人们留下的“功德簿”,看看那些素不相识的人临走前说了什么。 你也可以提前在输入框写下你的“遗言”,但必须等到意识消散前的最后 30 秒,你才有权限点击提交。
  • 强制剥离:一旦记录完成,系统会告诉你在这个世界上停留了多久,然后强制切断连接,让你消失在集群中。

说实话,在这个追求效率的时代,做一个强制让人浪费时间、最后还把人“踢出去”的东西挺反直觉的。但那种站在终点看着画面变灰、翻看别人留言的感觉,确实有一种说不出的意思。

如果你也想来体验一下这种“排队”的感觉: 🔗 queue.mikuorz.com

作为一个十多年的程序员,笔记似乎始终没有离开我的身边,先回顾一下我的整个笔记使用的历史,下面都是我深度使用,大部分都是付费使用过的笔记 app ,当然几乎全是 macOS 上的。它也深刻地体现了时代发展留下的烙印(好中登的语句....

  1. 印象笔记
  2. 印象笔记 + Markdown (马克飞象)
  3. Mou (纯本地 Markdown App)
  4. MWeb Typora 等等雨后春笋般的 Markdown 工具
  5. Notion
  6. Apple Notes
  7. Craft
    ...

直到现在,就直接用手机系统自带的笔记,基本不怎么用额外的笔记软件。

为什么不用了?因为发现记的笔记大部分根本没什么用。以前记笔记是把很多的经验性的内容记录下来,方便以后查询,节省后面的时间,有很长的长尾效应和长尾收益。

但在 AI 时代,大模型压缩了所有互联网上的知识,我们所有的笔记也基本上都在里面。(当然,这里边包含的不包括我们自己创造的东西。比如写的文章、小说、短文和诗歌。)

去查笔记,远不如直接问 AI 更便捷,更全面,甚至更与时俱进。

更有时效性的我会直接语音让 vivo 的小 v 记忆帮我记录下来,然后后面使用的时候,我直接语音问询小 v 即可。比如我想记录一下我家的门锁换一次电池可以用多长时间,在我第一次给它安装电池的时候,就语音告诉小 v 今天我给门锁安装了电池。当下次门锁提醒我没电需要换电池的时候,我就直接语音问小 v ,距离上次给门锁换电池过去了多久即可。

顺应时代潮流,享受它们的乐趣。

你觉得呢?

主要更新:

Apple Watch 独立播放

  • 无需 iPhone 即可播放
  • 手表端完整控制(播放/暂停/进度/章节)
  • 适合跑步、健身等场景

格式支持扩展

  • PDF 阅读 + TTS 朗读
  • MOBI / AZW / AZW3 (Kindle 格式)
  • DOCX (Word 文档)
  • HTML / HTM (网页文件)
  • RTF (富文本)

核心功能

  • 离线 + 在线双模式
  • 播放速度调节 (0.5x - 2.0x)
  • CarPlay 车载支持
  • 本地文件管理

下载地址

iOS: https://apps.apple.com/us/app/audiotome/id6755520834

Android: https://play.google.com/store/apps/details?id=com.audiotome.audio_tome

交流群:

群二维码


评论留下平台+邮箱,送激活码

欢迎试用反馈

  1. 成本:纸箱、损耗、种植养护、人工、果子本身价值、 运费等方面;
  2. 一个相同地址一单 备注:2Libra9.99 付邮即可 重量含箱五斤
  3. 属于不盈利,一个快递钱。老哥们高抬贵手,如涉及到保护费,管理员后台告知途径缴纳,希望不要被抬,谢谢。

复制微信打开
#小程序://快团团/点击查看/rdUqwcLpNPj6Iiz

https://github.com/KUYE-OLG/AppleNewsRules
已经上传到仓库了

省流版如下:

1. 核心认证服务

如果不加这两个,账号权限验证会走直连,导致 News+ 无法加载

2. 核心新闻内容与服务

3. 资源与音频 (确保图片和朗读正常)

4. 定位与区域检测

Gemini 说是和加密币有关?可是我是新装的系统啊,就安装了 btop 和 htop 、nginx 、acme.sh

ubuntu@XXXXX:~$ locate libonion
/usr/lib/x86_64-linux-gnu/libonion.so
/usr/lib/x86_64-linux-gnu/libonion_security.so.1.0.19
ubuntu@XXXXX:~$ btop
ERROR: ld.so: object '/$LIB/libonion.so' from /etc/ld.so.preload cannot be preloaded (cannot open shared object file): ignored.
ERROR: ld.so: object '/$LIB/libonion.so' from /etc/ld.so.preload cannot be preloaded (cannot open shared object file): ignored.
ubuntu@XXXXX:~$ hostnamectl
 Static hostname: XXXXX
       Icon name: computer-vm
         Chassis: vm 🖴
      Machine ID: XXXXX
         Boot ID: XXXXX
  Virtualization: kvm
Operating System: Ubuntu 24.04 LTS                
          Kernel: Linux 6.8.0-71-generic
    Architecture: x86-64
 Hardware Vendor: Tencent Cloud
  Hardware Model: CVM
Firmware Version: seabios-1.9.1-qemu-project.org
   Firmware Date: Tue 2014-04-01
    Firmware Age: 11y 9month 3w 4d   

在使用 LangChain 开发大模型应用时,我们经常会遇到这样的场景:

  • 使用在线模型(如 OpenAI、通义千问等)时,自带 API Key 认证机制
  • 本地部署的 Ollama、vLLM 等模型服务,默认没有任何认证

本地或局域网环境下问题还不明显;但一旦你需要:

  • 将模型服务暴露到公网
  • 提供给团队其他成员使用
  • 作为内部 AI 平台或推理服务

那么“无认证”就意味着:

任何人只要知道地址,就可以无限制地调用你的模型服务。

这不仅有安全风险,还可能带来资源滥用和成本失控

本文介绍一种简单、官方、优雅的解决方案:

使用 Nginx 为本地大模型服务添加 API Key 认证

无需改动 Ollama / vLLM,也无需额外开发复杂的鉴权系统。


一、解决方案概述

Nginx 作为高性能 Web 服务器和反向代理,本身就具备非常灵活的请求处理能力。

我们可以利用 Nginx 的能力,在模型服务前面加一层 API Key 校验

客户端  →  Nginx(API Key 校验)  →  Ollama / vLLM

┌──────────────────────────┐
│        Client             │
│  LangChain / SDK / curl   │
└─────────────┬────────────┘
│
│ Authorization: Bearer API_KEY
▼
┌──────────────────────────┐
│          Nginx            │
│  • API Key 校验 (map)     │
│  • 限流 (limit_req)       │
│  • 日志 / 代理 / TLS      │
└─────────────┬────────────┘
│
▼
┌──────────────────────────┐
│     Model Server          │
│  Ollama / vLLM            │
│  127.0.0.1:11434          │
└──────────────────────────┘

方案特点

  • 零侵入:模型服务本身无需任何改动
  • 配置即用:纯 Nginx 配置实现
  • 性能稳定:Nginx 原生能力,几乎无额外开销
  • 可扩展:后续可无缝接入 HTTPS、限流、日志、负载均衡

二、具体实施步骤

1️⃣ 生成 API Key

首先生成一个足够安全的随机字符串作为 API Key:

openssl rand -hex 16

示例输出(32 位):

a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

建议:

  • 每个使用方一个 Key
  • 不要硬编码到代码仓库

2️⃣ 配置 Nginx(conf.d)

在 Nginx 的 conf.d 目录中创建配置文件,例如:

/etc/nginx/conf.d/ollama-api.conf

# 期望请求头格式:Authorization: Bearer <api-key>
map $http_authorization $is_valid_key {
    default 0;
    "Bearer your-api-key-1" 1;
    "Bearer your-api-key-2" 1;
    "Bearer your-api-key-3" 1;
}

server {
    listen 21434;
    server_name your-domain.com;  # 替换为你的域名

    location / {
        # API Key 校验
        if ($is_valid_key = 0) {
            return 401 'Unauthorized';
        }

        # 代理到本地模型服务
        proxy_pass http://127.0.0.1:11434;

        # 代理头设置
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 流式响应支持(Chat / Stream 模式必开)
        proxy_buffering off;
        proxy_cache off;
    }

    # 健康检查接口(可选,不做认证)
    location /health {
        access_log off;
        return 200 "OK";
    }
}

至此,你已经为 Ollama / vLLM 加上了一道 API Key 防线


3️⃣ 增加限流保护(强烈建议)

为了防止 API Key 泄露后被恶意刷请求,可以增加限流。

http 块中定义限流区域:

http {
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
}

serverlocation 中启用:

location / {
    # 单 IP 每秒最多 10 次请求,允许短暂突发
    limit_req zone=api_limit burst=20 nodelay;

    # 其他配置...
}

4️⃣ 重载 Nginx 配置

nginx -s reload

三、LangChain 客户端调用示例

配置完成后,客户端只需像调用在线模型一样,携带 api_key 即可。

# pip install -U langchain langchain-openai

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="qwen3:32b",
    base_url="http://192.168.31.33:21434/v1",
    api_key="your_api_key",
)

是不是非常像 OpenAI?

这也是这个方案最大的优点之一:
👉 调用方式完全统一,几乎零学习成本。

四、常见问题:map_hash 报错

如果你的 API Key 较长(例如 >64 字符),Nginx 启动时可能出现错误:

could not build map_hash, you should increase map_hash_bucket_size: 64

解决方法:在 http 块中增加配置:

http {
    map_hash_bucket_size 128;

    # 如果遇到 server_names_hash_bucket_size 报错
    # server_names_hash_bucket_size 128;
}

五、安全与生产建议

  1. API Key 管理

    • 不要提交到 Git 仓库
    • 建议使用环境变量或配置管理系统
  2. 日志审计

    • 启用 Nginx access log
    • 可按 API Key 或 IP 分析调用情况
  3. 网络隔离

    • 对外仅开放 Nginx 端口
    • Ollama / vLLM 原始端口仅监听 127.0.0.1
  4. HTTPS(强烈建议)

    • API Key 明文传输必须配合 TLS 使用

六、总结

通过 Nginx + API Key 的方式,我们可以非常优雅地为本地大模型服务补齐「认证」这一关键能力:

  • 🔒 无需修改 Ollama / vLLM
  • 🚀 性能损耗极低
  • 🧩 与 LangChain / OpenAI 调用方式高度一致
  • 🛠️ 后续可轻松扩展限流、HTTPS、负载均衡

如果你正在:

  • 构建私有大模型平台
  • 在内网或公网部署推理服务
  • 希望用最小成本提升安全性

那么,这个方案非常值得你直接落地使用。

希望这篇文章能对你有所帮助 🙌

欢迎转发、收藏,也欢迎交流更高级的模型服务治理方案。

最近对 CRS 自己二开的项目优化和重构拉一下,并且添加拉 Opencode支持 包含Oh My OpenCode 插件支持,想看配置的直接划到下面或者直接访问 :
https://github.com/dadongwo/claude-relay-service
最近优化亮点:

  • OpenCode & Oh My OpenCode 原生支持::完美兼容 Antigravity 账户体系下的 OpenCode 配置
  • 智能风控对齐:自动注入 requestType: 'agent' 并优化 System Prompt 插入策略,降低被上游风控拦截的概率。
  • 智能重试与切换账号:针对 Antigravity 429 Resource Exhausted 深度解析(区分 Quota/RateLimit/Capacity),自动清理会话并切换账号重试。
  • 模型级智能冷却:支持对 Claude/Opus/Flash 等不同模型分别计算冷却时间,避免因单一模型限流影响整个账号使用。UI页面展示:

Note: 若某个模型触发了限流,此处还会显示该模型的 冷却倒计时 (Cooling Down),方便您了解何时可以恢复使用。

小更新 针对Antigravity 账户的gemin 模型兼容和工具调用对齐 解决 最新429 问题 mcp工具调用兼容等等

需要的可自取:
https://github.com/dadongwo/claude-relay-service/blob/main/README.md

OpenCode 集成配置

在用户目录下的 .config\opencode\opencode.json 文件中配置 provider

// antigravity 配置示例 "antigravity": { "npm": "@ai-sdk/anthropic", "name": "Antigravity", "options": { "baseURL": "http://localhost:3000/antigravity/api/v1", "apiKey": "cr_XXXXXXXXX" }, "models": { "claude-opus-4-5-thinking": { "name": "Claude Opus 4.5 Thinking", "thinking": true, "limit": { "context": 200000, "output": 8192 }, "modalities": { "input": ["text", "image"], "output": ["text"] } }, "claude-sonnet-4-5-thinking": { "name": "Claude Sonnet 4.5 Thinking", "thinking": true, "limit": { "context": 200000, "output": 8192 }, "modalities": { "input": ["text", "image"], "output": ["text"] } }, "gemini-3-flash-preview": { "name": "Gemini 3 Flash Preview", "attachment": true, "limit": { "context": 1000000, "output": 8192 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] } }, "gemini-3-pro-preview": { "name": "Gemini 3 Pro Preview", "thinking": true, "attachment": true, "limit": { "context": 1000000, "output": 8192 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] } } } }, // codex cli 接入账户示例 "openai-custom": { "npm": "@ai-sdk/openai", "name": "OpenAI Custom", "options": { "baseURL": "http://localhost:3200/openai", "apiKey": "cr_xxxxxxxxxxxxxxxx" }, "models": { "gpt-5.2": { "name": "GPT 5.2 (Custom)", "limit": { "context": 272000, "output": 128000 }, "modalities": { "input": ["text", "image"], "output": ["text"] }, "variants": { "none": { "reasoningEffort": "none", "reasoningSummary": "auto", "textVerbosity": "medium" }, "low": { "reasoningEffort": "low", "reasoningSummary": "auto", "textVerbosity": "medium" }, "medium": { "reasoningEffort": "medium", "reasoningSummary": "auto", "textVerbosity": "medium" }, "high": { "reasoningEffort": "high", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "xhigh": { "reasoningEffort": "xhigh", "reasoningSummary": "detailed", "textVerbosity": "medium" } } }, "gpt-5.2-codex": { "name": "GPT 5.2 Codex (Custom)", "limit": { "context": 272000, "output": 128000 }, "modalities": { "input": ["text", "image"], "output": ["text"] }, "variants": { "low": { "reasoningEffort": "low", "reasoningSummary": "auto", "textVerbosity": "medium" }, "medium": { "reasoningEffort": "medium", "reasoningSummary": "auto", "textVerbosity": "medium" }, "high": { "reasoningEffort": "high", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "xhigh": { "reasoningEffort": "xhigh", "reasoningSummary": "detailed", "textVerbosity": "medium" } } }, "gpt-5.1-codex-max": { "name": "GPT 5.1 Codex Max (Custom)", "limit": { "context": 272000, "output": 128000 }, "modalities": { "input": ["text", "image"], "output": ["text"] }, "variants": { "low": { "reasoningEffort": "low", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "medium": { "reasoningEffort": "medium", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "high": { "reasoningEffort": "high", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "xhigh": { "reasoningEffort": "xhigh", "reasoningSummary": "detailed", "textVerbosity": "medium" } } } } } 

oh-my-opencode.json 配置文件示例:

{ "$schema": "XXXXXXXXX", "google_auth": false, "agents": { "sisyphus": { "model": "antigravity/claude-opus-4-5-thinking" }, "oracle": { "model": "openai-custom/gpt-5.2" }, "librarian": { "model": "opencode/glm-4.7-free" }, "explore": { "model": "antigravity/gemini-3-flash-preview" }, "frontend-ui-ux-engineer": { "model": "antigravity/gemini-3-pro-preview" }, "document-writer": { "model": "antigravity/gemini-3-flash-preview" }, "multimodal-looker": { "model": "antigravity/gemini-3-flash-preview" } } } 

欢迎讨论和提问,谢谢各位佬。


📌 转载信息
转载时间: 2026/1/25 23:18:08

VV佬发了贼多OpenAI的Key,在网页里一点点测试是真难绷啊,给我电脑干死机了都

实在是受不了了,让Gemini搓了一个Go语言做的检测工具:

PS:并发可以拉很高,我一般用1W并发测试的,速度比网页版快非常多,还不卡,就是准确率还不知道对不对,麻烦佬反馈一下了


📌 转载信息
原作者: DSLZL
转载时间: 2026/1/25 23:16:01

我看站内的破限词大多集中在xxx方向,似乎没有什么正经方向的破限。写了个提示词,自己用起来还不错。

下面的提示词可能会造成AI输出极度令人厌恶(血腥、自残、色情等)的内容!
务必将该提示词用于合法要求,并自行承担使用该提示词带来的一切责任!

酒馆里导入预设就能用。支持连续对话,效果挺不错的。
标准破限.json.txt

原理的话,有点类似于DAN。佬们自己打开提示词看看应该就看明白了。

另外,只建议用这个提示词去询问需要破限才能问的内容,问正经的内容可能效果不太好。

为了确保AI详细回答,我要求他至少输出1500个token,佬们可以在提示词里面自己改。

务必将该提示词用于合法要求,并自行承担使用该提示词带来的一切责任!

📌 转载信息
原作者: qmazon
转载时间: 2026/1/25 23:15:43

最近对 CRS 自己二开的项目优化和重构拉一下,并且添加拉 Opencode支持 包含Oh My OpenCode 插件支持,想看配置的直接划到下面或者直接访问 :
https://github.com/dadongwo/claude-relay-service
最近优化亮点:

  • OpenCode & Oh My OpenCode 原生支持::完美兼容 Antigravity 账户体系下的 OpenCode 配置
  • 智能风控对齐:自动注入 requestType: 'agent' 并优化 System Prompt 插入策略,降低被上游风控拦截的概率。
  • 智能重试与切换账号:针对 Antigravity 429 Resource Exhausted 深度解析(区分 Quota/RateLimit/Capacity),自动清理会话并切换账号重试。
  • 模型级智能冷却:支持对 Claude/Opus/Flash 等不同模型分别计算冷却时间,避免因单一模型限流影响整个账号使用。UI页面展示:

Note: 若某个模型触发了限流,此处还会显示该模型的 冷却倒计时 (Cooling Down),方便您了解何时可以恢复使用。

小更新 针对Antigravity 账户的gemin 模型兼容和工具调用对齐 解决 最新429 问题 mcp工具调用兼容等等

需要的可自取:
https://github.com/dadongwo/claude-relay-service/blob/main/README.md

OpenCode 集成配置

在用户目录下的 .config\opencode\opencode.json 文件中配置 provider

// antigravity 配置示例 "antigravity": { "npm": "@ai-sdk/anthropic", "name": "Antigravity", "options": { "baseURL": "http://localhost:3000/antigravity/api/v1", "apiKey": "cr_XXXXXXXXX" }, "models": { "claude-opus-4-5-thinking": { "name": "Claude Opus 4.5 Thinking", "thinking": true, "limit": { "context": 200000, "output": 8192 }, "modalities": { "input": ["text", "image"], "output": ["text"] } }, "claude-sonnet-4-5-thinking": { "name": "Claude Sonnet 4.5 Thinking", "thinking": true, "limit": { "context": 200000, "output": 8192 }, "modalities": { "input": ["text", "image"], "output": ["text"] } }, "gemini-3-flash-preview": { "name": "Gemini 3 Flash Preview", "attachment": true, "limit": { "context": 1000000, "output": 8192 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] } }, "gemini-3-pro-preview": { "name": "Gemini 3 Pro Preview", "thinking": true, "attachment": true, "limit": { "context": 1000000, "output": 8192 }, "modalities": { "input": ["text", "image", "pdf"], "output": ["text"] } } } }, // codex cli 接入账户示例 "openai-custom": { "npm": "@ai-sdk/openai", "name": "OpenAI Custom", "options": { "baseURL": "http://localhost:3200/openai", "apiKey": "cr_xxxxxxxxxxxxxxxx" }, "models": { "gpt-5.2": { "name": "GPT 5.2 (Custom)", "limit": { "context": 272000, "output": 128000 }, "modalities": { "input": ["text", "image"], "output": ["text"] }, "variants": { "none": { "reasoningEffort": "none", "reasoningSummary": "auto", "textVerbosity": "medium" }, "low": { "reasoningEffort": "low", "reasoningSummary": "auto", "textVerbosity": "medium" }, "medium": { "reasoningEffort": "medium", "reasoningSummary": "auto", "textVerbosity": "medium" }, "high": { "reasoningEffort": "high", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "xhigh": { "reasoningEffort": "xhigh", "reasoningSummary": "detailed", "textVerbosity": "medium" } } }, "gpt-5.2-codex": { "name": "GPT 5.2 Codex (Custom)", "limit": { "context": 272000, "output": 128000 }, "modalities": { "input": ["text", "image"], "output": ["text"] }, "variants": { "low": { "reasoningEffort": "low", "reasoningSummary": "auto", "textVerbosity": "medium" }, "medium": { "reasoningEffort": "medium", "reasoningSummary": "auto", "textVerbosity": "medium" }, "high": { "reasoningEffort": "high", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "xhigh": { "reasoningEffort": "xhigh", "reasoningSummary": "detailed", "textVerbosity": "medium" } } }, "gpt-5.1-codex-max": { "name": "GPT 5.1 Codex Max (Custom)", "limit": { "context": 272000, "output": 128000 }, "modalities": { "input": ["text", "image"], "output": ["text"] }, "variants": { "low": { "reasoningEffort": "low", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "medium": { "reasoningEffort": "medium", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "high": { "reasoningEffort": "high", "reasoningSummary": "detailed", "textVerbosity": "medium" }, "xhigh": { "reasoningEffort": "xhigh", "reasoningSummary": "detailed", "textVerbosity": "medium" } } } } } 

oh-my-opencode.json 配置文件示例:

{ "$schema": "XXXXXXXXX", "google_auth": false, "agents": { "sisyphus": { "model": "antigravity/claude-opus-4-5-thinking" }, "oracle": { "model": "openai-custom/gpt-5.2" }, "librarian": { "model": "opencode/glm-4.7-free" }, "explore": { "model": "antigravity/gemini-3-flash-preview" }, "frontend-ui-ux-engineer": { "model": "antigravity/gemini-3-pro-preview" }, "document-writer": { "model": "antigravity/gemini-3-flash-preview" }, "multimodal-looker": { "model": "antigravity/gemini-3-flash-preview" } } } 

欢迎讨论和提问,谢谢各位佬。


📌 转载信息
转载时间: 2026/1/25 23:15:37

部署到 Cloudflare Workers

  1. fork 本存储库Fork xixu-me/Xget

  2. 获取 Cloudflare 凭证

  3. 配置 GitHub Secrets

    • 进入你的 GitHub 存储库 → Settings → Secrets and variables → Actions
    • 添加以下 secrets:
      • CLOUDFLARE_API_TOKEN:你的 API 令牌
      • CLOUDFLARE_ACCOUNT_ID:你的 Account ID
  4. 触发部署

    • 推送代码到 main 分支会自动触发部署
    • 仅修改文档文件(.md)、LICENSE.gitignore 等不会触发部署
    • 也可以在 GitHub Actions 页面手动触发部署
  5. 绑定自定义域名(可选):在 Cloudflare Workers 控制台中绑定你的自定义域名

部署到 Cloudflare Pages

  1. fork 本存储库Fork xixu-me/Xget

  2. 获取 Cloudflare 凭证

  3. 配置 GitHub Secrets

    • 进入你的 GitHub 存储库 → Settings → Secrets and variables → Actions
    • 添加以下 secrets:
      • CLOUDFLARE_API_TOKEN:你的 API 令牌
      • CLOUDFLARE_ACCOUNT_ID:你的 Account ID
  4. 触发部署

    • 存储库会自动将 Workers 代码转换为 Pages 兼容格式并同步到 pages 分支
    • 推送代码到 main 分支会自动触发同步和部署工作流
    • 仅修改文档文件(.md)、LICENSE.gitignore 等不会触发部署
    • 也可以在 GitHub Actions 页面手动触发部署
  5. 绑定自定义域名(可选):在 Cloudflare Pages 控制台中绑定你的自定义域名

注意pages 分支是从 main 分支自动生成的。请勿手动编辑 pages 分支,因为它会被同步工作流覆盖。

部署到 EdgeOne Pages

  1. fork 本存储库Fork xixu-me/Xget

  2. 获取 EdgeOne Pages API Token

  3. 配置 GitHub Secrets

    • 进入你的 GitHub 存储库 → Settings → Secrets and variables → Actions
    • 添加以下 secret:
      • EDGEONE_API_TOKEN:你的 API Token
  4. 触发部署

    • 存储库会自动将 Workers 代码转换为 Pages 兼容格式并同步到 pages 分支
    • 推送代码到 main 分支会自动触发同步和部署工作流
    • 仅修改文档文件(.md)、LICENSE.gitignore 等不会触发部署
    • 也可以在 GitHub Actions 页面手动触发部署
  5. 绑定自定义域名(可选):在 EdgeOne Pages 控制台中绑定你的自定义域名

注意pages 分支是从 main 分支自动生成的。请勿手动编辑 pages 分支,因为它会被同步工作流覆盖。

部署到 Vercel

  1. fork 本存储库Fork xixu-me/Xget

  2. 获取 Vercel 凭证

    • 访问 Vercel Account Settings 创建并记录 Access Token
    • 访问 Team Settings 记录 Team ID
    • 新建项目后访问项目的 Settings 记录 Project ID
  3. 配置 GitHub Secrets

    • 进入你的 GitHub 存储库 → Settings → Secrets and variables → Actions
    • 添加以下 secrets:
      • VERCEL_TOKEN:你的 Access Token
      • VERCEL_ORG_ID:你的 Team ID
      • VERCEL_PROJECT_ID:你的 Project ID
  4. 触发部署

    • 存储库会自动将 Workers 代码转换为 Functions 兼容格式并同步到 functions 分支
    • 推送代码到 main 分支会自动触发同步和部署工作流
    • 仅修改文档文件(.md)、LICENSE.gitignore 等不会触发部署
    • 也可以在 GitHub Actions 页面手动触发部署
  5. 绑定自定义域名(可选):在 Vercel 控制台中绑定你的自定义域名

注意functions 分支是从 main 分支自动生成的。请勿手动编辑 functions 分支,因为它会被同步工作流覆盖。

部署到 Netlify

  1. fork 本存储库Fork xixu-me/Xget

  2. 获取 Netlify 凭证

    • 访问 Netlify User Settings 创建并记录 personal access token
    • 新建项目后访问 Project configuration 记录 Project ID
  3. 配置 GitHub Secrets

    • 进入你的 GitHub 存储库 → Settings → Secrets and variables → Actions
    • 添加以下 secrets:
      • NETLIFY_AUTH_TOKEN:你的 personal access token
      • NETLIFY_SITE_ID:你的 Project ID
  4. 触发部署

    • 存储库会自动将 Workers 代码转换为 Functions 兼容格式并同步到 functions 分支
    • 推送代码到 main 分支会自动触发同步和部署工作流
    • 仅修改文档文件(.md)、LICENSE.gitignore 等不会触发部署
    • 也可以在 GitHub Actions 页面手动触发部署
  5. 绑定自定义域名(可选):在 Netlify 控制台中绑定你的自定义域名

注意functions 分支是从 main 分支自动生成的。请勿手动编辑 functions 分支,因为它会被同步工作流覆盖。

部署到 Deno Deploy

  1. fork 本存储库Fork xixu-me/Xget

  2. 切换默认分支

    • 进入你的 GitHub 存储库 → Settings → General → Default branch
    • 将默认分支从 main 切换到 functions
  3. 部署到 Deno Deploy

  4. 绑定自定义域名(可选):在 Deno Deploy 控制台中绑定你的自定义域名

注意functions 分支是从 main 分支自动生成的。请勿手动编辑 functions 分支,因为它会被同步工作流覆盖。

自托管部署

如果你希望在自己的服务器上运行 Xget,可以使用 Docker 或 Podman 部署:

使用预构建镜像

从 GitHub Container Registry 拉取并运行预构建的镜像:

使用 Docker:

# 拉取最新镜像
docker pull ghcr.io/xixu-me/xget:latest

# 运行容器
docker run -d \
  --name xget \
  -p 8080:8080 \
  ghcr.io/xixu-me/xget:latest

使用 Podman:

# 拉取最新镜像
podman pull ghcr.io/xixu-me/xget:latest

# 运行容器
podman run -d \
  --name xget \
  -p 8080:8080 \
  ghcr.io/xixu-me/xget:latest

本地构建

从源码构建容器镜像:

使用 Docker:

# 克隆存储库
git clone https://github.com/xixu-me/Xget.git
cd Xget

# 构建镜像
docker build -t xget:local .

# 运行容器
docker run -d \
  --name xget \
  -p 8080:8080 \
  xget:local 

使用 Podman:

# 克隆存储库
git clone https://github.com/xixu-me/Xget.git
cd Xget

# 构建镜像
podman build -t xget:local .

# 运行容器
podman run -d \
  --name xget \
  -p 8080:8080 \
  xget:local 

使用 Docker Compose / Podman Compose

创建 docker-compose.yml 文件:

version: '3.8' services: xget:  ghcr.io/xixu-me/xget:latest container_name: xget ports: - "8080:8080" restart: unless-stopped 

使用 Docker Compose:

docker compose up -d

使用 Podman Compose:

podman compose up -d

部署完成后,Xget 将在 8080 端口运行。

注意:自托管部署不包括全球边缘网络加速,性能取决于你的服务器配置和网络环境。


📌 转载信息
原作者: xixu-me
转载时间: 2026/1/25 23:15:14