(feature)(headless) Add tag rest api (#733)

This commit is contained in:
jipeli
2024-02-21 17:45:28 +08:00
committed by GitHub
parent d10801ef38
commit b8831317e9
18 changed files with 699 additions and 3 deletions

View File

@@ -0,0 +1,9 @@
package com.tencent.supersonic.headless.api.pojo;
import lombok.Data;
@Data
public class TagDefineParams {
private String expr;
}

View File

@@ -0,0 +1,8 @@
package com.tencent.supersonic.headless.api.pojo.enums;
public enum TagDefineType {
FIELD,
DIMENSION,
Tag
}

View File

@@ -0,0 +1,27 @@
package com.tencent.supersonic.headless.api.pojo.enums;
import java.util.Objects;
public enum TagType {
ATOMIC,
DERIVED;
public static TagType of(String src) {
for (TagType tagType : TagType.values()) {
if (Objects.nonNull(src) && src.equalsIgnoreCase(tagType.name())) {
return tagType;
}
}
return null;
}
public static Boolean isDerived(String src) {
TagType tagType = of(src);
return Objects.nonNull(tagType) && tagType.equals(DERIVED);
}
public static TagType getType(TagDefineType tagDefineType) {
return Objects.nonNull(tagDefineType) && TagDefineType.Tag.equals(tagDefineType) ? TagType.DERIVED
: TagType.ATOMIC;
}
}

View File

@@ -0,0 +1,33 @@
package com.tencent.supersonic.headless.api.pojo.request;
import com.alibaba.fastjson.JSONObject;
import com.tencent.supersonic.headless.api.pojo.SchemaItem;
import com.tencent.supersonic.headless.api.pojo.TagDefineParams;
import com.tencent.supersonic.headless.api.pojo.enums.TagDefineType;
import com.tencent.supersonic.headless.api.pojo.enums.TagType;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import lombok.Data;
@Data
public class TagReq extends SchemaItem {
private Long modelId;
private Map<String, Object> ext = new HashMap<>();
private TagDefineType tagDefineType;
private TagDefineParams tagDefineParams;
public String getTypeParamsJson() {
return JSONObject.toJSONString(tagDefineParams);
}
public String getExtJson() {
return Objects.nonNull(ext) && ext.size() > 0 ? JSONObject.toJSONString(ext) : "";
}
public TagType getType() {
return TagType.getType(tagDefineType);
}
}

View File

@@ -0,0 +1,29 @@
package com.tencent.supersonic.headless.api.pojo.response;
import com.tencent.supersonic.headless.api.pojo.SchemaItem;
import com.tencent.supersonic.headless.api.pojo.TagDefineParams;
import com.tencent.supersonic.headless.api.pojo.enums.TagDefineType;
import java.util.HashMap;
import java.util.Map;
import lombok.Data;
import lombok.ToString;
@Data
@ToString(callSuper = true)
public class TagResp extends SchemaItem {
private Long modelId;
private String type;
private Map<String, Object> ext = new HashMap<>();
private TagDefineType tagDefineType = TagDefineType.FIELD;
private TagDefineParams tagDefineParams;
public String getExpr() {
return tagDefineParams.getExpr();
}
}

View File

@@ -0,0 +1,79 @@
package com.tencent.supersonic.headless.server.persistence.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.Data;
@Data
@TableName("s2_tag")
public class TagDO {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主体域ID
*/
private Long modelId;
/**
* 指标名称
*/
private String name;
/**
* 字段名称
*/
private String bizName;
/**
* 描述
*/
private String description;
/**
* 指标状态,0正常,1下架,2删除
*/
private Integer status;
/**
* 敏感级别
*/
private Integer sensitiveLevel;
/**
* 类型 DERIVED,ATOMIC
*/
private String type;
/**
* 创建时间
*/
private Date createdAt;
/**
* 创建人
*/
private String createdBy;
/**
* 更新时间
*/
private Date updatedAt;
/**
* 更新人
*/
private String updatedBy;
/**
* 类型参数
*/
private String defineType;
private String typeParams;
private String ext;
}

View File

@@ -0,0 +1,11 @@
package com.tencent.supersonic.headless.server.persistence.mapper;
import com.tencent.supersonic.headless.server.persistence.dataobject.TagDO;
import com.tencent.supersonic.headless.server.pojo.TagFilter;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TagCustomMapper {
List<TagDO> query(TagFilter tagFilter);
}

View File

@@ -0,0 +1,10 @@
package com.tencent.supersonic.headless.server.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tencent.supersonic.headless.server.persistence.dataobject.TagDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TagMapper extends BaseMapper<TagDO> {
}

View File

@@ -0,0 +1,18 @@
package com.tencent.supersonic.headless.server.persistence.repository;
import com.tencent.supersonic.headless.server.persistence.dataobject.TagDO;
import com.tencent.supersonic.headless.server.pojo.TagFilter;
import java.util.List;
public interface TagRepository {
Long create(TagDO tagDO);
void update(TagDO tagDO);
TagDO getTagById(Long id);
List<TagDO> query(TagFilter tagFilter);
}

View File

@@ -0,0 +1,44 @@
package com.tencent.supersonic.headless.server.persistence.repository.impl;
import com.tencent.supersonic.headless.server.persistence.dataobject.TagDO;
import com.tencent.supersonic.headless.server.persistence.mapper.TagCustomMapper;
import com.tencent.supersonic.headless.server.persistence.mapper.TagMapper;
import com.tencent.supersonic.headless.server.persistence.repository.TagRepository;
import com.tencent.supersonic.headless.server.pojo.TagFilter;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
@Slf4j
@Repository
public class TagRepositoryImpl implements TagRepository {
private final TagMapper mapper;
private final TagCustomMapper tagCustomMapper;
public TagRepositoryImpl(TagMapper mapper,
TagCustomMapper tagCustomMapper) {
this.mapper = mapper;
this.tagCustomMapper = tagCustomMapper;
}
@Override
public Long create(TagDO tagDO) {
mapper.insert(tagDO);
return tagDO.getId();
}
@Override
public void update(TagDO tagDO) {
mapper.updateById(tagDO);
}
@Override
public TagDO getTagById(Long id) {
return mapper.selectById(id);
}
@Override
public List<TagDO> query(TagFilter tagFilter) {
return tagCustomMapper.query(tagFilter);
}
}

View File

@@ -0,0 +1,13 @@
package com.tencent.supersonic.headless.server.pojo;
import java.util.List;
import lombok.Data;
@Data
public class TagFilter extends MetaFilter {
private String type;
private List<Integer> statusList;
}

View File

@@ -0,0 +1,59 @@
package com.tencent.supersonic.headless.server.rest;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.auth.api.authentication.utils.UserHolder;
import com.tencent.supersonic.headless.api.pojo.request.TagReq;
import com.tencent.supersonic.headless.api.pojo.response.TagResp;
import com.tencent.supersonic.headless.server.service.TagService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/semantic/tag")
public class TagController {
private final TagService tagService;
public TagController(TagService tagService) {
this.tagService = tagService;
}
@PostMapping("/create")
public TagResp create(@RequestBody TagReq tagReq,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
User user = UserHolder.findUser(request, response);
return tagService.create(tagReq, user);
}
@PostMapping("/update")
public TagResp update(@RequestBody TagReq tagReq,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
User user = UserHolder.findUser(request, response);
return tagService.update(tagReq, user);
}
@DeleteMapping("delete/{id}")
public Boolean delete(@PathVariable("id") Long id,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
User user = UserHolder.findUser(request, response);
tagService.delete(id, user);
return true;
}
@GetMapping("getTag/{id}")
public TagResp getTag(@PathVariable("id") Long id,
HttpServletRequest request,
HttpServletResponse response) {
return tagService.getMetric(id);
}
}

View File

@@ -0,0 +1,20 @@
package com.tencent.supersonic.headless.server.service;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.headless.api.pojo.request.TagReq;
import com.tencent.supersonic.headless.api.pojo.response.TagResp;
import com.tencent.supersonic.headless.server.pojo.TagFilter;
import java.util.List;
public interface TagService {
TagResp create(TagReq tagReq, User user) throws Exception;
TagResp update(TagReq tagReq, User user) throws Exception;
void delete(Long id, User user) throws Exception;
TagResp getMetric(Long id);
List<TagResp> query(TagFilter tagFilter);
}

View File

@@ -0,0 +1,165 @@
package com.tencent.supersonic.headless.server.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.common.pojo.enums.StatusEnum;
import com.tencent.supersonic.common.pojo.exception.InvalidArgumentException;
import com.tencent.supersonic.headless.api.pojo.TagDefineParams;
import com.tencent.supersonic.headless.api.pojo.enums.TagDefineType;
import com.tencent.supersonic.headless.api.pojo.request.TagReq;
import com.tencent.supersonic.headless.api.pojo.response.TagResp;
import com.tencent.supersonic.headless.server.persistence.dataobject.TagDO;
import com.tencent.supersonic.headless.server.persistence.repository.TagRepository;
import com.tencent.supersonic.headless.server.pojo.TagFilter;
import com.tencent.supersonic.headless.server.service.TagService;
import com.tencent.supersonic.headless.server.utils.NameCheckUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class TagServiceImpl implements TagService {
private final TagRepository tagRepository;
public TagServiceImpl(TagRepository tagRepository) {
this.tagRepository = tagRepository;
}
@Override
public TagResp create(TagReq tagReq, User user) throws Exception {
checkParam(tagReq);
checkExit(tagReq);
TagDO tagDO = convert(tagReq);
tagDO.setCreatedBy(user.getName());
tagDO.setCreatedAt(new Date());
tagDO.setStatus(StatusEnum.ONLINE.getCode());
tagRepository.create(tagDO);
return convert(tagDO);
}
@Override
public TagResp update(TagReq tagReq, User user) throws Exception {
if (Objects.isNull(tagReq.getId()) || tagReq.getId() <= 0) {
throw new RuntimeException("id is empty");
}
TagDO tagDO = tagRepository.getTagById(tagReq.getId());
if (Objects.nonNull(tagDO) && tagDO.getId() > 0) {
if (Objects.nonNull(tagReq.getExt()) && !tagReq.getExt().isEmpty()) {
tagDO.setExt(tagReq.getExtJson());
}
}
if (Objects.nonNull(tagReq.getTagDefineType())) {
tagDO.setDefineType(tagReq.getTagDefineType().name());
}
if (Objects.nonNull(tagReq.getTagDefineParams()) && !StringUtils.isBlank(
tagReq.getTagDefineParams().getExpr())) {
tagDO.setTypeParams(tagReq.getTypeParamsJson());
}
tagDO.setUpdatedBy(user.getName());
tagDO.setUpdatedAt(new Date());
tagRepository.update(tagDO);
return convert(tagDO);
}
@Override
public void delete(Long id, User user) throws Exception {
TagDO tagDO = tagRepository.getTagById(id);
if (Objects.isNull(tagDO)) {
throw new RuntimeException("tag not found");
}
tagDO.setStatus(StatusEnum.DELETED.getCode());
tagDO.setUpdatedBy(user.getName());
tagDO.setUpdatedAt(new Date());
tagRepository.update(tagDO);
}
@Override
public TagResp getMetric(Long id) {
return convert(tagRepository.getTagById(id));
}
@Override
public List<TagResp> query(TagFilter tagFilter) {
List<TagDO> tagDOS = tagRepository.query(tagFilter);
if (!CollectionUtils.isEmpty(tagDOS)) {
return tagDOS.stream().map(tagDO -> convert(tagDO)).collect(Collectors.toList());
}
return new ArrayList<>();
}
private void checkExit(TagReq tagReq) {
TagFilter tagFilter = new TagFilter();
tagFilter.setModelIds(Arrays.asList(tagReq.getModelId()));
//tagFilter.setStatusList(Arrays.asList(StatusEnum.ONLINE.getCode(),StatusEnum.OFFLINE.getCode()));
List<TagResp> tagResps = query(tagFilter);
if (!CollectionUtils.isEmpty(tagResps)) {
Long bizNameSameCount = tagResps.stream().filter(tagResp -> !tagResp.getId().equals(tagReq.getId()))
.filter(tagResp -> tagResp.getBizName().equalsIgnoreCase(tagReq.getBizName())).count();
if (bizNameSameCount > 0) {
throw new RuntimeException(String.format("the bizName %s is exit", tagReq.getBizName()));
}
Long nameSameCount = tagResps.stream().filter(tagResp -> !tagResp.getId().equals(tagReq.getId()))
.filter(tagResp -> tagResp.getName().equalsIgnoreCase(tagReq.getName())).count();
if (nameSameCount > 0) {
throw new RuntimeException(String.format("the name %s is exit", tagReq.getName()));
}
}
}
private void checkParam(TagReq tagReq) {
if (Objects.isNull(tagReq.getModelId()) || tagReq.getModelId() <= 0) {
throw new RuntimeException("the modelId is empty");
}
if (Objects.isNull(tagReq.getBizName()) || tagReq.getBizName().isEmpty() || Objects.isNull(tagReq.getName())
|| tagReq.getName().isEmpty()) {
throw new RuntimeException("the bizName or name is empty");
}
if (Objects.isNull(tagReq.getTagDefineType()) || Objects.isNull(tagReq.getTagDefineParams())
|| StringUtils.isBlank(tagReq.getTagDefineParams().getExpr())) {
throw new InvalidArgumentException("表达式不可为空");
}
if (NameCheckUtils.containsSpecialCharacters(tagReq.getBizName())) {
throw new InvalidArgumentException("名称包含特殊字符, 请修改");
}
}
private TagResp convert(TagDO tagDO) {
TagResp tagResp = new TagResp();
BeanUtils.copyProperties(tagDO, tagResp);
if (Objects.nonNull(tagDO.getExt()) && !tagDO.getExt().isEmpty()) {
Map<String, Object> ext = JSONObject.parseObject(tagDO.getExt(),
Map.class);
tagResp.setExt(ext);
}
tagResp.setTagDefineType(TagDefineType.valueOf(tagDO.getDefineType()));
if (Objects.nonNull(tagDO.getTypeParams()) && !tagDO.getTypeParams().isEmpty()) {
TagDefineParams tagDefineParams = JSONObject.parseObject(tagDO.getTypeParams(),
TagDefineParams.class);
tagResp.setTagDefineParams(tagDefineParams);
}
return tagResp;
}
private TagDO convert(TagReq tagReq) {
TagDO tagDO = new TagDO();
BeanUtils.copyProperties(tagReq, tagDO);
tagDO.setDefineType(tagReq.getTagDefineType().name());
tagDO.setType(tagReq.getType().name());
tagDO.setTypeParams(tagReq.getTypeParamsJson());
tagDO.setExt(tagReq.getExtJson());
return tagDO;
}
}

View File

@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tencent.supersonic.headless.server.persistence.mapper.TagCustomMapper">
<resultMap id="BaseResultMap" type="com.tencent.supersonic.headless.server.persistence.dataobject.TagDO">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="model_id" jdbcType="BIGINT" property="modelId" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="biz_name" jdbcType="VARCHAR" property="bizName" />
<result column="description" jdbcType="VARCHAR" property="description" />
<result column="status" jdbcType="INTEGER" property="status" />
<result column="sensitive_level" jdbcType="INTEGER" property="sensitiveLevel" />
<result column="type" jdbcType="VARCHAR" property="type" />
<result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
<result column="created_by" jdbcType="VARCHAR" property="createdBy" />
<result column="updated_at" jdbcType="TIMESTAMP" property="updatedAt" />
<result column="updated_by" jdbcType="VARCHAR" property="updatedBy" />
<result column="define_type" jdbcType="VARCHAR" property="defineType" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="com.tencent.supersonic.headless.server.persistence.dataobject.TagDO">
<result column="type_params" jdbcType="LONGVARCHAR" property="typeParams" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
<foreach collection="oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and
#{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem"
open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Base_Column_List">
id, model_id, name, biz_name, description, status, sensitive_level, type, created_at,
created_by, updated_at, updated_by, define_type
</sql>
<sql id="Blob_Column_List">
type_params
</sql>
<select id="query" resultMap="ResultMapWithBLOBs">
select *
from s2_tag
where status != 3
<if test="type != null and type != ''">
and type = #{type}
</if>
<if test="key != null and key != ''">
and ( id like CONCAT('%',#{key , jdbcType=VARCHAR},'%') or
name like CONCAT('%',#{key , jdbcType=VARCHAR},'%') or
biz_name like CONCAT('%',#{key , jdbcType=VARCHAR},'%') or
description like CONCAT('%',#{key , jdbcType=VARCHAR},'%')
</if>
<if test="id != null">
and id like CONCAT('%',#{id , jdbcType=VARCHAR},'%')
</if>
<if test="name != null and name != '' ">
and name like CONCAT('%',#{name , jdbcType=VARCHAR},'%')
</if>
<if test="bizName != null and bizName != ''">
and biz_name like CONCAT('%',#{bizName , jdbcType=VARCHAR},'%')
</if>
<if test="sensitiveLevel != null">
and sensitive_level = #{sensitiveLevel}
</if>
<if test="status != null">
and status = #{status}
</if>
<if test="modelIds != null and modelIds.size >0">
and model_id in
<foreach collection="modelIds" index="index" item="model" open="(" close=")"
separator=",">
#{model}
</foreach>
</if>
<if test="ids != null and ids.size >0">
and id in
<foreach collection="ids" index="index" item="id" open="(" close=")"
separator=",">
#{id}
</foreach>
</if>
<if test="createdBy != null">
and created_by = #{createdBy}
</if>
</select>
</mapper>

View File

@@ -189,4 +189,24 @@ CREATE TABLE s2_view(
alter table s2_plugin change column model `view` varchar(100);
alter table s2_view_info rename to s2_canvas;
alter table s2_query_stat_info add column `view_id` bigint(20) DEFAULT NULL after `model_id`;
alter table s2_query_stat_info add column `view_id` bigint(20) DEFAULT NULL after `model_id`;
--20240221
CREATE TABLE s2_tag(
`id` INT NOT NULL AUTO_INCREMENT,
`model_id` INT NOT NULL ,
`name` varchar(255) NOT NULL ,
`biz_name` varchar(255) NOT NULL ,
`description` varchar(500) DEFAULT NULL ,
`status` INT NOT NULL ,
`sensitive_level` INT NOT NULL ,
`type` varchar(50) NOT NULL , -- ATOMIC, DERIVED
`define_type` varchar(50) NOT NULL, -- FIELD, DIMENSION
`type_params` LONGVARCHAR DEFAULT NULL ,
`created_at` TIMESTAMP NOT NULL ,
`created_by` varchar(100) NOT NULL ,
`updated_at` TIMESTAMP DEFAULT NULL ,
`updated_by` varchar(100) DEFAULT NULL ,
`ext` LONGVARCHAR DEFAULT NULL ,
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -573,4 +573,24 @@ CREATE TABLE IF NOT EXISTS `s2_view` (
query_config VARCHAR(3000),
`admin` varchar(3000) DEFAULT NULL,
`admin_org` varchar(3000) DEFAULT NULL
);
);
CREATE TABLE IF NOT EXISTS `s2_tag` (
`id` INT NOT NULL AUTO_INCREMENT,
`model_id` INT NOT NULL ,
`name` varchar(255) NOT NULL ,
`biz_name` varchar(255) NOT NULL ,
`description` varchar(500) DEFAULT NULL ,
`status` INT NOT NULL ,
`sensitive_level` INT NOT NULL ,
`type` varchar(50) NOT NULL , -- ATOMIC, DERIVED
`define_type` varchar(50) NOT NULL, -- FIELD, DIMENSION
`type_params` LONGVARCHAR DEFAULT NULL ,
`created_at` TIMESTAMP NOT NULL ,
`created_by` varchar(100) NOT NULL ,
`updated_at` TIMESTAMP DEFAULT NULL ,
`updated_by` varchar(100) DEFAULT NULL ,
`ext` LONGVARCHAR DEFAULT NULL ,
PRIMARY KEY (`id`)
);
COMMENT ON TABLE s2_tag IS 'tag information';

View File

@@ -498,4 +498,25 @@ CREATE TABLE s2_view
query_config VARCHAR(3000),
`admin` varchar(3000) DEFAULT NULL,
`admin_org` varchar(3000) DEFAULT NULL
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `s2_tag`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`model_id` bigint(20) DEFAULT NULL,
`name` varchar(255) NOT NULL COMMENT '名称',
`biz_name` varchar(255) NOT NULL COMMENT '英文名称',
`description` varchar(500) DEFAULT NULL COMMENT '描述',
`status` int(10) NOT NULL COMMENT '状态',
`sensitive_level` int(10) NOT NULL COMMENT '敏感级别',
`type` varchar(50) NOT NULL COMMENT '类型(DERIVED,ATOMIC)',
`define_type` varchar(50) DEFAULT NULL, -- FIELD, DIMENSION
`type_params` text NOT NULL COMMENT '类型参数',
`created_at` datetime NOT NULL COMMENT '创建时间',
`created_by` varchar(100) NOT NULL COMMENT '创建人',
`updated_at` datetime NULL COMMENT '更新时间',
`updated_by` varchar(100) NULL COMMENT '更新人',
`ext` text DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT ='标签表';