通用數(shù)據(jù)權(quán)限設(shè)計——列權(quán)限(二)

上文 通用數(shù)據(jù)權(quán)限設(shè)計——列權(quán)限(一)說了列權(quán)限的設(shè)計理念和整體架構(gòu),下面來說說具體實現(xiàn)

疑問

下面我們從疑問入手暑诸,從問題出發(fā)來看字段權(quán)限的具體祥設(shè):

  • 攔截器或者鉤子函數(shù)應(yīng)該從哪兒入手惨缆,什么時候開始接入序列化返回的進行我們的邏輯處理;
  • 怎么獲取配置的字段權(quán)限剂桥,有可能在數(shù)據(jù)庫忠烛,有可能在緩存;
  • 字段權(quán)限的配置表如何擴展权逗,比如增加修改時間;
  • 返回類型不一致美尸,如何從封裝類獲取斟薇;
  • 如何做到動態(tài)配置想處理的字段师坎;
  • 若已有策略處理(加密、脫敏堪滨、清除胯陋、混淆)不滿足需求,需要復(fù)寫加密袱箱,或新增策略如何實現(xiàn)遏乔;
  • 如何實現(xiàn)動態(tài)序列化

代碼

Talk is cheap. Show me the code.
地址:代碼后期在考慮上傳開源,已封裝為starter发笔,引入依賴盟萨,加上注解即可使用


image.png

處理器核心流程圖

處理器核心流程圖.png

UML

列數(shù)據(jù)權(quán)限UML.png

列數(shù)據(jù)權(quán)限UML.jpg

Q&A

  1. 攔截器或者鉤子函數(shù)應(yīng)該從哪兒入手;
  • 根據(jù)上篇文章內(nèi)容了讨,我們需要在序列化之前對返回內(nèi)容進行攔截處理捻激,spring boot 序列化默認采用是 jackson 實現(xiàn),如圖量蕊;

    GenericHttpMessageConverter的實現(xiàn)類.png

    我們需要重寫org.springframework.http.converter.json.MappingJackson2HttpMessageConverter & writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)方法铺罢,在序列化之前執(zhí)行對象的字段處理即可,通用返回的code置換msg也可在此處理

  • 但此種處理方式存在局限性残炮,當項目使用fastjson\gson等其他序列化組件時韭赘,又需要額外重寫序列化邏輯,違背通用性的原則势就;
    查看源碼發(fā)現(xiàn)org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor & writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)方法主要對返回結(jié)果進行處理

if (selectedMediaType != null) {
            selectedMediaType = selectedMediaType.removeQualityValue();
            for (HttpMessageConverter<?> converter : this.messageConverters) {
                GenericHttpMessageConverter genericConverter =
                        (converter instanceof GenericHttpMessageConverter
                                ? (GenericHttpMessageConverter<?>) converter
                                : null);
                if (genericConverter != null
                        ? ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType,
                                selectedMediaType)
                        : converter.canWrite(valueType, selectedMediaType)) {
                    // ResponseBodyAdvice接口是在Controller執(zhí)行return之后泉瞻,在response返回給瀏覽器或者APP客戶端之前脉漏,執(zhí)行的對response的一些處理⌒溲溃可以實現(xiàn)對response數(shù)據(jù)的一些統(tǒng)一封裝或者加密等操作
                    body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
                            (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
                            inputMessage, outputMessage);
                    if (body != null) {
                        Object theBody = body;
                        LogFormatUtils.traceDebug(logger, traceOn -> "Writing ["
                                + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
                        addContentDispositionHeader(inputMessage, outputMessage);
                        // 對象轉(zhuǎn)json
                        if (genericConverter != null) {
                            genericConverter.write(body, targetType, selectedMediaType,
                                    outputMessage);
                        } else {
                            ((HttpMessageConverter) converter).write(body, selectedMediaType,
                                    outputMessage);
                        }
                    } else {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Nothing to write: null body");
                        }
                    }
                    return;
                }
            }
        }

所以我們只要實現(xiàn)ResponseBodyAdvice接口侧巨,重寫beforeBodyWrite方法,在方法中進行對象字段處理

  1. 怎么獲取配置的字段權(quán)限鞭达,有可能在數(shù)據(jù)庫司忱,有可能在緩存;

對于權(quán)限配置表所對應(yīng)的類畴蹭,組件不應(yīng)該提供具體實現(xiàn)坦仍,參考spring security 中UserDetails 的設(shè)計思路,提供接口叨襟,讓使用者去具體實現(xiàn)繁扎,代碼如下:

/**
 * @title: FiledAuth
 * @projectName born
 * @description: 字段權(quán)限數(shù)據(jù)庫配置接口
 * @author summer
 * @date 2021/7/23 10:06
 */
public interface FiledAuth extends Serializable {

    String getUserId();

    String getUrl();

    Integer getProcessor();

    String getProcessColumn();
}
/**
 * @title: FieldPermissionExt
 * @projectName born
 * @description: 獲取字段配置的緩存接口,用戶需自己實現(xiàn)
 * @author summer
 * @date 2021/7/26 16:00
 */
public interface FieldPermissionExt {

    /**
     * @title GetColumnConfigForCache
     * @description 返回緩存中配置的字段權(quán)限信息
     * @author summer
     * @return List<Map<String, FiledAuth>>
     * @updateTime 2021/7/26 16:01
     */
    List<Map<String, FiledAuth>> getColumnConfigForCache();

這樣組件無需關(guān)注關(guān)注配置信息的字段設(shè)計和配置信息的存儲位置糊闽,通過接口進行約束梳玫,并通過多態(tài)運行時獲取bean,從而獲取字段配置信息右犹;

  1. 字段權(quán)限的配置表如何擴展提澎,比如增加修改時間;

參考問題2,F(xiàn)iledAuth 為接口傀履,使用者可以在實現(xiàn)類中加上自己需要的額外字段

  1. 返回類型不一致虱朵,如何從封裝類獲取
/**
 * @title: FieldPermissionExt
 * @projectName born
 * @description: 獲取字段配置的緩存接口莉炉,用戶需自己實現(xiàn)
 * @author summer
 * @date 2021/7/26 16:00
 */
public interface FieldPermissionExt {

    /**
     * @title GetColumnConfigForCache
     * @description 返回緩存中配置的字段權(quán)限信息
     * @author summer
     * @return List<Map<String, FiledAuth>>
     * @updateTime 2021/7/26 16:01
     */
    List<Map<String, FiledAuth>> getColumnConfigForCache();

    /**
     * @title beforeHandlerReturnObj
     * @description 用于擴展返回結(jié)果非單個對象或list包裹對象數(shù)據(jù)
     * @author summer
     * @return Object  只能返回業(yè)務(wù)數(shù)據(jù)對象或包裹數(shù)據(jù)的List集合钓账,否則拋異常
     * @updateTime 2021/7/27 14:15
     */
    Object beforeHandlerReturnObj(Object body);

    /**
     * @title afterHandlerReturnObj
     * @description 用于擴展返回結(jié)果非單個對象或list包裹對象數(shù)據(jù)
     * @author summer
     * @return Object  只能返回業(yè)務(wù)數(shù)據(jù)對象或包裹數(shù)據(jù)的List集合,否則拋異常
     * @updateTime 2021/7/27 14:15
     */
    Object afterHandlerReturnObj(Object Target, Object Original);
}

beforeHandlerReturnObj 和 afterHandlerReturnObj 分別為前置和后置處理器絮宁,此處借鑒spring中BeanPostProcessor的思想梆暮,在處理具體對象時,如果對象進行深度封裝绍昂,組件無法遍歷獲取想要處理的對象啦粹,則用戶擴展實現(xiàn)該接口,將預(yù)處理對象返回窘游,在核心處理器處理完畢后再裝載回去唠椭;

  1. 如何做到動態(tài)配置想處理的字段;

由FiledAuth接口中g(shù)etProcessor(處理器)和getProcessColumn(處理字段)可知忍饰,web后臺可以動態(tài)對該字段進行賦值贪嫂,而核心處理器可以根據(jù)策略模式進行調(diào)用對應(yīng)處理方法進行處理,代碼如下:

/**
 * @title: ColumnContext
 * @projectName born
 * @description: 字段處理環(huán)境類
 * @author summer
 * @date 2021/7/23 9:38
 */
public class ColumnContext {

    @Autowired
    private Processors processors;

    /**
     * @title executeStrategy
     * @description 字段權(quán)限處理的執(zhí)行類
     * @param: processor
     * @param: targetValue
     * @author summer
     * @return java.lang.String
     * @updateTime 2021/7/23 9:40
     */
    public String executeStrategy(Integer processor, String processColumn) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ColumnStrategy columnStrategy = processors.getStrategyObject(processor);
        return columnStrategy.handler(processColumn);
    }

}
/**
 * @title: ColumnStrategy
 * @projectName born
 * @description: 字段處理的策略類
 * @author summer
 * @date 2021/7/23 9:36
 */
public interface ColumnStrategy {

    String handler(String processColumn);
}
  1. 若已有策略處理(加密艾蓝、脫敏力崇、清除斗塘、混淆)不滿足需求,需要復(fù)寫加密亮靴,或新增策略如何實現(xiàn)馍盟;

組件首先提供一個處理器接口,包含獲取策略實現(xiàn)類茧吊,注冊新的策略贞岭,代碼如下

/**
 * @title: Processors
 * @projectName born
 * @description: 獲取處理類型的頂層接口
 * @author summer
 * @date 2021/8/3 10:19
 */
public interface Processors {

    /**
     * @title getStrategyObject
     * @description 根據(jù)處理器類型返回對應(yīng)的策略實現(xiàn)類
     * @author summer
     * @return ColumnStrategy 策略的具體實現(xiàn)類
     * @updateTime 2021/8/3 10:45
     */
    ColumnStrategy getStrategyObject(Integer processor);

    /**
     * @title registerStrategy
     * @description 注冊新的策略,若processor相同搓侄,則覆蓋之前
     * @author summer
     * @return void
     * @updateTime 2021/8/3 15:17
     */
    void registerStrategy(Integer processor, ColumnStrategy columnStrategy);
}

其次組件提供了抽象類實現(xiàn)該接口曹步,用于實現(xiàn)Processors接口


/**
 * @title: ColumnProcess
 * @projectName born
 * @description: 獲取處理類型的抽象類
 * @author summer
 * @date 2021/8/3 9:57
 */
public abstract class ColumnProcessAbstract implements Processors{

    // 存放策略實現(xiàn)類的map
    private static Map<Integer, ColumnStrategy> STRATEGY_MAP = new HashMap<>();

    static{
        // 1:加密
        STRATEGY_MAP.put(1, new EncryptionStrategy());
        // 2:脫敏
        STRATEGY_MAP.put(2, new DesensitizationStrategy());
        // 3:清除
        STRATEGY_MAP.put(3, new ClearStrategy());
        // 4.混淆
        STRATEGY_MAP.put(4, new ObfuscationStrategy());
    }

    @Override
    public ColumnStrategy getStrategyObject(Integer processor) {
        return STRATEGY_MAP.get(processor);
    }

    @Override
    public void registerStrategy(Integer processor, ColumnStrategy columnStrategy) {
        STRATEGY_MAP.put(processor, columnStrategy);
    }

}

接著一個默認實現(xiàn)集成該抽象類

/**
 * @title: DefaultColumnProcess
 * @projectName born
 * @description: 獲取處理類型的默認裝載類
 * @author summer
 * @date 2021/8/3 10:08
 */
@ConditionalOnMissingBean(Processors.class)
public class DefaultColumnProcess extends ColumnProcessAbstract {

    @Override
    public ColumnStrategy getStrategyObject(Integer processor) {
        return super.getStrategyObject(processor);
    }

}

最后,若用戶需要復(fù)寫或新增休讳,則直接繼承抽象類即可讲婚,代碼如下:

/**
 * @title: MyColumnProcess
 * @projectName born
 * @description: 測試-模擬修改默認的加密方法,改為自定義實現(xiàn)
 * @author summer
 * @date 2021/8/3 10:26
 */
@Component
public class MyColumnProcess extends ColumnProcessAbstract {

    public ColumnStrategy getStrategyObject(Integer processor) {
        super.registerStrategy(1,new MyAes());
        return super.getStrategyObject(processor);
    }
}
  1. ResponseBodyAdvice接口有多個實現(xiàn)俊柔,spring 是怎么實現(xiàn)掃描加載替換掉默認實現(xiàn)和排序的筹麸,根據(jù)源碼排查對應(yīng)邏輯如下:
    org.springframework.web.method.ControllerAdviceBean&findAnnotatedBeans(ApplicationContext context)
/**
     * Find beans annotated with {@link ControllerAdvice @ControllerAdvice} in the
     * given {@link ApplicationContext} and wrap them as {@code ControllerAdviceBean}
     * instances.
     * <p>As of Spring Framework 5.2, the {@code ControllerAdviceBean} instances
     * in the returned list are sorted using {@link OrderComparator#sort(List)}.
     * @see #getOrder()
     * @see OrderComparator
     * @see Ordered
     */
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
        List<ControllerAdviceBean> adviceBeans = new ArrayList<>();
        for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)) {
            if (!ScopedProxyUtils.isScopedTarget(name)) {
                ControllerAdvice controllerAdvice = context.findAnnotationOnBean(name, ControllerAdvice.class);
                if (controllerAdvice != null) {
                    // Use the @ControllerAdvice annotation found by findAnnotationOnBean()
                    // in order to avoid a subsequent lookup of the same annotation.
                    adviceBeans.add(new ControllerAdviceBean(name, context, controllerAdvice));
                }
            }
        }
        OrderComparator.sort(adviceBeans);
        return adviceBeans;
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者雏婶。
  • 序言:七十年代末物赶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子留晚,更是在濱河造成了極大的恐慌酵紫,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件错维,死亡現(xiàn)場離奇詭異奖地,居然都是意外死亡,警方通過查閱死者的電腦和手機赋焕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門参歹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人隆判,你說我怎么就攤上這事犬庇。” “怎么了侨嘀?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵臭挽,是天一觀的道長。 經(jīng)常有香客問我咬腕,道長欢峰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮赤赊,結(jié)果婚禮上闯狱,老公的妹妹穿的比我還像新娘。我一直安慰自己抛计,他們只是感情好哄孤,可當我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吹截,像睡著了一般瘦陈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上波俄,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天晨逝,我揣著相機與錄音,去河邊找鬼懦铺。 笑死捉貌,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的冬念。 我是一名探鬼主播趁窃,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼急前!你這毒婦竟也來了醒陆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤裆针,失蹤者是張志新(化名)和其女友劉穎刨摩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體世吨,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡澡刹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了另假。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片像屋。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖边篮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奏甫,我是刑警寧澤戈轿,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站阵子,受9級特大地震影響思杯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一色乾、第九天 我趴在偏房一處隱蔽的房頂上張望誊册。 院中可真熱鬧,春花似錦暖璧、人聲如沸案怯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘲碱。三九已至,卻和暖如春局蚀,著一層夾襖步出監(jiān)牢的瞬間麦锯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工琅绅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留扶欣,地道東北人。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓千扶,卻偏偏與公主長得像宵蛀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子县貌,可洞房花燭夜當晚...
    茶點故事閱讀 45,585評論 2 359