最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

如何使用ThreadLocal上下文解決查詢性能問題

 更新時間:2023年07月21日 10:13:07   作者:我一直在流浪  
這篇文章主要介紹了利用ThreadLocal上下文解決查詢性能問題,有兩種解決方案,一種是使用ThreadLocal上下文,另一種是使用Redis緩存,需要的朋友可以參考下

問題背景:

安全事件列表中數(shù)據(jù)右鍵操作需要控制工單處置記錄和SOAR處置記錄是否置灰,置灰的邏輯是判斷當(dāng)前用戶是否有相應(yīng)的菜單權(quán)限以及當(dāng)前租戶是否有相應(yīng)的授權(quán)權(quán)限。這個判斷邏輯在每次刷新安全事件列表時都需要調(diào)用grpc接口判斷租戶是否授權(quán),為了提高性能,所以禁止每次都去請求調(diào)用grpc接口,有兩種解決方案,一種是使用ThreadLocal上下文,另一種是使用Redis緩存。簡單介紹下如何使用ThreadLocal上下文解決性能問題。

01. 定義實(shí)體 Subscription

@Generated
public final class TenantManagement {
    @Generated
    public static final class Subscription {
        private volatile Object id;
        private volatile Object name;
        private volatile Object productId;
        private long startTime;
        private long expiryTime;
        private long createTime;
        private long renewTime;
        private volatile Object info;
        private volatile Object status;
        private volatile Object subscribeType;
    }
}

02. 定義 SubscribeInfoListContext 上下文

利用ThreadLocal上下文存儲需要緩存的數(shù)據(jù) List<TenantManagement.Subscription>

public class SubscribeInfoListContext {
    private static final ThreadLocal<List<TenantManagement.Subscription>> SUBSCRIBE_INFO_LIST_THREAD_LOCAL = new ThreadLocal<>();
    public static void set(List<TenantManagement.Subscription> subscribeInfoList) {
        SUBSCRIBE_INFO_LIST_THREAD_LOCAL.set(subscribeInfoList);
    }
    public static List<TenantManagement.Subscription> get() {
        return SUBSCRIBE_INFO_LIST_THREAD_LOCAL.get();
    }
    public static void remove() {
        SUBSCRIBE_INFO_LIST_THREAD_LOCAL.remove();
    }
    private SubscribeInfoListContext() {
    }
}

03. 定義SubscribeInfoService 接口

進(jìn)行g(shù)rpc接口調(diào)用獲取 List<TenantManagement.Subscription> 信息

public interface SubscribeInfoService {
    /**
     * 獲取授權(quán)信息
     *
     * @return 授權(quán)信息
     */
    List<TenantManagement.Subscription> getSubscribeInfoList();
}

1. 定義 SubscribeInfoServiceImpl 實(shí)現(xiàn)類

@CustomLog
@Service
public class SubscribeInfoServiceImpl implements SubscribeInfoService {
    @Setter(onMethod_ = @Autowired)
    private TenantManageClient tenantManageClient;
    @NotNull
    private static final List<String> SUBSCRIPTION_NAME_LIST = List.of(
            SubscriptionConstant.ORDER_SUBSCRIPTION,
            SubscriptionConstant.SOAR_SUBSCRIPTION
    );
    @Override
    public List<TenantManagement.Subscription> getSubscribeInfoList() {
        // 先從上下文SubscribeInfoListContext中獲取List<TenantManagement.Subscription>
        List<TenantManagement.Subscription> subscriptions = SubscribeInfoListContext.get();
        // 如果上下文SubscribeInfoListContext中獲取不到,再去調(diào)用grpc接口獲取
        if (!CollectionUtil.isEmpty(subscriptions)) {
            return subscriptions;
        }
        String tenantId = Objects.requireNonNull(TenantInfoContext.getTenantInfo()).getTenantId();
        List<TenantManagement.Subscription> subscribeInfos = tenantManageClient.getSubscribeInfoByIds(tenantId, SUBSCRIPTION_NAME_LIST);
        // 獲取到List<TenantManagement.Subscription> 后存入上下文SubscribeInfoListContext中
        SubscribeInfoListContext.set(subscribeInfos);
        return subscribeInfos;
    }
}
public interface SubscriptionInfoConstant {
    // 工單
    String ORDER_SUBSCRIPTION = "XDR_ORDER_MANAGEMENT";
    // SOAR
    String SOAR_SUBSCRIPTION = "XDR_SOAR_SECURITY_MANAGEMENT";
}

2. 判斷是否授權(quán)SOAR和是否有SOAR菜單權(quán)限 GenerateSoarDisableFlagConverter

@NoArgsConstructor
@AllArgsConstructor
public class GenerateSoarDisableFlagConverter implements ResultConverter {
    @Getter
    @Setter
    private Supplier<LicenseInfoService> licenseInfoServiceSupplier;
    @Getter
    @Setter
    private Supplier<SessionNoRelatedService> sessionNoRelatedService;
    @Getter
    @Setter
    private Supplier<SubscribeInfoService> subscribeInfoServiceSupplier;
    private static final String SUPER_ADMIN_POLICY = "superAdmin";
    private static final String SOAR_QUERY_POLICY = "soarQuery";
    private static final String SOAR_SUBSCRIPTION = "XDR_SOAR_SECURITY_MANAGEMENT";
    @Nullable
    @Override
    public Map<?, ?> convert(@NotNull SplTranslateContext<?> splTranslateContext, @Nullable Map<?, ?> map, boolean ifUsingSplReturnEntity) {
        LinkedHashMap<Object, Object> result;
        if (map == null) {
            result = new LinkedHashMap<>();
        } else {
            result = new LinkedHashMap<>(map);
        }
        Object value = !isSoarAvailable();
        result.put(
                "soarDisableFlag",
                ifUsingSplReturnEntity ? SplArrayReturnEntity.of(value)
                        : value
        );
        return result;
    }
    private Boolean isSoarAvailable() {
        return hasSoarPolicy() && hasSubscription(SOAR_SUBSCRIPTION);
    }
    public Boolean hasSubscription(String subscriptionName) {
        LicenseInfoService licenseInfoService = licenseInfoServiceSupplier.get();
        if (licenseInfoService.ifLicenseInfoIsSaas()) {
            List<TenantManagement.Subscription> subscribeInfos = subscribeInfoServiceSupplier.get().getSubscribeInfoList();
            if (CollectionUtils.isEmpty(subscribeInfos)) {
                return false;
            }
            List<TenantManagement.Subscription> subscriptionList = subscribeInfos.stream()
                    .filter(subscriptionInfo -> subscriptionInfo.getProductID().equals(subscriptionName) && checkSubscribeStatus(subscriptionInfo.getStatus()))
                    .collect(Collectors.toList());
            if (CollectionUtils.isEmpty(subscriptionList)) {
                return false;
            }
            return true;
        }
        Collection<String> moduleCodes = licenseInfoServiceSupplier.get().getActiveModuleCodes();
        return moduleCodes.contains(subscriptionName);
    }
    private boolean checkSubscribeStatus(String status) {
        return SubscriptionStatusEnum.getActivateSubscriptionStatus().contains(status);
    }
    public Boolean hasSoarPolicy() {
        SessionContextResponseVo sessionContextNoRelated = sessionNoRelatedService.get().getSessionContextNoRelated();
        if (sessionContextNoRelated == null) {
            return false;
        }
        Set<String> policies = sessionContextNoRelated.getPolicies();
        if (policies.isEmpty()) {
            return false;
        }
        return policies.contains(SOAR_QUERY_POLICY) || policies.contains(SUPER_ADMIN_POLICY);
    }
}

3. 判斷是否授權(quán)工單和是否有工單菜單權(quán)限 GenerateSoarDisableFlagConverter

@NoArgsConstructor
@AllArgsConstructor
public class GenerateOrderDisableFlagConverter implements ResultConverter {
    @Getter
    @Setter
    private Supplier<LicenseInfoService> licenseInfoServiceSupplier;
    @Getter
    @Setter
    private Supplier<SessionNoRelatedService> sessionNoRelatedServiceSupplier;
    @Getter
    @Setter
    private Supplier<SubscribeInfoService> subscribeInfoServiceSupplier;
    private static final String SUPER_ADMIN_POLICY = "superAdmin";
    private static final String ORDER_QUERY_POLICY = "safeOperationQuery";
    private static final String ORDER_SUBSCRIPTION = "XDR_ORDER_MANAGEMENT";
    @Nullable
    @Override
    public Map<?, ?> convert(@NotNull SplTranslateContext<?> splTranslateContext, @Nullable Map<?, ?> map, boolean ifUsingSplReturnEntity) {
        LinkedHashMap<Object, Object> result;
        if (map == null) {
            result = new LinkedHashMap<>();
        } else {
            result = new LinkedHashMap<>(map);
        }
        Object value = !isOrderAvailable();
        result.put(
                "orderDisableFlag",
                ifUsingSplReturnEntity ? SplArrayReturnEntity.of(value)
                        : value
        );
        return result;
    }
    private Boolean isOrderAvailable() {
        return hasOrderPolicy() && hasSubscription(ORDER_SUBSCRIPTION);
    }
    public Boolean hasSubscription(String subscriptionName) {
        LicenseInfoService licenseInfoService = licenseInfoServiceSupplier.get();
        if (licenseInfoService.ifLicenseInfoIsSaas()) {
            List<TenantManagement.Subscription> subscribeInfos = subscribeInfoServiceSupplier.get().getSubscribeInfoList();
            if (CollectionUtils.isEmpty(subscribeInfos)) {
                return false;
            }
            List<TenantManagement.Subscription> subscriptionList = subscribeInfos.stream()
                    .filter(subscriptionInfo -> subscriptionInfo.getProductID().equals(subscriptionName) && checkSubscribeStatus(subscriptionInfo.getStatus()))
                    .collect(Collectors.toList());
            if (CollectionUtils.isEmpty(subscriptionList)) {
                return false;
            }
            return true;
        }
        Collection<String> moduleCodes = licenseInfoService.getActiveModuleCodes();
        return moduleCodes.contains(subscriptionName);
    }
    private boolean checkSubscribeStatus(String status) {
        return SubscriptionStatusEnum.getActivateSubscriptionStatus().contains(status);
    }
    public Boolean hasOrderPolicy() {
        SessionContextResponseVo sessionContextNoRelated = sessionNoRelatedServiceSupplier.get().getSessionContextNoRelated();
        if (sessionContextNoRelated == null) {
            return false;
        }
        Set<String> policies = sessionContextNoRelated.getPolicies();
        if (policies.isEmpty()) {
            return false;
        }
        return policies.contains(ORDER_QUERY_POLICY) || policies.contains(SUPER_ADMIN_POLICY);
    }
}

4. 查詢安全事件列表接口中添加 ResultConverter

@CustomLog
@Service
public class IncidentSplTableHandlingServiceImpl extends SplTableHandlingServiceImpl implements InitializingBean {
    @Getter
    @Setter(onMethod_ = @Autowired)
    private LicenseInfoService licenseInfoService;
    @Getter
    @Setter(onMethod_ = @Autowired)
    private SessionNoRelatedService sessionNoRelatedService;
    @Getter
    @Setter(onMethod_ = @Autowired)
    private SubscribeInfoService subscribeInfoService;
    @Override
    public void afterPropertiesSet() throws Exception {
        List<ResultConverter> originalResultConverters = this.getResultConverters();
        List<ResultConverter> resultConverters = new ArrayList<>(originalResultConverters);
        resultConverters.add(
                new GenerateOrderDisableFlagConverter(
                        this::getLicenseInfoService,
                        this::getSessionNoRelatedService,
                        this::getSubscribeInfoService
                )
        );
        resultConverters.add(
                new GenerateSoarDisableFlagConverter(
                        this::getLicenseInfoService,
                        this::getSessionNoRelatedService,
                        this::getSubscribeInfoService
                )
        );
        this.setResultConverters(resultConverters);
    }
    // 省略.....
}

04. 添加攔截器 IncidentInterceptorConfig

在請求開始和請求結(jié)束時清除上下文SubscribeInfoListContext中的數(shù)據(jù),消除不同租戶間數(shù)據(jù)的影響。

@Data
@Configuration
@CustomLog
public class IncidentInterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors( @NotNull InterceptorRegistry registry) {
        registry.addInterceptor(
                new HandlerInterceptorAdapter() {
                    // 在請求開始清除上下文SubscribeInfoListContext中的數(shù)據(jù)
                    @Override
                    public boolean preHandle(
                            @NotNull HttpServletRequest request,
                            @NotNull HttpServletResponse response,
                            @NotNull Object handler
                    ) throws Exception {
                        SubscribeInfoListContext.remove();
                        return true;
                    }
					// 在請求結(jié)束時清除上下文SubscribeInfoListContext中的數(shù)據(jù)
                    @Override
                    public void afterCompletion(
                            @NotNull HttpServletRequest request,
                            @NotNull HttpServletResponse response,
                            @NotNull Object handler,
                            @Nullable Exception ex
                    ) throws Exception {
                        SubscribeInfoListContext.remove();
                    }
                }
        );
    }
}

05. 添加 SaasThreadContextDataHolderSubscription 存儲緩存信息

public interface SaasThreadContextDataHolder {
}
@Data
@AllArgsConstructor
public class SaasThreadContextDataHolderSubscription implements SaasThreadContextDataHolder {
    @Nullable
    private final List<TenantManagement.Subscription> subscriptionList;
}

06. 添加 SaasThreadContextHolderSubscriptionInfo 以便后續(xù)擴(kuò)展和使用

public interface SaasThreadContextHolder<T extends SaasThreadContextDataHolder> {
    /**
     * 獲取data holder類
     *
     * @return data holder類
     */
    @NotNull
    Class<T> getSaasThreadContextDataHolderClass();
    /**
     * 嘗試加載SaasThreadContextDataHolder
     *
     * @param holder SaasThreadContextDataHolder
     * @return 可以加載則true, 否則false
     */
    default boolean tryLoad(@NotNull SaasThreadContextDataHolder holder) {
        if (!getSaasThreadContextDataHolderClass().isInstance(holder)) {
            return false;
        }
        // noinspection unchecked
        this.load((T) holder);
        return true;
    }
    /**
     * 加載SaasThreadContextDataHolder
     *
     * @param holder SaasThreadContextDataHolder
     */
    void load(@NotNull T holder);
    /**
     * 存檔SaasThreadContextDataHolder
     *
     * @return SaasThreadContextDataHolder
     */
    @NotNull
    T save();
    /**
     * 清理SaasThreadContextDataHolder
     */
    void remove();
}
@AutoService(SaasThreadContextHolder.class)
public class SaasThreadContextHolderSubscriptionInfo implements SaasThreadContextHolder<SaasThreadContextDataHolderSubscriptionInfo> {
    @Override
    public @NotNull Class<SaasThreadContextDataHolderSubscriptionInfo> getSaasThreadContextDataHolderClass() {
        return SaasThreadContextDataHolderSubscriptionInfo.class;
    }
    @Override
    public void load(@NotNull SaasThreadContextDataHolderSubscriptionInfo holder) {
        List<TenantManagement.Subscription> subscriptionInfoList = holder.getSubscriptionInfoList();
        if(!CollectionUtils.isEmpty(subscriptionInfoList)){
            SubscriptionInfoContext.set(subscriptionInfoList);
        }
    }
    @Override
    public @NotNull SaasThreadContextDataHolderSubscriptionInfo save() {
        return new SaasThreadContextDataHolderSubscriptionInfo(
                SubscriptionInfoContext.get()
        );
    }
    @Override
    public void remove() {
        SubscriptionInfoContext.remove();
    }
}

07. 添加 SaasThreadContextUtil 工具類

public class SaasThreadContextUtil {
    @NotNull
    static List<SaasThreadContextHolder<?>> getSaasThreadContextHolders() {
        // IterableUtils.toList 方法將 ServiceLoader.load 返回的 Iterable 轉(zhuǎn)換成了 List
        return (List) IterableUtils.toList(
            	// 加載所有實(shí)現(xiàn)了 SaasThreadContextHolder 接口的類,并將它們轉(zhuǎn)換成 List 返回
            	ServiceLoader.load(SaasThreadContextHolder.class)
        );
    }
    @NotNull
    public static List<SaasThreadContextDataHolder> save() {
        List<SaasThreadContextHolder<?>> saasThreadContextHolders = getSaasThreadContextHolders();
        List<SaasThreadContextDataHolder> saasThreadContextDataHolders = new ArrayList<>(
                saasThreadContextHolders.size()
        );
        for (SaasThreadContextHolder<?> saasThreadContextHolder : saasThreadContextHolders) {
            saasThreadContextDataHolders.add(saasThreadContextHolder.save());
        }
        return saasThreadContextDataHolders;
    }
    public static void load(
            @NotNull List<SaasThreadContextDataHolder> saasThreadContextDataHolders
    ) {
        for (SaasThreadContextHolder<?> saasThreadContextHolder : getSaasThreadContextHolders()) {
            for (SaasThreadContextDataHolder saasThreadContextDataHolder : saasThreadContextDataHolders) {
                if (saasThreadContextHolder.tryLoad(saasThreadContextDataHolder)) {
                    break;
                }
            }
        }
    }
    public static void remove() {
        for (SaasThreadContextHolder<?> saasThreadContextHolder : getSaasThreadContextHolders()) {
            saasThreadContextHolder.remove();
        }
    }
    private SaasThreadContextUtil() {
    }
}

08. 使用 SaasThreadContextUtil

@Override
public FusionAlertVo countFusionAlerts(FusionAlertQo fusionAlertQo) {
    // 調(diào)用 SaasThreadContextUtil.save()
    List<SaasThreadContextDataHolder> threadContextDataHolders = SaasThreadContextUtil.save();
    CompletableFuture<Long> fusionAlertCountFuture = CompletableFuture.supplyAsync(() -> {
        try {
            // 調(diào)用 SaasThreadContextUtil.load(threadContextDataHolders);
            SaasThreadContextUtil.load(threadContextDataHolders);
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices(SaasEsFactory.getTenantIndex(DatabaseConstants.ALERT));
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchRequest.source(searchSourceBuilder);
            searchSourceBuilder.trackTotalHits(true);
            searchSourceBuilder.query(createBoolQueryBuilder(timeRange));
            return getSearchResponseTotalHits(fusionAlertQo, searchRequest);
        } finally {
            // 調(diào)用 SaasThreadContextUtil.remove();
            SaasThreadContextUtil.remove();
        }
    }, THREAD_POOL_EXECUTOR);
   CompletableFuture<Long> multiSourceAssociateAlertCountFuture = CompletableFuture.supplyAsync(() -> {
        try {
            // 調(diào)用 SaasThreadContextUtil.load(threadContextDataHolders);
            SaasThreadContextUtil.load(threadContextDataHolders);
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices(SaasEsFactory.getTenantIndex(DatabaseConstants.ALERT));
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchRequest.source(searchSourceBuilder);
            searchSourceBuilder.trackTotalHits(true);
            BoolQueryBuilder boolQueryBuilder = createBoolQueryBuilder(timeRange);
            searchSourceBuilder.query(boolQueryBuilder);
            TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("fusionAlert", true);
            boolQueryBuilder.must(termQueryBuilder);
            return getSearchResponseTotalHits(fusionAlertQo, searchRequest);
        } finally {
            // 調(diào)用 SaasThreadContextUtil.remove();
            SaasThreadContextUtil.remove();
        }
    }, THREAD_POOL_EXECUT
    // ...                                                                                            
}

到此這篇關(guān)于利用ThreadLocal上下文解決查詢性能問題的文章就介紹到這了,更多相關(guān)ThreadLocal查詢性能內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • mybatis-plus如何根據(jù)任意字段saveOrUpdateBatch

    mybatis-plus如何根據(jù)任意字段saveOrUpdateBatch

    MyBatisPlus saveOrUpdateBatch默認(rèn)按主鍵判斷操作類型,若需按其他唯一字段(如agentId、period、type組合)判斷,需在service層重寫方法,通過predicate定義插入條件并結(jié)合consumer執(zhí)行更新邏輯,適用于批量和單條數(shù)據(jù)操作場景
    2025-09-09
  • Spring Boot Logging Level設(shè)置為off時的Bug

    Spring Boot Logging Level設(shè)置為off時的Bug

    這篇文章主要介紹了Spring Boot Logging Level設(shè)置為off時的Bug,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • java基礎(chǔ)之字符串編碼知識點(diǎn)總結(jié)

    java基礎(chǔ)之字符串編碼知識點(diǎn)總結(jié)

    這篇文章主要介紹了java基礎(chǔ)之字符串編碼總結(jié),文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有很好的幫助,要的朋友可以參考下
    2021-04-04
  • 深入詳解Java泛型擦除原理與限制

    深入詳解Java泛型擦除原理與限制

    這篇文章主要介紹了深入詳解Java泛型擦除原理與限制,Java泛型的設(shè)計有個獨(dú)特之處,類型信息只存在于編譯期,運(yùn)行時會被徹底擦除,這種 擦除機(jī)制讓很多開發(fā)者困惑,下面我們來探索它,需要的朋友可以參考下
    2026-01-01
  • Java中Optional的使用指南

    Java中Optional的使用指南

    這篇文章主要給大家介紹了關(guān)于Java中Optional使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • springboot中mybatis多數(shù)據(jù)源動態(tài)切換實(shí)現(xiàn)

    springboot中mybatis多數(shù)據(jù)源動態(tài)切換實(shí)現(xiàn)

    在開發(fā)中,動態(tài)數(shù)據(jù)源配置還是用的比較多的,比如在多數(shù)據(jù)源使用方面,又或者是在多個DB之間切換方面。這里給出一個動態(tài)數(shù)據(jù)源的配置方案,感興趣的可以了解一下
    2021-07-07
  • 使用Feign傳遞請求頭信息(Finchley版本)

    使用Feign傳遞請求頭信息(Finchley版本)

    這篇文章主要介紹了使用Feign傳遞請求頭信息(Finchley版本),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • SpringBoot中關(guān)于static和templates的注意事項以及webjars的配置

    SpringBoot中關(guān)于static和templates的注意事項以及webjars的配置

    今天小編就為大家分享一篇關(guān)于SpringBoot中關(guān)于static和templates的注意事項以及webjars的配置,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • SpringBoot使用Prometheus采集自定義指標(biāo)數(shù)據(jù)的方法詳解

    SpringBoot使用Prometheus采集自定義指標(biāo)數(shù)據(jù)的方法詳解

    隨著微服務(wù)在生產(chǎn)環(huán)境大規(guī)模部署和應(yīng)用,隨之而來也帶來了新的問題,其中比較關(guān)鍵的就是關(guān)于微服務(wù)的運(yùn)維和監(jiān)控,本文將結(jié)合微服務(wù)運(yùn)維監(jiān)控中的指標(biāo)監(jiān)控進(jìn)行詳細(xì)的說明,需要的朋友可以參考下
    2024-07-07
  • JavaWeb簡單用戶登錄注冊實(shí)例代碼(有驗證碼)

    JavaWeb簡單用戶登錄注冊實(shí)例代碼(有驗證碼)

    這篇文章主要介紹了JavaWeb簡單用戶登錄注冊實(shí)例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02

最新評論

甘孜县| 合阳县| 莱州市| 阿克苏市| 绍兴市| 玉门市| 南丹县| 洛宁县| 岫岩| 阳信县| 贡嘎县| 余姚市| 岑巩县| 永清县| 呼伦贝尔市| 神农架林区| 太湖县| 三亚市| 阿合奇县| 通山县| 自治县| 特克斯县| 伊春市| 双桥区| 永州市| 长顺县| 台南市| 古交市| 铁力市| 行唐县| 邮箱| 绥德县| 双江| 华亭县| 河津市| 玉田县| 定日县| 石首市| 陈巴尔虎旗| 无极县| 安龙县|