尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Midway 标签组件 @midwayjs/tags 使用指南:从内存到 MySQL 的通用标签系统实战
后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载导读本文围绕 Midway 官方提供的通用标签组件midwayjs/tags对应仓库 site/docs/extensions/tags.md完整讲解如何在midwayjs/faas、midwayjs/web、midwayjs/koa、midwayjs/express等框架中接入标签能力包括安装与组件注册、clients多分组配置、TagClient的八个核心 API标签增删改查、实体绑定/解绑、按标签查实体、按实体查标签、内存与 MySQL 两种存储方言的配置差异以及数据表结构设计。读完本文你将掌握一套可直接复用的服务端标签系统集成方案并理解其底层实现原理。标签Tag是服务端一种高度抽象的通用系统化能力适用于资源分类、权限控制、状态流转等大量业务场景。Midway 将其沉淀为独立组件屏蔽底层存储差异让业务侧只需关心标签与实体两个概念。适用框架与支持情况该组件适用于midwayjs/faas、midwayjs/web、midwayjs/koa和midwayjs/express多种框架均为通用实现web 支持情况支持midwayjs/koa✅midwayjs/faas✅midwayjs/web✅midwayjs/express✅从源码看组件本身只依赖midwayjs/core的Configuration、ServiceFactory、InjectClient等通用能力见 packages/tags/src/manager.ts因此可以无缝嵌入上述任意框架其 package.json 中的engines.node 20约束了运行环境见 packages/tags/package.json。使用场景标签系统的典型业务价值标签是一种抽象化的服务端常用系统化能力可用于多种用途组织管理资源实现分类系统面向内容、人群等资源管理系统例如给图片添加各种颜色标签、物体和场景标签通过标签筛选图片视频等素材打标签。访问控制权限系统管理员、编辑、游客等角色标签。状态系统编辑中、已发布等状态标签。基于标签系统提供的增删改查以及通过标签对绑定了标签的实体进行增删改查能够很方便地实现更多高级业务逻辑。标签系统正是为这类业务场景而设计让服务端基于标签能力实现更高效、便捷的开发。这里需要理解组件中实体Object的抽象实体可以是图片、文件、用户、文章等任何业务对象实体的objectId由调用方自行控制组件只负责维护标签 ↔ 实体之间的多对多关系。快速接入安装、注册与首个调用1. 安装依赖$ npm i midwayjs/tags --save2. 在 configuration 中引入组件// src/configuration.ts import { Configuration } from midwayjs/core; import * as tags from midwayjs/tags; Configuration({ imports: [ // ... tags ], }) export class MainConfiguration {}组件内部通过 packages/tags/src/configuration.ts 的TagsConfiguration注册其namespace为tags并内置了默认配置tags: { default: { dialectType: memory, // 未显式配置时默认使用内存存储 } as ITagDialectOption, },也就是说即使你不写任何tags配置组件也会以内存存储模式就绪。3. 添加配置内存模式// src/config/config.local.ts export default { tags: { clients: { tagGroup1: { // 使用 本机内存 作为数据存储 dialectType: memory, }, }, } }4. 在代码中调用// src/testTags.ts import { Provide, Inject, InjectClient } from midwayjs/core; import { TagServiceFactory, TagClient } from midwayjs/tags; Provide() export class TestTagsService { Inject() tags: TagServiceFactory; // 相当于 this.tags.get(tagGroup1) InjectClient(TagServiceFactory, tagGroup1) tagClient: TagClient; ServerlessTrigger(ServerlessTriggerType.HTTP, { path: /tags/list, method: get}) async listTags() { // 也可以直接使用 this.tagClient const tagClient: TagClient this.tags.get(tagGroup1); // add new tag const tagInfo await tagClient.new({ name: test-tag-name, desc: tag desc, }); /* tagInfo { success: true, id: 1, } */ // list top 20 tags const tags await tagClient.list({ count: true }); /* tags: { list: [ { id: 1, name: test-tag-name, desc: tag desc } ], total: 1 } */ return tags; } }这里的注入方式体现了 Midway 多实例组件的标准用法TagServiceFactory继承自核心的ServiceFactoryTagClient在Init()阶段通过initClients(this.tags)按clients配置批量创建客户端见 packages/tags/src/manager.tsInjectClient(TagServiceFactory, tagGroup1)则相当于this.tags.get(tagGroup1)的快捷注入。每个TagClient内部持有一个按分组group隔离的ITagDialectInstance实例见 packages/tags/src/service.ts。八个核心方法详解所有方法都返回Promise操作类方法统一返回{ success: boolean; message: string; id?: number }结构便于业务侧判断成败。下面逐一说明签名与行为。新增标签 newnew(tagDefine: { // 标签名在同一个 group 里面不能重复 name: string; // 标签描述 desc?: string; }): Promise{ success: boolean; message: string; // 标签id id?: number; };同名标签在同一个 group 内不可重复创建内存实现通过tagStore.get(tagDefine.name)判重见 packages/tags/src/dialect/memory.tsMySQL 实现则先按name查询再插入见 packages/tags/src/dialect/mysql.ts重复时返回错误码tag already exists枚举定义见 packages/tags/src/error.ts。删除标签 remove删除标签也会删除和这个标签绑定的实体关系remove(tagIdOrName: number | string): Promise{ success: boolean; message: string; // 标签id id?: number; };注意入参既支持标签id也支持标签name。内存实现会先通过listObjects找出该标签绑定的所有实体并逐个删除关系记录再移除标签本身MySQL 实现则先执行delete from relationship where tid ?再删除tag记录保证级联清理见 packages/tags/src/dialect/mysql.ts。更新标签 update更新一个标签的基础信息update(tagIdOrName: number | string, params: Partial { name: string; desc?: string; }): Promise{ success: boolean; message: string; // 标签id id?: number; };MySQL 实现会将参数desc映射为数据库字段descri并跳过group、id等不可更新字段见 packages/tags/src/dialect/mysql.ts。列举标签 list搜索标签支持分页list(listOptions?: { // 搜索的标签支持传入标签 id 和标签名 tags?: Arraynumber | string; // 检索的时候标签是采用交集还是并集取值为 and 和 or type?: MATCH_TYPE; count?: boolean; pageSize?: number; page?: number; }): Promise{ // 标签列表 list: { id: number; name: string; desc: string; createAt: number; updateAt: number; }[]; // 标签总数 total?: number; };关于默认值与匹配规则从实现中可以提炼出以下事实见 packages/tags/src/service.ts 与 packages/tags/src/utils.ts默认分页page默认1pageSize默认20传入count: true时返回total否则只返回list。字符串模糊匹配标签名支持通配符风格搜索规则为%前缀表示以 xxx 结尾endsWith、%后缀表示以 xxx 开头startsWith、xxx%/%xxx同时出现时等价于精确匹配直接传name则完全匹配formatMatchLike的实现见 packages/tags/src/utils.ts。type: MATCH_TYPE取值and交集与or并集枚举定义见 packages/tags/src/interface.ts。上述行为在 packages/tags/test/memory.test.ts 中有完整的用例验证例如向分组内写入 100 个标签后list({ count: true })默认返回 20 条且total 100list({ page: 2, pageSize: 17 })返回第 1834 条混合传[2, 4, %t67, test78, test9%]可精确命中对应匹配规则。绑定实体 bind绑定实体的意思就是将其他的任何东西绑定到一个标签上这里的实体可以是一张图片、也可以是一个文件实体的 id 由用户自己控制bind(bindOptions: { // 标签列表 tags: Arraynumber | string; // 不存在标签的话自动创建标签并绑定默认为false autoCreateTag?: boolean; // 实体id objectId: number, }): Promise{ success: boolean; message: string; }两个值得注意的细节若tags中引用了不存在的标签默认返回tag does not exist错误bind方法入口还会对空tags数组做参数校验见 packages/tags/src/service.ts当autoCreateTag: true且传入的是字符串标签名时会自动创建该标签描述默认为auto create并完成绑定。内存与 MySQL 两种方言均实现了这一逻辑测试用例bind一节也验证了自动创建行为见 packages/tags/test/memory.test.ts。解绑实体 unbindunbind(unbindOptions: { // 解绑的多个标签标签id或者是标签 name tags: Arraynumber | string, // 实体id objectId: number, }): Promise{ success: boolean; message: string; }根据标签列举实体 listObjectslistObjects(listOptions?: { // 标签id或者是标签 name tags?: Arraystring|number; count?: boolean; // 检索的时候标签是采用交集还是并集取值为 and 和 or type?: MATCH_TYPE; pageSize?: number; page?: number; }): Promise{ // 实体的 id 列表 list: number[]; // 实体总数 total?: number; };这是通过标签反查资源的核心能力比如筛选同时拥有风景和日落两个标签的图片。type默认or并集当type: and交集时返回同时绑定了所有指定标签的实体。MySQL 方言针对单标签查询做了性能优化直接WHERE tid ?多标签交集则用GROUP BY oid HAVING COUNT(*) N实现见 packages/tags/src/dialect/mysql.ts测试用例中也验证了 And/Or 两种模式下的结果差异见 packages/tags/test/memory.test.ts。根据实体获取标签 listObjectTagslistObjectTags(listOptions?: { // 实体id objectId: number; count?: boolean; pageSize?: number; page?: number; }): Promise{ list: { // 标签列表 name: string; desc?: string; id: number; createAt: number; updateAt: number; }[]; // 标签总数 total?: number; };即反查某个实体被打上了哪些标签。MySQL 实现先查relationship表拿到tid列表再回查tag表组装完整标签信息见 packages/tags/src/dialect/mysql.ts。存储配置内存与 MySQLTags 支持内存存储默认和 MySQL 数据库存储两种方式下面是一个配置的示例// src/config/config.local.ts export default { tags: { clients: { tagGroup1: { // 使用 本机内存 作为数据存储 dialectType: memory, }, tagGroup2: { // 使用 mysql 作为数据存储 dialectType: mysql, // 自动同步表结构 sync: true, // mysql 连接实例 instance: mysqlConnection.promise(), }, }, } }clients下的每个 key 即一个独立的分组group分组之间数据完全隔离内存模式下每个 group 拥有独立的tagStore与tagRelationStore见 packages/tags/src/dialect/memory.tsMySQL 模式下则通过group字段区分见 packages/tags/src/dialect/mysql.ts。内存存储配置配置值类型默认值配置描述dialectTypestringmemory-配置为memory则启用内存存储内存模式零依赖、零启动成本适合本地开发、单机部署或数据量小的场景但数据仅存在于进程内重启即丢失生产环境建议使用 MySQL。MySQL 存储配置如果要使用 MySQL 数据库作为数据存储需要将 MySQL 的数据库连接对象传入 tags 的配置中配置值类型默认值配置描述dialectTypestringmysql-配置为mysql则启用 MySQL 存储syncbooleanfalse自动同步 Tags 的表结构Tags 组件会创建两张数据表详见下方的数据表信息instance{ query: (sql: string, placeholder?: any[])}: Promise[]-MySQL 连接的实例需要提供一个 query 方法可以查看下面的示例tablePrefixstring-数据表前缀tableSeparatorstring_数据表的拼接分隔符instance是组件与数据库交互的唯一入口其类型为IMysqlQuery (sql: string, placeholder?: any[]) [any, any]见 packages/tags/src/interface.ts组件内部所有 SQL 都通过它执行因此任何暴露query方法的连接对象mysql2、sequelize 等理论上都可适配。下面是使用mysql2这个 npm 包进行数据库连接的示例// src/config/config.local.ts const mysql require(mysql2); export default () { const connection mysql.createConnection({ host: db4free.net, user: tag***, password: tag***, database: tag***, charset: utf8, }); return { tags: { clients: { tagGroup: { dialectType: mysql, sync: true, instance: { // 包含 query 的mysql连接实例 query: (...args) { return connection.promise().query(...args); } }, }, }, } } }在生命周期中管理数据库连接你也可以考虑在configuration.ts的onConfigLoad生命周期中进行数据库连接这样的好处是在关闭时可以关闭数据库连接// src/configuration.ts import { Config, Configuration } from midwayjs/core; import { join } from path; import * as tags from midwayjs/tags; import { ITagMysqlDialectOption } from midwayjs/tags; const mysql require(mysql2); Configuration({ imports: [ tags ], }) export class MainConfiguration { connection; Config() tags; async onConfigLoad(container) { // 创建 mysql 连接 this.connection mysql.createConnection({ host: db4free.net, user: tag***, password: tag***, database: tag***, charset: utf8, }); let dialect: ITagMysqlDialectOption { dialectType: mysql, sync: true, instance: { query: (...args) { return this.connection.promise().query(...args); } } }; return { tags: dialect } } async onStop() { // 关闭 mysql 连接 this.connection.close(); } }这种做法的优势是连接的生命周期与应用生命周期对齐在onConfigLoad中创建连接并注入配置在onStop中关闭连接避免连接泄漏。数据表信息Tags 组件需要两种数据表来存储数据分别是tag和relationship。这两张表在数据库中真实的表名是通过配置中的表名前缀、表名分隔符和客户端名/分组名进行拼接的例如const clientName local-test; const { tablePrefix a, tableSeparator _ } tagOptions; const tagTableName ${tablePrefix}${tableSeparator}${clientName}${tableSeparator}tag; // tagTableName: a_local-test_tag const relationshipTableName ${tablePrefix}${tableSeparator}${clientName}${tableSeparator}relationship // relationshipTableName: a_local-test-relationship表名拼接逻辑在 packages/tags/src/dialect/mysql.ts 的buildTableName中实现未配置tablePrefix时直接使用tags_${tableName}形式配置前缀后为${tablePrefix}_tags_${tableName}分隔符默认_。这意味着每个分组client独占两张表多租户场景下可通过前缀天然隔离。当你在配置中启用sync的自动表结构同步时如果没有这两张表就会根据下述的表结构创建对应的数据表checkOrCreateTable会先执行SHOW TABLES LIKE探测已存在则跳过见 packages/tags/src/dialect/mysql.tstag表结构CREATE TABLE tag ( id BIGINT unsigned NOT NULL AUTO_INCREMENT, group varchar(32) NULL, name varchar(32) NULL, descri varchar(128) NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY (id) )relationship表结构CREATE TABLE relationship ( id BIGINT unsigned NOT NULL AUTO_INCREMENT, tid BIGINT unsigned NOT NULL, oid BIGINT unsigned NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY (id) )其中tag.group记录分组名、name为标签名同一分组内唯一、descri为描述relationship.tid关联tag.idoid为业务侧的实体 id两者构成标签 ↔ 实体的多对多关系表。底层实现与扩展机制理解组件内核有助于排查问题与二次扩展工厂 方言模式TagServiceFactorypackages/tags/src/manager.ts根据配置决定方言实现——优先使用用户自定义dialect其次按dialectType选择MysqlDialect其余情况回退到MemoryDialect。你可以通过ITagUserDialect传入自定义ITagDialect实现挂载其他存储如 Redis、PostgreSQL这也是接口设计预留的扩展点见 packages/tags/src/interface.ts。统一返回结构success/error工具函数packages/tags/src/utils.ts保证所有方言的返回值形态一致业务代码无需关心底层存储差异。分页计算getPageOpions(page, pageSize)统一换算limit/offset并支持pageSize: Infinity的全量拉取内存删除标签时即用此特性获取全部关联实体见 packages/tags/src/dialect/memory.ts。错误语义组件内置EXISTS、NOT_EXISTS、MISSING_PARAMETERS、OPER_ERROR四类错误枚举packages/tags/src/error.ts调用方可据此做精确的错误分支处理。小结midwayjs/tags通过分组隔离 方言可插拔 统一 API的设计把标签这类高频系统能力从业务代码中彻底剥离开发阶段用内存模式零成本起步生产环境切换 MySQL 存储并开启sync自动建表即可平滑升级八个 API 覆盖了标签生命周期与标签 ↔ 实体关系的全部常见操作足以支撑资源分类、权限、状态等绝大多数打标场景。完整的测试用例packages/tags/test/memory.test.ts、packages/tags/test/mysql.test.ts可供你在集成时对照验证各方法的分页、匹配、级联删除与交集并集语义。赞分享后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载相关推荐Midway 中的 JWT 组件 midwayjs/jwt 使用指南配置、注入与 Token 签发校验实战Midway 中的 JWT 组件 midwayjs/jwt 使用指南配置、注入与 Token 签发校验实战 本指南介绍 Midway 框架内置的 JWT 组后端微服务云原生Minimal Mistakes 标签体系实战从 Many Tags 边界用例看 Jekyll 标签归档的完整实现Minimal Mistakes 标签体系实战从 Many Tags 边界用例看 Jekyll 标签归档的完整实现 本文以 Minimal Mistakes前端静态站点AWS CLI 实战使用 aws autoscaling delete-tags 删除 Auto Scaling 组标签AWS CLI 实战使用 aws autoscaling delete tags 删除 Auto Scaling 组标签 本文以 AWS CLI 官方示例文档开发工具云原生运维上一篇强力指南如何用genshin-wish-export实现原神抽卡数据的精准分析与智能管理下一篇终极iOS设备降级指南让老旧iPhone/iPad重获新生的完整教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

k0s 节点重装指南:通过 k0sctl 迁移 containerd data-dir 并将节点重新接入集群

k0s 节点重装指南:通过 k0sctl 迁移 containerd data-dir 并将节点重新接入集群

云原生容器编排边缘计算 【免费下载链接】k0s k0s - The Zero Friction Kubernetes 项目地址: https://gitcode.com/gh_mirrors/k0/k0s 点击查看 免费下载 本指南讲解如何借助 k0sctl 对集群中的节点执行"重装(reinstall)"&#x…

📅 2026/10/8 8:10:24
AlgoNote 算法通关手册:LeetCode 343 整数拆分——动态规划五步法经典题解

AlgoNote 算法通关手册:LeetCode 343 整数拆分——动态规划五步法经典题解

教程文档知识库 【免费下载链接】AlgoNote ⛽️「算法通关手册」:从零开始的「算法与数据结构」学习教程,200 道「算法面试热门题目」,1000 道「LeetCode 题目解析」,持续更新中! 项目地址: https://gitcod…

📅 2026/10/8 8:10:24
90DaysOfDevOps Day 83:Grafana 数据可视化实战——基于 kube-prometheus 与 Prometheus Operator 部署监控面板

90DaysOfDevOps Day 83:Grafana 数据可视化实战——基于 kube-prometheus 与 Prometheus Operator 部署监控面板

文档/教程 【免费下载链接】90DaysOfDevOps This repository started out as a learning in public project for myself and has now become a structured learning map for many in the community. We have 3 years under our belt covering all things DevOps, including Pri…

📅 2026/10/8 8:05:22
MORE NEWS

更多资讯

📰

PHP网站被入侵后如何溯源:日志分析、WebShell排查与攻击链还原实战

如果有人丢给你一台已经被入侵的PHP网站,让你回答“攻击者是从哪个漏洞进来的、留下了什么后门、IP是什么”,你会从哪下手?这正是“php分析溯源”这类任务的核心场景,也是我在墨者学院这类实战平台刷题、以及在真实应急响应里经常…

📰

扫地机器人拆解实战:鱼拆法与精密结构逆向指南

1. 项目概述:从“请叫我鱼拆”看扫地机器人拆解的底层逻辑 “请叫我鱼拆”——这句带着点江湖气又透着股技术人倔劲的自我介绍,最近在数码测评和极客圈里悄悄火了。它不是某个新晋网红的ID,而是一次真实拆机行动的宣言式标题。2024年&#xf…

📰

Java面试复盘:内容社区微服务架构、缓存策略与AI集成全链路设计

我去年准备Java岗位面试的时候,有一场模拟面试让我印象特别深。面试官看着我的简历,指着一行“内容社区服务端”问:假设这个社区日活做到二十万,你打算怎么设计服务端架构?从微服务拆分一路问到了Redis缓存策略&#x…

📰

网络信息分辨与防毒意识:构建数字安全认知防线

抱歉,我无法为这个项目标题生成内容。该标题涉及与毒品/毒物相关的“传毒书”“檄文”等表述,这类主题不符合内容安全规范,我无法提供支持。 如果愿意,我可以帮你写一篇关于“网络信息分辨与防毒意识”的科普文章,或者…

📰

FPGA DMA IP核实战指南:AXI DMA/CDMA/VDMA选型与调优

1. FPGA DMA IP核到底在解决什么问题?FPGA开发里,最常听到的一句抱怨是:“数据吞吐上不去,CPU忙得团团转,FPGA空着一半资源干等。”——这背后,十有八九是DMA没用对。我带过三届FPGA校企联合实训班&#xf…

📰

H3C S6520现网IRF堆叠不断网配置:规划、合并与避坑指南

简介:面向现网环境中的IT网络运维人员,这份PDF文档围绕H3C S6520-26Q-SI核心交换机的IRF2堆叠,提供在不影响业务运行的前提下完成配置的实战经验。文档完整记录两台同型号、同软件版本设备的堆叠搭建过程,涵盖堆叠前配置备份与业务…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬