Spring Boot项目通用功能第三讲之《通用属性》

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/aiyaya_/article/details/80964156

前言

接着上两篇(《通用Service》《通用树结构操作》)通用服务的功能,我们继续说下我们的第三篇,关于通用的属性服务,先来说说为什么会想着抽离这么个服务,想必工作时间久的开发人员肯定会遇到这种问题,随着业务的增长,会对原有业务表上增加各种字段,但是有的字段并不应该归属于主表上,而是应该放在其扩展属性表上,因为有些字段仅仅是主业务其中某一种特定业务才具有的(比如:在order表中,总是有一些并不是所有订单都会有的属性,这时我们可能会将这些属性存储在一个叫ext的字段中,格式可能是json格式的字符串也可能是key:value这种数据结构,但是想想这种方式的存储是不是很不利于结构化搜索,而且还要在主表上维护扩展属性的增删改查功能,抽离下公共功能的附加表是个很好的方式),那如果我们把这种key->value结构关系单独抽离一个功能,来简化我们对这种附加属性的使用,是不是会更好呢?

实现思路

实现思路很简单,就是写一个通用的属性服务mapper和service,让任何主表的属性扩展表仅需要简单的配置就可以去使用该功能,那么现在来看下通用的扩展表结构和需要定义几个核心类:

表结构

DROP TABLE IF EXISTS `XXX_attr`;
CREATE TABLE `XXX_attr` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `object_id` varchar(64) NOT NULL COMMENT '对象ID',-- 该字段类型需与主表的主键一致
  `key` varchar(64) NOT NULL COMMENT '键',
  `value` varchar(1024) DEFAULT NULL COMMENT '值',
  `type` varchar(32) NOT NULL COMMENT '类型',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE INDEX uq_object_id_key(`object_id`, `key`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='XXX属性表';

核心类
Attribute:属性PO类
AttributeMapper:通用属性MAPPER类
InsertAttributeService、UpdateAttributeService、DeleteAttributeService、SelectAttributeService:通用属性API接口
AttributeServiceImpl:通用属性实现逻辑
AttributesChangedEvent:属性变更事件

注意

  • key就是我们的属性字段名,value就是属性字段的值,字段type用来存储value字段的类型
  • XXX 就代表你的主表喽,其中object_id存储你的主表的主键关联
  • 值得注意的是object_id字段的类型是根据主表的主键类型一致的,防止因类型不一致导致程序走不上索引问题

具体实现

Attribute对象

package com.zm.zhuma.commons.attributes.model;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Attribute<OID> implements Serializable{

    private static final long serialVersionUID = 1L;

    private Long id;

    private OID objectId;

    private String key;

    private String value;

    private String type;

}

AttributeMapper类(通用属性操作mapper)

package com.zm.zhuma.commons.attributes.mapper;

import java.util.List;

import com.zm.zhuma.commons.attributes.model.Attribute;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface AttributeMapper<OID> {

    void addAttributes(@Param(value = "tableName") String tableName, @Param(value = "attributes") List<Attribute<OID>> attributes);

    void deleteAttributes(@Param(value = "tableName") String tableName, @Param(value = "objectId") OID objectId, @Param(value = "keys") List<String> keys);

    void updateAttributes(@Param(value = "tableName") String tableName, @Param("attr") Attribute<OID> attribute);

    List<Attribute<OID>> getAttributeMapByKeys(@Param(value = "tableName") String tableName, @Param(value = "objectIds") List<OID> objectIds, @Param(value = "keys") List<String> keys);

    List<Attribute<OID>> getAttributeMapByKeyAndValue(@Param(value = "tableName") String tableName, @Param(value = "objectIds") List<OID> objectIds, @Param(value = "key") String key, @Param(value = "value") Object value);

    List<Attribute<OID>> getAttributeMapByKeyAndValues(@Param(value = "tableName") String tableName, @Param(value = "key") String key, @Param(value = "values") List<Object> values);

}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mapper.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zm.zhuma.commons.attributes.mapper.AttributeMapper">

    <insert id="addAttributes" useGeneratedKeys="true">
        insert into ${tableName} (`object_id`,`key`,`value`,`type`,`create_time`) values
        <foreach collection="attributes" item="item" index="index" separator=",">
            (#{item.objectId},#{item.key},#{item.value},#{item.type},now())
        </foreach>
    </insert>

    <delete id="deleteAttributes">
        delete from ${tableName} where `object_id`=#{objectId}
        <if test="keys != null and keys.size() > 0">
            and `key` in
            <foreach collection="keys" item="key" open="(" separator="," close=")">
                #{key}
            </foreach>
        </if>
    </delete>

    <update id="updateAttributes">
        update ${tableName} set `value` = #{attr.value},type = #{attr.type}
        <where>
            <if test="attr.id != null">id = #{attr.id}</if>
            <if test="attr.objectId != null and attr.objectId != ''"> and `object_id` = #{attr.objectId}</if>
            <if test="attr.key != null and attr.key != '' "> and `key` = #{attr.key}</if>
        </where>
    </update>

    <select id="getAttributeMapByKeys" resultType="com.zm.zhuma.commons.attributes.model.Attribute">
        select * from ${tableName}
        <where>
            <if test="objectIds != null and objectIds.size() > 0">
                `object_id` in
                <foreach collection="objectIds" item="id" open="(" separator="," close=")">
                    #{id}
                </foreach>
            </if>
            <if test="keys != null and keys.size() > 0">
                and `key` in 
                <foreach collection="keys" item="key" open="(" separator="," close=")">
                    #{key}
                </foreach>
            </if>
        </where>
    </select>

    <select id="getAttributeMapByKeyAndValue" resultType="com.zm.zhuma.commons.attributes.model.Attribute">
        select * from ${tableName}
        <where>
            <if test="objectIds != null and objectIds.size() > 0">
                `object_id` in
                <foreach collection="objectIds" item="id" open="(" separator="," close=")">
                    #{id}
                </foreach>
            </if>
            <if test="key != null and key != ''">
                and `key` = #{key}
            </if>
            <if test="value != null and value != ''">
                and `value` = #{value}
            </if>
        </where>
    </select>

    <select id="getAttributeMapByKeyAndValues" resultType="com.zm.zhuma.commons.attributes.model.Attribute">
        select * from ${tableName}
        <where>
            <if test="key != null and key != ''">
                `key` = #{key}
            </if>
            <if test="values != null and values.size() > 0">
                and `value` in
                <foreach collection="values" item="value" open="(" separator="," close=")">
                    #{value}
                </foreach>
            </if>
        </where>
    </select>

</mapper>

备注

  • 可以看到我们其实是把表名字(${tableName})作为参数传递进来,以达到通用属性mapper的目的,有没有又学到一招的感觉呢?O(∩_∩)O

增加属性接口定义

package com.zm.zhuma.commons.attributes.service;

import com.zm.zhuma.commons.attributes.model.AttributesChange;

import java.util.Map;

/**
 * @desc 插入属性服务
 *
 * @author zhuamer
 * @since 7/9/2018 11:13
 */
public interface InsertAttributeService<OID> {

    /**
     * 添加对象属性
     * 该操作将保存attributes中的属性,不存在于attributes中的属性不做任何操作
     * @param objectId 对象id
     * @param attributes 属性集合
     */
    AttributesChange<OID> addAttributes(OID objectId, Map<String, Object> attributes);

}

修改属性接口定义

package com.zm.zhuma.commons.attributes.service;

import com.zm.zhuma.commons.attributes.model.AttributesChange;

import java.util.Map;

/**
 * @desc 更新属性服务
 *
 * @author zhuamer
 * @since 7/9/2018 11:13
 */
public interface UpdateAttributeService<OID> {

    /**
     * 设置对象属性
     * @param objectId 对象id
     * @param key 属性key
     * @param value 属性值
     */
    AttributesChange<OID> setAttribute(OID objectId, String key, Object value);

    /**
     * 设置对象属性
     * 该操作将保存attributes中的属性,不存在于attributes中的属性将删除
     * @param objectId 对象id
     * @param attributes 属性map,key:属性key,value:属性值
     */
    AttributesChange<OID> setAttributes(OID objectId, Map<String, Object> attributes);

}

删除属性接口定义

package com.zm.zhuma.commons.attributes.service;

import com.zm.zhuma.commons.attributes.model.AttributesChange;

import java.util.Map;

/**
 * @desc 删除属性服务
 *
 * @author zhuamer
 * @since 7/9/2018 11:13
 */
public interface DeleteAttributeService<OID> {

    /**
     * 删除单个属性
     * @param objectId 对象id
     * @param key 属性key
     */
    AttributesChange<OID> deleteAttribute(OID objectId, String key);

    /**
     * 删除对象属性
     * @param objectId 对象id
     */
    AttributesChange<OID> deleteAttributes(OID objectId);

    /**
     * 删除对象属性
     * @param objectId 对象id
     * @param keys 属性keys
     */
    AttributesChange<OID> deleteAttributes(OID objectId, Iterable<String> keys);

}

查看属性接口定义

package com.zm.zhuma.commons.attributes.service;

import java.util.Map;

/**
 * @desc 查询属性服务
 *
 * @author zhuamer
 * @since 7/9/2018 11:13
 */
public interface SelectAttributeService<OID> {

    /**
     * 获取对象所有属性
     * @param objectId 对象id
     * @return 属性map,key:属性key,value:属性值
     */
    Map<String, Object> getAttributes(OID objectId);

    /**
     * 获取对象所有属性
     * @param objectId 对象id
     * @param objectClass 属性对应的类
     * @return 所有属性对应转化的类对象
     */
    <T> T getAttributes(OID objectId, Class<T> objectClass);

    /**
     * 获取对象某一个属性
     * @param objectId 对象id
     * @param key 属性key
     * @return 属性值
     */
    Object getAttribute(OID objectId, String key);

    /**
     * 获取对象某一个属性
     * @param objectId 对象id
     * @param key 属性key
     * @param valueClass 属性value类
     * @return 属性值
     */
    <V> V getAttribute(OID objectId, String key, Class<V> valueClass);

    /**
     * 获取对象某一批属性
     * @param objectId 对象id
     * @param keys 属性keys
     * @return 属性map,key:属性key,value:属性值
     */
    Map<String, Object> getAttributes(OID objectId, Iterable<String> keys);

    /**
     * 批量获取多个对象的属性
     * @param objectIds 对象ids
     * @param keys 属性keys
     * @return map,key:对象id,value:对象属性map(key:属性key,value:属性值)
     */
    Map<OID, Map<String, Object>> getAttributes(Iterable<OID> objectIds, Iterable<String> keys);

    /**
     * 批量获取多个对象的属性
     * @param objectIds 对象ids
     * @return map,key:对象id,value:对象属性map(key:属性key,value:属性值)
     */
    Map<OID, Map<String, Object>> getAttributes(Iterable<OID> objectIds);

    /**
     * 批量获取多个对象的属性
     * @param objectIds 对象ids
     * @param key 属性key
     * @return
     */
    Map<OID, Object> getAttributes(Iterable<OID> objectIds, String key);

    /**
     * 批量获取多个对象ID对应属性信息
     * @param objectIds 对象ids
     * @param key 属性key
     * @return
     */
    Map<OID, Object> getAttributes(Iterable<OID> objectIds, String key, Object value);

    /**
     * 一个key,不同value值
     * @param key
     * @param values
     * @return
     */
    Map<OID, Object> getAttributes(String key, Iterable<Object> values);

}

备注

  • 获取属性的方法还是比较多的,所以应该基本能满足平时开发中的业务场景了

AttributeServiceImpl实现逻辑

package com.zm.zhuma.commons.attributes.service.impl;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.google.common.collect.Maps;
import com.zm.zhuma.commons.attributes.mapper.AttributeMapper;
import com.zm.zhuma.commons.attributes.event.publisher.AttributeEventPublisher;
import com.zm.zhuma.commons.attributes.model.Attribute;
import com.zm.zhuma.commons.attributes.model.AttributeChange;
import com.zm.zhuma.commons.attributes.model.AttributesChangedEvent;
import com.zm.zhuma.commons.attributes.model.AttributesChange;
import com.zm.zhuma.commons.attributes.service.AttributeService;

import com.google.common.collect.Lists;

import com.zm.zhuma.commons.util.BeanUtil;
import com.zm.zhuma.commons.util.CollectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.exceptions.TooManyResultsException;
import org.springframework.util.Assert;

@Slf4j
public class AttributeServiceImpl<OID> implements AttributeService<OID> {

    private String table = null;

    private AttributeMapper<OID> attributeDao;

    private AttributeEventPublisher<OID> eventPublisher;

    public AttributeServiceImpl(String table, AttributeMapper<OID> attributeDao, AttributeEventPublisher<OID> eventPublisher) {
        this.table = table;
        this.attributeDao = attributeDao;
        this.eventPublisher = eventPublisher;
    }

    @Override
    public Map<String, Object> getAttributes(OID objectId) {
        Assert.notNull(objectId, "objectId is not null");

        List<Attribute<OID>> list = attributeDao.getAttributeMapByKeys(table, Lists.newArrayList(objectId), null);

        return list.stream().collect(Collectors.toMap(Attribute::getKey, this::convertType,(key1, key2) -> key2));
    }

    @Override
    public <T> T getAttributes(OID objectId, Class<T> objectClass) {
        Map<String, Object> attrMap = getAttributes(objectId);
        return BeanUtil.mapToObject(attrMap, objectClass);
    }

    @Override
    public Object getAttribute(OID objectId, String key) {
        Assert.notNull(objectId, "objectId is not null");
        Assert.notNull(key, "key is not null");

        List<Attribute<OID>> list = attributeDao.getAttributeMapByKeys(table, Lists.newArrayList(objectId), Lists.newArrayList(key));

        if (list.size() == 0) {
            return null;
        } else if (list.size() == 1) {
            return convertType(list.get(0));
        } else {
            throw new TooManyResultsException();
        }
    }

    @Override
    public <V> V getAttribute(OID objectId, String key, Class<V> valueClass) {
        return (V) getAttribute(objectId, key);
    }

    @Override
    public Map<String, Object> getAttributes(OID objectId, Iterable<String> keys) {
        Assert.notNull(objectId, "objectId is not null");
        Assert.notNull(keys, "keys is not null");

        List<Attribute<OID>> list = attributeDao.getAttributeMapByKeys(table, Lists.newArrayList(objectId), Lists.newArrayList(keys));

        return list.stream().collect(Collectors.toMap(Attribute::getKey, this::convertType,(key1, key2) -> key2));
    }

    @Override
    public Map<OID, Map<String, Object>> getAttributes(Iterable<OID> objectIds, Iterable<String> keys) {
        Assert.notNull(objectIds, "objectIds is not null");
        Assert.notNull(keys, "objectId is not null");

        Map<OID, Map<String, Object>> map = Maps.newHashMap();

        objectIds.forEach(objectId -> {
            Map<String, Object> partMap = getAttributes(objectId, keys);
            map.put(objectId, partMap);
        });

        return map;
    }

    @Override
    public Map<OID, Map<String, Object>> getAttributes(Iterable<OID> objectIds) {
        Assert.notNull(objectIds, "objectIds is not null");

        Map<OID, Map<String, Object>> map = Maps.newHashMap();

        objectIds.forEach(objectId -> {
            Map<String, Object> partMap = getAttributes(objectId);
            map.put(objectId, partMap);
        });

        return map;
    }

    @Override
    public Map<OID, Object> getAttributes(Iterable<OID> objectIds, String key) {
        Assert.notNull(objectIds, "objectIds is not null");
        Assert.notNull(key, "key is not null");

        List<Attribute<OID>> list = attributeDao.getAttributeMapByKeys(table, Lists.newArrayList(objectIds), Lists.newArrayList(key));

        return list.stream().collect(Collectors.toMap(Attribute::getObjectId, this::convertType,(key1, key2) -> key2));
    }

    @Override
    public Map<OID, Object> getAttributes(Iterable<OID> objectIds, String key, Object value) {
        Assert.notNull(objectIds, "objectIds is not null");
        Assert.notNull(key, "key is not null");

        List<Attribute<OID>> list = attributeDao.getAttributeMapByKeyAndValue(table, Lists.newArrayList(objectIds), key, value);

        return list.stream().collect(Collectors.toMap(Attribute::getObjectId, this::convertType,(key1, key2) -> key2));
    }

    @Override
    public Map<OID, Object> getAttributes(String key, Iterable<Object> values) {
        Assert.notNull(key, "key is not null");
        Assert.notNull(values, "values is not null");

        List<Attribute<OID>> list = attributeDao.getAttributeMapByKeyAndValues(table, key, Lists.newArrayList(values));

        Map<OID, Object> map = new HashMap<>();
        for (Attribute<OID> attribute : list) {
            map.put(attribute.getObjectId(), convertType(attribute));
        }

        return map;
    }

    @Override
    public AttributesChange<OID> setAttribute(OID objectId, String key, Object value) {
        Map<String, Object> attributes = Maps.newHashMap();
        attributes.put(key, value);
        return this.setAttributes(objectId, attributes);
    }


    @Override
    public AttributesChange<OID> setAttributes(OID objectId, Map<String, Object> attributes) {
        Assert.notNull(objectId, "objectId is not null");
        Assert.notNull(attributes, "attributes is not null");

        Map<String, AttributeChange> added = Maps.newHashMap();
        Map<String, AttributeChange> updated = Maps.newHashMap();
        Map<String, AttributeChange> removed = Maps.newHashMap();

        Map<String, Object> previousMap = getAttributes(objectId);

        List<String> previousKeyList = Lists.newArrayList(previousMap.keySet());
        List<String> currentKeyList = Lists.newArrayList(attributes.keySet());

        List<String> addKeyList = CollectionUtil.subtract(currentKeyList, previousKeyList);
        List<String> updateKeyList = CollectionUtil.intersection(currentKeyList, previousKeyList);
        List<String> removeKeyList = CollectionUtil.subtract(previousKeyList, currentKeyList);

        List<Attribute<OID>> addAttrList = addKeyList.stream().map(c -> {
            Attribute<OID> attribute = new Attribute<>();
            attribute.setKey(c);
            attribute.setObjectId(objectId);
            convertType(attributes.get(c), attribute);

            added.put(c, AttributeChange.builder().previous(null).current(attributes.get(c)).build());

            return attribute;
        }).collect(Collectors.toList());

        if (!CollectionUtil.isEmpty(addAttrList)) {
            attributeDao.addAttributes(table, addAttrList);
        }

        updateKeyList.forEach(c -> {
            Attribute<OID> attribute = new Attribute<>();
            attribute.setKey(c);
            attribute.setObjectId(objectId);
            convertType(attributes.get(c), attribute);

            attributeDao.updateAttributes(table, attribute);

            updated.put(c, AttributeChange.builder().previous(previousMap.get(c)).current(attributes.get(c)).build());
        });

        if (!CollectionUtil.isEmpty(removeKeyList)) {
            removeKeyList.forEach(c -> removed.put(c, AttributeChange.builder().previous(previousMap.get(c)).current(null).build()));
            attributeDao.deleteAttributes(table, objectId, removeKeyList);
        }

        return buildResult(objectId, added, updated, removed);
    }

    @Override
    public AttributesChange<OID> addAttributes(OID objectId, Map<String, Object> attributes) {
        Assert.notNull(objectId, "objectId is not null");
        Assert.notNull(attributes, "attributes is not null");

        Map<String, AttributeChange> added = Maps.newHashMap();
        Map<String, AttributeChange> updated = Maps.newHashMap();
        Map<String, AttributeChange> removed = Maps.newHashMap();

        if (!CollectionUtil.isEmpty(attributes)) {
            Map<String, Object> previousMap = getAttributes(objectId);

            List<String> previousKeyList = Lists.newArrayList(previousMap.keySet());

            List<String> currentKeyList = Lists.newArrayList(attributes.keySet());

            List<String> addKeyList = CollectionUtil.subtract(currentKeyList, previousKeyList);
            List<String> updateKeyList = CollectionUtil.intersection(currentKeyList, previousKeyList);

            List<Attribute<OID>> addAttrList = addKeyList.stream().map(c -> {
                Attribute<OID> attribute = new Attribute<>();
                attribute.setKey(c);
                attribute.setObjectId(objectId);
                convertType(attributes.get(c), attribute);

                added.put(c, AttributeChange.builder().previous(null).current(attributes.get(c)).build());

                return attribute;
            }).collect(Collectors.toList());

            if (!CollectionUtil.isEmpty(addAttrList)) {
                attributeDao.addAttributes(table, addAttrList);
            }

            updateKeyList.forEach(c -> {
                Attribute<OID> attribute = new Attribute<>();
                attribute.setKey(c);
                attribute.setObjectId(objectId);
                convertType(attributes.get(c), attribute);

                attributeDao.updateAttributes(table, attribute);

                updated.put(c, AttributeChange.builder().previous(previousMap.get(c)).current(attributes.get(c)).build());
            });
        }

        return buildResult(objectId, added, updated, removed);
    }

    @Override
    public AttributesChange<OID> deleteAttribute(OID objectId, String key) {
        return this.deleteAttributes(objectId, Lists.newArrayList(key));
    }

    @Override
    public AttributesChange<OID> deleteAttributes(OID objectId) {
        Assert.notNull(objectId, "objectId is not null");

        Map<String, AttributeChange> added = Maps.newHashMap();
        Map<String, AttributeChange> updated = Maps.newHashMap();
        Map<String, AttributeChange> removed = Maps.newHashMap();

        List<String> removeKeyList = Lists.newArrayList();

        Map<String, Object> previousMap = getAttributes(objectId);

        if (!CollectionUtil.isEmpty(previousMap)) {
            for (Map.Entry<String, Object> entry : previousMap.entrySet()) {
                removed.put(entry.getKey(), AttributeChange.builder().previous(entry.getValue()).current(null).build());
                removeKeyList.add(entry.getKey());
            }

            if (!CollectionUtil.isEmpty(removeKeyList)) {
                attributeDao.deleteAttributes(table, objectId, removeKeyList);
            }
        }

        return buildResult(objectId, added, updated, removed);
    }

    @Override
    public AttributesChange<OID> deleteAttributes(OID objectId, Iterable<String> keys) {
        Assert.notNull(objectId, "objectId is not null");
        Assert.notNull(keys, "keys is not null");

        Map<String, AttributeChange> added = Maps.newHashMap();
        Map<String, AttributeChange> updated = Maps.newHashMap();
        Map<String, AttributeChange> removed = Maps.newHashMap();

        Map<String, Object> previousMap = getAttributes(objectId);

        List<String> previousKeyList = Lists.newArrayList(previousMap.keySet());
        List<String> currentKeyList =  Lists.newArrayList(keys);

        List<String> removeKeyList = CollectionUtil.intersection(previousKeyList, currentKeyList);

        if (!CollectionUtil.isEmpty(previousMap) && !CollectionUtil.isEmpty(removeKeyList)) {
            for (String key : removeKeyList) {
                removed.put(key, AttributeChange.builder().previous(previousMap.get(key)).current(null).build());
            }
            attributeDao.deleteAttributes(table, objectId, removeKeyList);
        }

        return buildResult(objectId, added, updated, removed);
    }

    /** 保存扩展属性时对象类型转换 */
    private void convertType(Object obj, Attribute<OID> attribute) {
        String type = null;
        String value = null;

        if (obj instanceof Integer) {
            type = Integer.class.getSimpleName();
            value = obj.toString();
        } else if (obj instanceof Float) {
            type = Float.class.getSimpleName();
            value = obj.toString();
        }  else if (obj instanceof Double) {
            type = Double.class.getSimpleName();
            value = obj.toString();
        } else if (obj instanceof BigDecimal) {
            type = BigDecimal.class.getSimpleName();
            value = obj.toString();
        } else if (obj instanceof Long) {
            type = Long.class.getSimpleName();
            value = obj.toString();
        } else if (obj instanceof Date) {
            type = Date.class.getSimpleName();
            Date date = (Date) obj;
            value = String.valueOf(date.getTime());
        } else if (obj instanceof Boolean) {
            type = Boolean.class.getSimpleName();
            value = obj.toString();
        } else if (obj instanceof String) {
            type = String.class.getSimpleName();
            value = obj.toString();
        } else {
            if (null != obj )  {
                type = String.class.getSimpleName();
                value = obj.toString();
            }
        }

        attribute.setType(type);
        attribute.setValue(value);
    }

    /**
     * 返回值类型转换
     */
    private Object convertType(Attribute<OID> attribute) {
        String type = attribute.getType();
        String value = attribute.getValue();
        Object result = null;

        if (null != type && null != value) {
            if (Integer.class.getSimpleName().equals(type)) {
                result = Integer.valueOf(value);
            } else if (Float.class.getSimpleName().equals(type)) {
                result = Float.valueOf(value);
            } else if (Double.class.getSimpleName().equals(type)) {
                result = Double.valueOf(value);
            } else if (BigDecimal.class.getSimpleName().equals(type)) {
                result = BigDecimal.valueOf(Double.valueOf(value));
            } else if (Long.class.getSimpleName().equals(type)) {
                result = Long.valueOf(value);
            } else if (Date.class.getSimpleName().equals(type)) {
                result = new Date(Long.valueOf(value));
            } else if (Boolean.class.getSimpleName().equals(type)) {
                result = Boolean.valueOf(value);
            } else if (String.class.getSimpleName().equals(type)) {
                result = String.valueOf(value);
            } else {
                result = value;
            }
        }

        return result;
    }

    private AttributesChange<OID> buildResult(OID objectId, Map<String, AttributeChange> added,  Map<String, AttributeChange> updated, Map<String, AttributeChange> removed) {
        AttributesChange<OID> result = AttributesChange.<OID>builder().objectId(objectId).added(added).updated(updated).removed(removed).build();

        if (!CollectionUtil.isEmpty(added) || !CollectionUtil.isEmpty(updated) || !CollectionUtil.isEmpty(removed)) {
            sendAttributesChangeEvent(result);
        }

        return result;
    }

    /**
     * 发送属性变更消息
     */
    private void sendAttributesChangeEvent(AttributesChange<OID> attributesChange) {
        if (eventPublisher == null) {
            return;
        }

        AttributesChangedEvent<OID> event = AttributesChangedEvent.<OID>builder()
                .data(attributesChange)
                .occurredTime(new Date())
                .build();


        eventPublisher.publishAttributesChangedEvent(event, table);
        log.debug("{} is published.", event);
    }

}

备注

  • 实现逻辑代码时比较冗长,本不想粘贴出来的,但是为了代码的完整性,还是粘贴出来吧^_^,我们看到,代码中最后会统一封装一个属性变更事件,并将事件提用spring cloud stream 抛出,具体事件类请看下面

AttributesChangedEvent属性事件变更

package com.zm.zhuma.commons.attributes.model;

import java.io.Serializable;
import java.util.Date;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AttributesChangedEvent<OID> implements Serializable {
    private static final long serialVersionUID = -5098574719305009319L;

    private AttributesChange<OID> data;

    private Date occurredTime;

}
package com.zm.zhuma.commons.attributes.model;

import java.io.Serializable;
import java.util.Map;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AttributesChange<OID> implements Serializable {
    private static final long serialVersionUID = -5008407345712737581L;

    private String objectType;

    private OID objectId;

    private Map<String, AttributeChange> added;

    private Map<String, AttributeChange> updated;

    private Map<String, AttributeChange> removed;

}
package com.zm.zhuma.commons.attributes.model;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AttributeChange implements Serializable {
    private static final long serialVersionUID = -662090239071614840L;

    private Object previous;

    private Object current;

}

备注

  • 可以看到所有的能影响属性表的方法都统一的返回了属性变更事件,其实它是用added、update、remove的map类型接收,返回了增加、修改、删除属性时,属性的变更前字段和变更后的字段,为什么这么做呢?原因有两点,一个是这有利于我们对属性修改后对变更情况的的把控,有利于调用端对结果的使用,其实还有一点,就是我们会在通用属性服务实现逻辑中加入stream rabbitMQ的消息抛出,也会公用该对象

测试

属性测试控制器

package com.zhuma.demo.web.demo5;


import com.zm.zhuma.commons.attributes.model.AttributesChange;
import com.zm.zhuma.commons.attributes.service.AttributeService;
import com.zm.zhuma.commons.model.bo.Node;
import com.zm.zhuma.commons.web.annotations.ResponseResult;
import com.zm.zhuma.user.client.OrgClient;
import com.zm.zhuma.user.model.po.Org;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 * @desc 用户管理控制器
 * 
 * @author zhumaer
 * @since 7/16/2018 16:37 PM
 */
@ResponseResult
@RestController("demo4UserAttrController")
@RequestMapping("demo4/users/{userId}/attrs")
public class UserAttrController {

    @Autowired
    private AttributeService<String> userAttributeService;

    @PostMapping
    AttributesChange<String> add(@PathVariable("userId") String userId,@RequestBody Map<String, Object> attrMap) {
        return userAttributeService.addAttributes(userId, attrMap);
    }

    @GetMapping
    Map<String, Object>  get(@PathVariable("userId") String userId) {
        return userAttributeService.getAttributes(userId);
    }

    @PutMapping
    AttributesChange<String> put(@PathVariable("userId") String userId,@RequestBody Map<String, Object> attrMap) {
        return userAttributeService.setAttributes(userId, attrMap);
    }

    @DeleteMapping
    AttributesChange<String> delete(@PathVariable("userId") String userId) {
        return userAttributeService.deleteAttributes(userId);
    }
}

POST MAN截图
这里写图片描述

数据库截图
这里写图片描述

备注

  • 我们仅测试了一个新增属性的场景,课后,大家试一下修改删除和获取等场景吧^_^

结束语

通用属性服务的介绍到此结束,该服务运用到平时开发中,一定会大大增加开发效率,但是我最最担心的一点就是,因为属性表的增加,使用人会把后续业务新增字段都归结为扩展属性,都扔到属性表中,这并不是一种好的方法,还是建议主表字段和扩展属性字段分离清楚。祝好 ~~~

附上本实例代码github地址(本实例在zhuma-demo项目下,demo5模块):https://github.com/zhumaer/zhuma

欢迎关注我们的公众号或加群,等你哦!

猜你喜欢

转载自blog.csdn.net/aiyaya_/article/details/80964156