news 2026/10/21 19:18:33

小程序云数据库综合实战:活动报名系统的权限、事务与聚合全解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
小程序云数据库综合实战:活动报名系统的权限、事务与聚合全解

小程序云数据库,大部分人并不陌生,但你可能和我最开始一样,单项操作都会,一组合就出问题。比如分页没问题,加上权限就查不到数据;报名接口没问题,并发一起来统计就乱。这篇文章我直接用一套“活动报名+统计”的业务场景,把集合设计、权限规则、分页查询、原子更新、聚合统计和事务完整过一遍,标题叫“综合操作_4”,意思是这个系列里最落地的一篇。已经能看懂collection.where().orderBy().get()这种链路,但对索引、权限规则和事务还没系统理解的开发者,读完可以直接照着自己项目改造。

为什么要拿活动报名做例子?因为它几乎覆盖了小程序云数据库最常见的痛点:数据要过滤状态、写入要做并发控制、列表要分页、页面要统计数字。你把这一套吃透,以后换到商品订单、预约排队、内容社区,原理完全一样。下面我不准备绕弯子,直接从数据模型设计说起。

1. 整体场景与数据模型设计

1.1 场景设定:活动报名管理小程序

我需要实现一个报名小程序,核心业务有三块:用户浏览活动列表,能看到报名人数和剩余名额;用户可以对某个活动报名,也可以取消报名;管理端需要定期清理过期、无效的报名记录。这个场景看起来简单,但真做起来牵扯了云数据库的很多细节,因为每一步都不只是单表读写。

活动列表页要支持按状态筛选(未开始、进行中、已结束),还要按开始时间排序并分页,这涉及组合索引和游标分页。报名操作涉及查询活动状态、校验剩余名额、写入报名记录、更新报名人数,四个动作必须保证原子性,否则会超卖。管理端要按活动汇总人数,如果前端直接拉全量报名记录再统计,数据一大会卡死,必须用聚合管道。你看,光一个报名功能,已经足够把“综合操作”这四个字撑起来了。

1.2 集合设计与字段规划

很多人上手第一步就错了,喜欢把所有字段塞到一个集合里,想着反正JSON是灵活的。活动可以嵌套用户数组,用户又可以嵌套活动历史,最后代码写起来一团乱。我的建议是拆集合,模拟关系型设计思路,但保留文档数据库的灵活性。

这个场景我拆两个集合:

activities活动集合,负责活动的基本信息和动态统计信息,字段如下:

字段名类型说明
_idstring自动生成
titlestring活动标题
locationstring活动地点
startTimeDate开始时间
endTimeDate结束时间
capacitynumber名额上限
statusstring活动状态:draft / published / cancelled
participantCountnumber当前报名人数(冗余计数)
creatorOpenIdstring创建者标识
createdAtDate创建时间
updatedAtDate更新时间

registrations报名记录集合,一条数据代表一次报名行为:

字段名类型说明
_idstring自动生成
activityIdstring活动ID
userOpenIdstring报名用户标识
statusstring报名状态:active / cancelled
createTimeDate报名时间
cancelTimeDate取消时间,可空

有几点是经验之谈。

第一,participantCount这样的冗余字段一开始就要设计进去,不要等性能出问题了再补。每次报名时做原子inc(后面会讲),这样活动列表不需要聚合就能直接显示报名人数。可能有人觉得冗余字段会带来一致性问题,我宁愿多花一点精力在事务里维持一致性,也不愿每次列表查询都跑聚合,因为列表是最高频的读场景,聚合是慢查询,用在高频场景就是灾难。

第二,status字段不要省。很多开发者喜欢直接删除记录,但业务上软删除能让审计、恢复和统计都简单很多。比如取消报名就是把registrations.status改成cancelled,而不是删掉记录;活动下架就是把activities.status改成cancelled。这样统计口径始终清晰,历史数据也还在,后面排查问题有据可查。

第三,createTime这类字段虽然云数据库自动生成的_id也能排序,但_id不能替代业务时间。用Date类型存时间,配合db.serverDate()写入,别用本地时间,因为用户手机时间可能不准,会污染排序和判断。举个例子:一个用户把手机时间改到明天,本地时间createTime就跑到前面去了,管理端的统计和排序就会出错,服务端时间才能保证统一。

第四,两个集合之间不要直接嵌套对象数组。比如在activities里塞一个users数组,意思是你把报名关系冗余了,用户取消、查询、人数统计都会变得非常麻烦。文档型数据库允许嵌套,但“一对多”关系还是建议拆成单独的集合,用activityId做外键查询。嵌套适合“一对一”或者固定少数内容,比如活动封面图URL数组;动态增长的用户列表,别往里塞。

2. 云环境与权限模型:先别写代码,把规则定清楚

2.1 云函数优先的架构选择

在动手写add、update之前,先把数据访问路径定下来。小程序云数据库有两条路:客户端直连数据库,或者通过云函数访问。很多人图省事,全部在客户端调用数据库API,短期没问题,业务复杂之后,安全性和一致性都会出问题。

我画过一张对比表,建议收藏:

访问方式优点缺点
客户端直连延迟低、代码少、体验流畅权限规则暴露在端侧,复杂逻辑难校验,批量写和事务能力受限
云函数访问数据校验可控、可做事务、可按需执行批量操作、隐藏服务端逻辑每次操作多一次函数调用,冷启动时延迟略高

我的建议是:读列表可以用客户端直连,尤其是公开数据;任何写操作、统计操作,以及涉及用户身份判断的操作,都尽量走云函数。比如报名这个动作,不能有用户直连数据库往registrations里插一条记录就完事,他需要先判断活动状态、名额、是否重复报名,这些逻辑放客户端等于把命门交给浏览器。一旦有人绕过前端直接调数据库接口,你所有的名额限制和活动状态判断都会被绕过去。

云函数初始化代码大致是这样,具体 SDK 引入按你自己的平台来:

const cloud = require('cloud-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() const _ = db.command exports.main = async (event) => { // 业务逻辑 }

在云函数里建议用DYNAMIC_CURRENT_ENV,这样可以保证当前云函数运行在哪个环境,数据库请求就打到哪个环境,不需要手写环境ID。好处很明显:同一套代码部署到测试环境和生产环境,不会因为环境ID写死而串库,不然你在测试环境点一下报名,数据写到了生产环境,排错会非常痛苦。

2.2 权限规则与自定义规则

集合的访问权限,很多文章一句带过,但这里是最容易“凭空丢数据”的地方。云数据库默认权限可以选几种:仅创建者可读写、仅管理端可写、所有用户可读、仅管理端可读写、自定义安全规则。

我整理了一个速查表:

权限模式客户端能否读客户端能否写云函数能否访问使用场景
仅创建者可读写只能读自己创建的只能写自己的能用户私有数据
所有用户可读,仅创建者可写所有用户可读只能写自己的能公开内容和用户自己的草稿
仅管理端可读写客户端通常读不到客户端不能写能需要云函数包装的运营数据
自定义安全规则按规则执行按规则执行能复杂业务数据

在这个活动报名场景里,activities我倾向于“所有用户可读,仅创建者可写”,因为活动列表是公开的。registrations则应该是“仅创建者可读写”,或者干脆设成“仅管理端可读写”,用户只能通过云函数去报名、取消,禁止直连读写。这样设计的好处是,前端的报名按钮只是一个好看的外壳,真正决定能不能报名的逻辑全在服务端。

自定义安全规则是一个容易被忽略的进阶技能。比如我想让所有人能读已发布的活动,活动创建者只能改自己创建的活动,规则可以写成:

{ "read": "auth != null && doc.status == 'published' || doc._openid == auth.openid", "write": "doc._openid == auth.openid" }

但我要提醒你,自定义规则写越复杂,排查越费劲。如果两个集合有关联查询,客户端直连还容易撞上权限限制。最简单的做法就是:把registrations的权限设为“仅管理端可读写”,用户侧表结构完全通过云函数访问。这样权限规则清晰,后端逻辑只要在一个入口把关。

提示:云函数中的服务端SDK访问数据库时,是不受集合权限规则限制的。所以不要把敏感信息依靠权限规则保护,真正的保护是“不让客户端直接触碰”。

3. 从查询到写入:完整实操闭环

3.1 组合查询与游标分页,别再用 skip 硬翻页

活动列表最常见的写法是:

const list = await db.collection('activities') .where({ status: 'published' }) .orderBy('startTime', 'asc') .skip(page * pageSize) .limit(pageSize) .get()

这条代码能跑,但你得知道skip的机制:数据库在查询时,不是直接跳过前 N 条,而是先把前面的所有数据查出来,再丢弃。数据量小没问题,数据量到几千条,第 100 页的查询就会明显变慢,而且翻页越深越慢,CPU 白白耗在无效扫描上。你可以把它类比成一本几千页的书,想读第 99 页,每次都从第一页开始翻,前 98 页全被扫过才停,这就是skip的代价。

更稳的方案是用游标分页。核心思路是:记住上一页最后一条数据的位置,下一页基于这个位置继续向后查。用_id做游标就很方便,因为_id全局唯一且默认带索引。假设上一页最后一条记录的_id是lastId,下一页这样查:

const db = cloud.database() const _ = db.command const res = await db.collection('activities') .where({ status: 'published', _id: _.lt(lastId) }) .orderBy('_id', 'desc') .limit(pageSize) .get()

这里用_id倒序,通过_.lt(lastId)拿比上页末尾更早的记录。注意,如果业务要求按startTime排序,那游标就要用startTime和_id组合,保证排序键唯一。另一个办法是用上一页最后一条文档的createTime做游标,但如果同一秒内两条记录时间相同,排序就会不稳定。实际项目中,我会把分页参数封装成一个函数。前端传入页面大小和上页游标,后端统一处理,这样列表就不会越翻越慢。

3.2 报名写入与计数原子更新:杜绝超卖

报名功能的第一个版本,很多人这样写:

const activity = await db.collection('activities').doc(activityId).get() if (activity.data.status !== 'published') throw new Error('活动不可报名') if (activity.data.participantCount >= activity.data.capacity) throw new Error('名额已满') await db.collection('registrations').add({ data: {...} }) await db.collection('activities').doc(activityId).update({ data: { participantCount: activity.data.participantCount + 1 } })

这段代码表面没问题,并发一高就出事。两个用户同时读到剩余名额为 1,然后同时插入报名记录,同时把participantCount更新成相同的值,最终实际报名人数会比统计多。这就是经典的先读后写并发问题。你可以想象成售票窗口同时接到两个请求,窗口里只剩一张票,两个人都说自己抢到了,但库存只减了一次,必然有人要落空。

解决思路有两个。如果只更新一个文档,可以用条件更新加原子操作:

const result = await db.collection('activities') .where({ _id: activityId, status: 'published', participantCount: _.lt(capacity) }) .update({ data: { participantCount: _.inc(1) } }) if (result.stats.updated === 0) throw new Error('活动不存在或名额已满')

_.inc(1)是原子增加,不管并发多少请求,数据库内部会按顺序叠加,不会出现覆盖写。先扣名额再插报名记录,能挡住超卖,但报名记录如果插入失败,名额就白白扣了,所以更好的做法是放到事务里:

const result = await db.runTransaction(async transaction => { const activityRes = await transaction.collection('activities').doc(activityId).get() const activity = activityRes.data if (!activity || activity.status !== 'published') throw new Error('活动不可报名') if (activity.participantCount >= activity.capacity) throw new Error('名额已满') const dupRes = await transaction.collection('registrations') .where({ activityId, userOpenId, status: 'active' }) .count() if (dupRes.total > 0) throw new Error('你已经报名过了') await transaction.collection('registrations').add({ data: { activityId, userOpenId, status: 'active', createTime: db.serverDate() } }) await transaction.collection('activities').doc(activityId).update({ data: { participantCount: _.inc(1), updatedAt: db.serverDate() } }) return { ok: true } })

事务能保证报名记录和活动计数要么同时成功,要么同时失败。这里查重、加记录、加计数在同一个事务里,用户手机卡一下也不会出现脏数据。需要注意,事务中所有数据库操作都通过transaction对象执行,不要混用db对象,否则事务会失效。还要注意事务对操作数量和时间有要求,别把耗时的外部请求丢进去,后面第五节我会专门说这个问题。

3.3 更新与删除的边界:别随手 remove

很多初学者在云数据库里做“取消报名”时会直接doc(id).remove(),这种做法我强烈不建议。原因有三个:第一,一条报名记录可能关联支付记录、审批记录,硬删后出了问题没法追溯;第二,用户如果误操作,你很难恢复;第三,统计口径会变,明明该有历史记录却消失了。所以正常业务做法是软删除:更新status字段,标记为cancelled。

await transaction.collection('registrations').where({ _id: regId, userOpenId: userOpenId }).update({ data: { status: 'cancelled', cancelTime: db.serverDate() } }) await transaction.collection('activities').doc(activityId).update({ data: { participantCount: _.inc(-1) } })

这里用了inc(-1)把人数减回去,同样必须在事务里执行。更新时还有一个常见坑:update是局部更新,只修改你传入的字段,不会动其他字段,这是推荐用法;set则是整体覆盖,如果set({ data: { title: '新标题' } })传少了字段,其他字段会被清空。所以更新单条记录优先用update,除非你真想替换整条文档。

真删除不是不能用,比如管理员清理测试数据,可以用where({ status: 'cancelled', createTime: _.lt(...) }).remove()。但要先确认没有其他集合引用_id,否则会出现一堆悬空引用。批量删除建议在云函数里分批执行,一次最多处理的数量有限制,循环删除时注意别超过请求超时时间。硬删之前最好先导出备份,这个习惯能帮你省掉很多后悔药的钱。

4. 进阶:聚合、索引与读写性能调优

4.1 用聚合管道统计报名人数

管理端想看到每个活动的报名人数,一个直接但错误的方式是:把所有报名记录get到前端,然后for循环统计。这样拉的数据量可能是几万条,页面直接卡死。正确做法是把统计任务交给数据库,用聚合管道在服务端算好,只把结果返回给客户端。

聚合语句这样写:

const res = await db.collection('registrations').aggregate() .match({ status: 'active' }) .group({ _id: '$activityId', total: $.sum(1) }) .end()

解释一下每一段在干什么。match先把无效的取消记录过滤掉,group按activityId分组,$.sum(1)表示每个分组内每出现一条记录就加 1。这一步结束后,数据库返回的是一个活动ID对应报名人数的映射,数量级从我原本要拉取的几万条压缩到几百条。你可以把match理解成漏斗上层的粗筛,group是一排小桶,每条数据落到对应桶里。

如果还想把活动标题、开始时间一起带出来,就用lookup做关联查询,行为类似 SQL 里的left join:

const res = await db.collection('registrations').aggregate() .match({ status: 'active' }) .lookup({ from: 'activities', localField: 'activityId', foreignField: '_id', as: 'activityInfo' }) .group({ _id: '$activityId', total: $.sum(1), activity: $.first('$activityInfo') }) .end()

lookup把registrations里的activityId和目标集合activities的_id对齐,as指定结果的字段名。拿到结果后前端的渲染代码会简单很多。不过我得泼一盆冷水:lookup通常需要目标字段有索引,否则大集合下性能会很难看。而且每个聚合管道都挺消耗数据库计算资源,不要写一个通用聚合函数,然后所有页面都调用,尽量在业务入口按需调用。

另外,如果业务场景是“首页活动列表展示报名人数”,不需要每次从聚合里现算。更合适的方案是维护activities.participantCount这个冗余字段,列表查询直接读字段,只有管理端统计页才走聚合。这本质上是“读时计算”和“写时计算”的取舍,我倾向于列表读时零计算,把计算压力放在写入事务里,这样可以大幅降低页面查询的响应时间。

4.2 索引:查询慢的时候先看这里

云数据库的查询性能,百分之八十由索引决定。很多人建表之后什么都不配,数据一多就发现where查询变慢,接着去查是不是代码写错了,其实索引才是主因。

需要注意,云数据库默认会给_id建唯一索引,但你的业务字段不会自动建。比如上面活动列表的查询条件where({ status: 'published' }).orderBy('startTime', 'asc'),这里涉及两个字段:条件字段status,排序字段startTime。如果只建status单字段索引,排序时数据库仍然要把所有published的记录拉到内存里排序,数据多了就慢。正确做法是建一个组合索引:status升序、startTime升序。

组合索引的字段顺序很关键。规则可以概括为:等值条件字段放前面,排序字段放后面;范围条件字段,比如startTime >= xxx,一般放最后。比如报名记录里查某个活动的有效报名:

db.collection('registrations') .where({ activityId: 'xxx', status: 'active' }) .orderBy('createTime', 'desc') .get()

索引顺序建议是activityId、status、createTime,因为前两个是等值过滤,createTime是排序。如果把createTime放前面,等于先按时间排完再过滤活动,索引作用大打折扣。你可以把组合索引想象成一个多级目录:先按第一个字段分大类,再按第二个字段分小类,最后按第三个字段排序,目录层级和你的查询条件对不上,查起来自然慢。

建索引的方式是在控制台的索引管理里新增,选集合、字段、排序方式即可。部分平台支持后台异步建索引,大数据量时要等一段时间。要注意,索引不是越多越好,每多一个索引,写入时就要多更新一棵 B 树,写入性能会下降。我的习惯是一个集合只建三到五个关键组合索引,覆盖所有核心查询,不给无关字段加索引。比如我不会给location单独加索引,因为筛选地点的场景很少,加了这个索引平时用不上,反而拖慢写入。

4.3 客户端直连查询的隐性瓶颈

如果查询通过客户端直连数据库,即使索引建得好,也可能被权限规则拖慢。因为客户端每次读数据,数据库都要按权限规则做一次过滤,规则越复杂,扫描成本越高。比如规则里写了doc.status == 'published' && auth.openid != null,数据库必须对每条记录做条件判断。这个判断在几十条数据时没感觉,到十万条时,每条都过一遍规则,响应时间会明显上升。

如果遇到“有索引但还是很慢”的情况,先做两件事:把查询改成云函数访问,看看耗时是否下降;再检查是不是规则里用了很贵的函数调用。不是所有时候都应该追求客户端直连,读操作偶尔走一次云函数,完全可以接受。尤其是管理端查询,直接让云函数访问,权限规则设成“仅管理端可读写”,性能和安全性都更好。

聚合查询也是同样道理。小程序端不能执行某些聚合阶段,或者执行时受权限限制很大,所以聚合一般都在云函数里跑。这再次说明,云函数不是“为了用而用”,它是云数据库综合操作里的天然枢纽。你把逻辑放在这里,权限、性能、事务都能集中控制,后续迭代也不会东一榔头西一棒子。

5. 常见问题与排查技巧实录

5.1 集合权限导致前端查不到数据

这个问题的典型表现是:在控制台看集合有数据,但小程序里查询结果total是 0。大多数原因是权限模式设成了“仅创建者可读写”,而当前用户没有创建过任何记录,所以什么都读不到。排查时先打开控制台看集合权限,再把查询放到云函数里跑,如果云函数能查到,就可以确定是权限配置问题。

如果业务上确实需要公开活动信息,就把activities的权限改成“所有用户可读”;如果数据不是公开的,就不要期望客户端能直接读,改为云函数查询并判断身份。自定义安全规则写错的时候尤其隐蔽,比如布尔运算优先级没搞清楚,建议先在控制台用一个用户身份模拟测试,再放出来。记住一个原则:权限规则越简单越不容易出错,复杂校验一律放云函数。

5.2 分页结果顺序不稳定或数据重复

使用skip分页时,如果查询的排序字段有重复值(比如多条记录createTime相同),下一页的排序结果可能不稳定,导致同一记录被翻出来两次,或者漏掉记录。解决方法是增加一个唯一排序条件,比如orderBy('createTime', 'desc').orderBy('_id', 'desc'),确保总排序是确定的。如果使用游标分页,也要注意基于_id的游标和排序方向一致。

我踩过的另一个坑是:分页时用了skip,然后在用户下拉加载更多时直接把page加 1,但列表数据中间有新增记录被where过滤掉了,页码和位置就会错位。用游标方案之后,新增记录不影响下一页的位置,旧数据依然是连续滑动的,这也是我推荐游标分页的原因之一。如果你已经在线上用了skip并且数据量不大,可以先不改,但数据到百K级别后,尽量升级成游标方案。

5.3 并发下统计数字不准

如果你的活动报名统计偶尔正确,偶尔多了或少了,多半是没有使用原子操作和事务。先查所有participantCount更新的地方,是不是都用了_.inc;再查报名记录插入和计数更新是不是放在同一个事务里。只在某一条路径用了事务,另一条路径直接update自增,仍然会有漏网之鱼。统一入口很重要,所有写操作都走同一个云函数,不要今天写一个报名云函数,明天又在客户端直连改数据库。

如果已经出了脏数据,不要急着手工改计数。可以写一个临时的聚合脚本,把registrations按activityId统计出真实人数,再批量覆盖到activities.participantCount。注意覆盖操作前要把活动的报名入口先关掉,否则会边算边写,永远对不上。这个方法我用了好几次,每次都能把乱掉的数字拉回正轨。

5.4 批量写入超时或触发限流

需要一次性导入几百条初始数据时,客户端循环add很容易超时。正确姿势是在云函数里分批插入,比如每批 100 条,用for循环控制,每批之间可以稍微暂停一下。云函数单次执行时间有限,如果数据量很大,不要一个函数硬扛,改成多次调用,或者按批次触发任务。我见过有人在一个函数里循环插入几千条,跑到一半超时,退回一堆“未知错误”,查日志才发现是执行时间超过了限制。

批量add时还要注意重复提交。网络超时后用户可能重试,如果没有幂等控制,会产生重复数据。一个简单方案是给每条数据加一个业务唯一的requestId,写入前用where({ requestId }).limit(1).get()查一下是否已存在,存在就跳过。这个方法比单纯靠_id去重要可靠得多,因为_id是在调用add后才生成的,重试时你无法区分是“上次成功但响应丢了”还是“上次根本没成功”。

5.5 事务超时与嵌套操作

云数据库事务有执行时间上限,事务里不要做网络请求、不要调用第三方API、不要写耗时很长的循环。事务越小越好,只把需要一致性的操作放进去。比如报名事务只包含读活动、查重、写报名、更新计数,如果这时候在事务里再调用一个推送服务,极有可能超时回滚,用户看到报名失败但名额又被扣了,体验很差。推送这种非核心动作放到事务提交成功之后再异步去发,别把它和数据库写操作混在一起。

事务执行失败时,要注意捕获错误并返回给前端明确的提示,不要让用户看着一直转圈。前端发请求要设置合理的超时时间,并在失败后允许重试。事务本身不会自动重试,重试逻辑要你自己在云函数里控制,但重试前一定要考虑是不是已经提交成功了,避免同一用户创建两个报名记录。稳妥做法是重试前用查重逻辑再确认一次。这个问题在弱网环境特别容易出现,别等用户投诉了再修。


说一点个人体会。我早期做云数据库,总想着把代码写得简洁,能直连就直连,能少写一步就少写一步。后来业务量起来,踩了权限、并发、索引三个大坑,才明白“综合操作”的关键不是某一个API用得熟,而是每一步都在对的位置上:读写入口收敛到云函数,计数更新用原子操作,复杂的统计交给聚合,列表分页用游标,权限规则保持简单。这个思路放到其他业务场景里也一样能复用。希望这篇能帮你少走几圈弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 19:17:58

GenFlow 3.0:生成式AI工作流编排平台深度解析

做生成式 AI 项目这几年,我最大的感受是:模型能力本身已经不是瓶颈,真正难的是把模型调用、提示词管理、数据流转、人工审核这些环节拧成一条稳定可控的生产链路。GenFlow 3.0 就是这样一个重新定义生成式 AI 工作流的智能编排平台&#xff0…

作者头像 李华
网站建设 2026/10/21 19:16:36

开放式代码评审:从审批到协作的完整落地指南

代码评审这事,说简单也简单,就俩字:看代码。说复杂能吵一宿:格式对不齐要吵、实现方式要吵、要不要拆这个函数也要吵。我见过不少团队,也上手搭过好几轮评审流程,最后发现,真正能把 code review…

作者头像 李华
网站建设 2026/10/21 19:16:02

FM、LLM、SLM怎么选?一套面向业务边界的模型选型决策框架

上个月帮一个团队做技术方案评审,产品经理上来第一句就是"直接上最火的LLM不就行了",工程负责人当场沉默——那套系统要跑在一张消费级显卡上,LLM的参数量连部署门槛都摸不到。类似的分歧我见过太多次了,很多人把SLM、L…

作者头像 李华
网站建设 2026/10/21 19:15:34

FANUC PMC编程实战指南:从梯形图地址表到调试避坑手册

简介:FANUC数控系统PMC编程说明书是一份面向数控机床电气调试、维修及程序开发人员的专业PDF文档,内容围绕BEIJING-FANUC PMC PA1/SA1/SA3型号展开,系统梳理可编程机床控制器(PMC)的结构原理、指令系统、编程规则与调试…

作者头像 李华
网站建设 2026/10/21 19:15:33

三菱FX3U与485ADP-MB的Modbus RTU通讯模板程序实战

做设备改造和现场调试这些年,三菱FX3U算是小型机里出镜率最高的一台。但真到了跟仪表、变频器要数据的时候,很多人第一反应是头大——串口协议、寄存器地址、CRC校验、轮询时序,每一项都能让调试现场原地炸裂。我这次整理的三菱FX3U FX3U-48…

作者头像 李华
网站建设 2026/10/21 19:14:25

LiteLLM:统一100+大模型的协议级抽象层

1. 项目概述:LiteLLM 是什么,它解决的不是“模型调用”而是“模型调度”的本质问题 LiteLLM 这个名字刚出现时,很多人第一反应是:“又一个 LLM 封装库?”——但实际用过两周后,我把它从开发机里删了三次又装…

作者头像 李华