标签组件
适用于 @midwayjs/faas 、@midwayjs/web 、@midwayjs/koa 和 @midwayjs/express 多种框架的通用标签组件。
使用场景
标签是一种抽象化的服务端常用系统化能力,可用于多种用途,如:
- 组织管理资源
- 实现分类系统(面向内容、人群等)
 - 资源管理系统
- 图片添加各种颜色标签、物体和场景标签,通过标签筛选图片
 - 视频等素材标签
 
 
 - 访问控制
- 权限系统(管理员、编辑、游客)
 
 - 状态系统(编辑中、已发布等)
 
基于标签系统提供的增删改查,以及通过标签,对绑定了标签的 实体 进行增删改查,能够很方便的实现更多高级的业务逻辑。
标签系统就是为了这种业务场景,让服务端基于标签能力,实现更高效、便捷的业务开发。
相关信息:
| web 支持情况 | |
|---|---|
| @midwayjs/koa | ✅ | 
| @midwayjs/faas | ✅ | 
| @midwayjs/web | ✅ | 
| @midwayjs/express | ✅ | 
如何使用?
- 安装依赖
 
$ npm i @midwayjs/tags --save
- 在 configuration 中引入组件
 
// src/configuration.ts
import { Configuration } from '@midwayjs/core';
import * as tags from '@midwayjs/tags';
@Configuration({
  imports: [
    // ...
    tags
  ],
})
export class MainConfiguration {}
- 添加配置
 
// src/config/config.local.ts
export default {
  tags: {
    clients: {
      'tagGroup1': {
        // 使用 本机内存 作为数据存储
        dialectType: 'memory',
      },
    },
  }
}
- 在代码中调用
 
// 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;
  }
}
方法
新增标签 new
new(tagDefine:  {
  // 标签名,在同一个 group 里面不能重复
  name: string;
  // 标签描述
  desc?: string;
}): Promise<{
  success: boolean;
  message: string;
  // 标签id
  id?: number;
}>;
删除标签 remove
删除标签也会删除和这个标签绑定的实体关系
remove(tagIdOrName: number | string): Promise<{
  success: boolean;
  message: string;
  // 标签id
  id?: number;
}>;
更新标签 update
更细一个标签的基础信息
update(tagIdOrName: number | string, params: Partial< {
  name: string;
  desc?: string;
}>): Promise<{
  success: boolean;
  message: string;
  // 标签id
  id?: number;
}>;
列举标签 list
搜索标签,支持分页
list(listOptions?: {
  // 搜索的标签,支持传入标签 id 和标签名
  tags?: Array<number | 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;
}>;
绑定实体 bind
绑定实体的意思就是将其他的任何东西绑定到一个标签上,这里的实体可以是一张图片、也可以是一个文件,实体的id由用户自己控制
bind(bindOptions: {
  // 标签列表
  tags: Array<number | string>;
  // 不存在标签的话自动创建标签,并绑定,默认为false
  autoCreateTag?: boolean;
  // 实体id
  objectId: number,
}): Promise<{
  success: boolean;
  message: string;
}>
解绑实体 unbind
unbind(unbindOptions: {
  // 解绑的多个标签,标签id或者是标签 name
  tags: Array<number | string>,
  // 实体id
  objectId: number,
}): Promise<{
  success: boolean;
  message: string;
}>
根据标签列举实体 listObjects
listObjects(listOptions?: {
  // 标  签id或者是标签 name
  tags?: Array<string|number>;
  count?: boolean;
  // 检索的时候标签是采用交集还是并集,取值为 and 和 or
  type?: MATCH_TYPE;
  pageSize?: number;
  page?: number;
}): Promise<{
  // 实体的 id 列表
  list: number[];
  // 实体总数
  total?: number;
}>;
根据实体获取标签 listObjectTags
listObjectTags(listOptions?: {
  // 实体id
  objectId: number;
  count?: boolean;
  pageSize?: number;
  page?: number;
}): Promise<{
  list: { // 标签列表
    name: string;
    desc?: string;
    id: number;
    createAt: number;
    updateAt: number;
  }[];
  // 标签总数
  total?: number;
}>;
配置
Tags 支持内存存储(默认)和 mysql 数据库存储两种方式,下面是一个配置的示例:
// src/config/config.local.ts
export default {
  tags: {
    clients: {
      'tagGroup1': {
        // 使用 本机内存 作为数据存储
        dialectType: 'memory',
      },
      'tagGroup2': {
        // 使用 mysql 作为数据存储
        dialectType: 'mysql',
        // 自动同步表结构
        sync: true,
        // mysql 连接实例
        instance: mysqlConnection.promise(),
      },
    },
  }
}
内存存储配置
| 配置 | 值类型 | 默认值 | 配置描述 | 
|---|---|---|---|
| dialectType | string memory | - | 配置为 memory,则启用内存存储 | 
Mysql 存储配置
如果要使用 Mysql 数据库作为数据存储,那么需要将 Mysql 的 数据库连接对象 传入 tags 的配置中。
| 配置 | 值类型 | 默认值 | 配置描述 | 
|---|---|---|---|
| dialectType | string mysql | - | 配置为 mysql,则启用 Mysql 存储 | 
| sync | boolean | false | 自动同步 Tags 的表结构,Tags组件会创建两张数据表,详见下方的数据表信息 | 
| instance | { query: (sql: string, placeholder?: any[])}: Promise<[]> | - | Mysql 连接的示例,需要提供一个 query 方法,可以查看下面的示例 | 
| tablePrefix | string | - | 数据表前缀 | 
| tableSeparator | string | _ | 数据表的拼接分隔符 | 
下面是使用 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();
  }
}
数据表信息
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
当你在配置中启用 sync 的自动表结构同步时,如果没有这两张表,就会根据下述的表结构创建对应的数据表:
tag 表结构:
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)
)