轻量级的灰度配置平台|得物技术

news2024/9/24 15:26:48

一、前言

随着近几年得物的业务和技术的快速发展,我们不管是在面向C端场景还是B端供应链;业务版本的迭代更新,技术架构的不断升级;不管是业务稳定性还是架构稳定性,业务灰度的能力对我们来说都是一项重要的技术保障,越来越受到我们业务研发的关注。然而,传统的灰度发布服务往往过于定制化,缺乏灵活性和通用性,无法满足不断变化的业务需求,往往灰度的场景可能通过代码硬编码或者简单的配置中心配置。在这样的背景下,本文将介绍一种全新的、轻量级的灰度平台,它将为大家的业务带来全新的灰度体验。

二、总体架构设计

01.jpg

整体架构模块的概览

主要由如下模块组成

  • 灰度运营平台:为用户提供增删查改的灰度发布管理和UI界面;

  • 灰度服务端:为灰度运营平台提供标准的增删查改功能、权限控制、灰度场景管理和应用接入命名空间;

  • Nacos&Ark: 提供高性能的灰度配置读取和存储服务;

  • 灰度SDK: 为研发提供轻量级高性能的灰度判断API和配置服务。

功能特性说明

  • 系统灰度开关可以在后台运营页面上进行可视化管理

  • 配置类型可以支持多种开关、灰度、业务配置;

  • 配置的值可以采用富文本形式来进行编辑

  • 能够支持自定义维度白名单的方式进行灰度;

  • 能够支持自定义多个分组的方式进行灰度;

  • 能够支持自定义分组实验的方式进行灰度;

  • 能够支持按照指定维度以一定比例进行灰度;

  • 能够支持自定义维度进行灰度配置;

  • 能够支持历史配置一键回滚和追溯配置;

  • 能够方便灰度配置信息进行生命周期管理

适用的场景:

02.jpg

三、灰度服务关键设计

我们参考上面的总体架构设计可知,灰度配置的存储不是关键,复用已有的Nacos等或者自研的配置服务中心即可,重点是在SDK和灰度数据结构的设计上。

灰度数据类型设计

首先我们针对不同的场景可能会遇到不同的数据类型灰度,不同的数据类型在规则处理中可能也有特点场景的实现,所以先定义灰度数据类型参考如下:

/**
 * 版本号
 */
VERSION("version"),
/**
 * 字符串类型
 */
STRING("string"),
/**
 * 集合类型
 */
SEGMENT("segment"),

/**
 * 数字类型
 */
NUMBER("number"),

/**
 * 非法规则
 */
NONE("none"),
;

03.jpg

灰度规则设计

有了灰度数据类型的定义,我们需要针对不同的字段数据类型使用不同的灰度规则,如包含关系、完全匹配、正则表达式等。于是就可以自定义各种规则,根据实际的业务场景抽象,规则完全可以再自定义。

IS_IN("in"),
IS_NOT_IN("notIn"),
REGEX("regex"),
NREGEX("nregex"),
EQ("eq"),
NEQ("neq"),
EQUAL_TO("="),
NOT_EQUAL_TO("!="),
GREATER_THAN(">"),
GREATER_OR_EQUAL(">="),
LESS_THAN("<"),
LESS_OR_EQUAL("<="),
NONE("none"),

04.jpg

灰度类型实现的规则对应级联关系

05.jpg

06.jpg

07.jpg

08.jpg

简单灰度触发器模型

根据上面定义的数据结构和灰度规则,很容易抽象出我们需要定义的灰度模型,简单数据结构参考如下:

@Data
@NoArgsConstructor
public class Toggle implements Serializable {
    /**
     * 默认不配置该字段,如果启用该字段fullGray=1,则全量开关不进行后续的版本判断
     */
    private Integer fullGray = 0;

    /**
     * 是否生效;1:生效;0:不生效
     */
    private Integer enabled = 1;
    /**
     * 规则列表,每一项都是或的关系
     */
    private List<Rule> rules;

    /**
     * 规则列表
     * 每一项是或的关系
     */
    @Data
    public static class Rule implements Serializable {

        /**
         * 规则字段条件,每一项是且的关系
         */
        private List<Condition> conditions;
    }


    /**
     * 条件列表
     */
    @Data
    public static class Condition implements Serializable {

        /**
         * 规则解析类型
         *
         * @see ConditionType
         */
        private String type;

        /**
         * 规则解析字段
         *
         * @see GrayFields
         */
        private String subject;

        /**
         * 条件判断
         *
         * @see PredicateType
         */
        private String predicate;

        /**
         * 条件判断内容
         */
        private List<Object> objects;

    }
}

灰度触发器模型和规则实现

灵活的灰度规则实现,我们针对以上定义的好的数据模型和灰度规则需要进行映射实现。整体的架构实现需要考虑,每新增一种规则实现代码量都非常少,极具扩展性,后续如有不满足的规则场景也容易扩展。

定义灰度规则匹配器

public interface Matcher {

    ConditionType getConditionType();

    Map<PredicateType, PredicateMatcher> getPredicateMatcher();
}

数字类型灰度规则匹配

/**
 * 数字类型匹配
 */
@Component
public class NumberMatch implements Matcher {

    @Override
    public ConditionType getConditionType() {
        return ConditionType.NUMBER;
    }

    @Override
    public Map<PredicateType, PredicateMatcher> getPredicateMatcher() {
        EnumMap<PredicateType, PredicateMatcher> matcherEnumMap = new EnumMap<>(PredicateType.class);

        matcherEnumMap.put(PredicateType.EQUAL_TO, ((target, objects) -> objects.stream()
                .filter(Objects::nonNull)
                .map(Objects::toString)
                .filter(StringUtils::isNotBlank)
                .map(Long::valueOf)
                .anyMatch(t -> Objects.equals(Long.valueOf(String.valueOf(target)), t))));

       // ...... 此处后续的代码省略,设计和上面的实现雷同....
        return matcherEnumMap;
    }
}

字符串类型灰度规则匹配

/**
 * 字符串匹配
 */
@Component
public class StringMatch implements Matcher {

    @Override
    public ConditionType getConditionType() {
        return ConditionType.STRING;
    }

    @Override
    public Map<PredicateType, PredicateMatcher> getPredicateMatcher() {
        EnumMap<PredicateType, PredicateMatcher> matcherEnumMap = new EnumMap<>(PredicateType.class);

        matcherEnumMap.put(PredicateType.EQ, ((target, objects) -> objects.stream()
                .filter(Objects::nonNull)
                .map(Objects::toString)
                .filter(StringUtils::isNotBlank)
                .anyMatch(t -> String.valueOf(target).equalsIgnoreCase(t))));

        // ...... 此处后续的代码省略,设计和上面的实现雷同....
        return matcherEnumMap;
    }
}

客户端版本号灰度规则类型匹配

/**
 * @author feel
 */
@Component
public class VersionMatch implements Matcher {

    @Override
    public ConditionType getConditionType() {
        return ConditionType.VERSION;
    }

    @Override
    public Map<PredicateType, PredicateMatcher> getPredicateMatcher() {
        EnumMap<PredicateType, PredicateMatcher> matcherEnumMap = new EnumMap<>(PredicateType.class);
        matcherEnumMap.put(PredicateType.EQUAL_TO, ((target, objects) -> objects.stream()
                .filter(Objects::nonNull)
                .map(Objects::toString)
                .filter(StringUtils::isNotBlank)
                .anyMatch(t -> VersionUtils.compareMajorVersion(String.valueOf(target), t) == 0)));

        // ...... 此处后续的代码省略,设计和上面的实现雷同....
        return matcherEnumMap;
    }

}

复杂集合类型灰度规则匹配

/**
 * 集合列表匹配
 */
@Component
public class SegmentMatch implements Matcher {

    @Override
    public ConditionType getConditionType() {
        return ConditionType.SEGMENT;
    }

    @Override
    public Map<PredicateType, PredicateMatcher> getPredicateMatcher() {
        EnumMap<PredicateType, PredicateMatcher> matcherEnumMap = new EnumMap<>(PredicateType.class);

        matcherEnumMap.put(PredicateType.IS_IN, ((target, objects) -> objects.stream()
                .filter(Objects::nonNull)
                .map(Objects::toString)
                .filter(StringUtils::isNotBlank)
                .anyMatch(t -> {
                    if (target instanceof List || target instanceof Set) {
                        return ((Collection<Object>) target).stream()
                                .filter(Objects::nonNull)
                                .map(Objects::toString)
                                .anyMatch(v -> String.valueOf(v).equalsIgnoreCase(t));
                    }
                    return String.valueOf(target).equalsIgnoreCase(t);
                })));

        // ...... 此处后续的代码省略,设计和上面的实现雷同....

        return matcherEnumMap;
    }
}

客户端版本号比较算法

目前客户端的版本号存在特殊的灰度版本号,且安卓和iOS由于历史原因存在2套不兼容的情况,我们也做了定制的算法实现。

/**
 * 通用版本号比较
 * 时间复杂度O(n+m)
 * 空间复杂度O(n+m)
 *
 * @param v1    版本号v1
 * @param v2    版本号v2
 * @param limit 限制截取长度
 * @return
 */
public static int compareVersion(String v1, String v2, Integer limit) {
    if (v1 == null || v2 == null) {
        return 0;
    }
    try {
        // 处理非法字符和老版本iphone,appversion:"5.16.1(100.0421)" 格式问题
        String[] majorV1 = parserPinkAppVersion(v1).split("\\.");
        String[] majorV2 = parserPinkAppVersion(v2).split("\\.");

        int len = Math.min(Optional.ofNullable(limit)
                .orElse(Integer.MAX_VALUE), Math.max(majorV1.length, majorV2.length));
        // 一位一位比较,注意:中间不能直接跳出
        for (int i = 0; i < len; ++i) {
            int x = 0, y = 0;
            if (i < majorV1.length) {
                x = Integer.parseInt(majorV1[i]);
            }
            if (i < majorV2.length) {
                y = Integer.parseInt(majorV2[i]);
            }
            if (x > y) {
                return 1;
            }
            if (x < y) {
                return -1;
            }
        }
    } catch (NumberFormatException e) {
        //ignore
    }
    return 0;
}

灰度服务SDK接口设计

根据上面的设计,我们也就很容易抽象出一个通用标准的灰度服务接口,打包成SDK供业务研发使用。

/**
 * 灰度服务
 */
public interface GrayService {

    /**
     * 根据灰度场景判断当前请求是否命中灰度
     *
     * @param sceneKey 灰度场景key
     * @param attrs    灰度参数值
     * @return
     */
    boolean hitGray(String sceneKey, Map<String, Object> attrs);

    /**
     * 根据规则配置命中灰度
     *
     * @param toggle
     * @param attrs
     * @return
     */
    boolean hitGray(Toggle toggle, Map<String, Object> attrs);

    /**
     * 根据灰度场景key判断当前请求命中的的实验分组
     *
     * @param sceneKey 灰度场景key
     * @param attrs    灰度参数值
     * @return
     * @see MapValueUtils 配置值的获取可以使用该工具方法
     */
    HitResult hitExperimentGroup(String sceneKey, Map<String, Object> attrs);

    /**
     * 根据灰度场景key指定实验分组key,如果命中则返回命中分组都对应的配置;如果没有命中,则配置重置为空字符
     *
     * @param sceneKey           灰度场景key
     * @param experimentGroupKey 实验分组KEY
     * @param attrs              灰度参数值
     * @return
     * @see MapValueUtils 配置值的获取可以使用该工具方法
     */
    HitResult hitExperimentGroup(String sceneKey, String experimentGroupKey, Map<String, Object> attrs);
}

四、配置服务关键设计

我们很多业务类型场景自定义的配置,早期的设计都是存放在Nacos或者Ark配置平台上。这种配置存在只能面向研发等局限性,尤其在配置复杂时,修改过程需要特别谨慎。为了解决这个问题,我们设计了面向研发和业务的可视化动态表单的配置方式,集成现成的页面表单搭建技术平台或动态表单技术平台,使得配置可以以可视化的形式展示。

配置可视化设计集成

09.jpg

10.jpg

这是我们内部页面表单搭建技术,通过简单配置或者拖拽的方式进行自定义表单。

11.jpg

12.jpg

最后在灰度配置里面选择关联上对应的表单模版即可。

13.jpg

灰度服务的接口抽象

/**
 * 配置服务
 */
public interface GrayConfigService {


    /**
     * 获取配置JSON VALUE
     *
     * @param sceneKey
     * @return
     */
    String getConfigValue(String sceneKey);

    /**
     * 根据场景key获取String类型值
     *
     * @param sceneKey     配置开关配置key
     * @param pathKey      字段key,支持路径path查找;字段是”$."前缀表示,则优先使用JSONPATH解析<a  href="https://gotest.hz.netease.com/doc/jie-kou-ce-shi/xin-zeng-yong-li/can-shu-xiao-yan/jsonpi-pei/jsonpathyu-fa.html">jsonpath使用用例</a>
     * @param defaultValue 默认值
     * @return
     */
    String getStringValue(String sceneKey, String pathKey, String defaultValue);

    /**
     * 根据场景key获取boolean类型值
     *
     * @param sceneKey     配置开关配置key
     * @param pathKey      字段key,支持路径path查找;字段是”$."前缀表示,则优先使用JSONPATH解析<a  href="https://gotest.hz.netease.com/doc/jie-kou-ce-shi/xin-zeng-yong-li/can-shu-xiao-yan/jsonpi-pei/jsonpathyu-fa.html">jsonpath使用用例</a>
     * @param defaultValue 默认值
     * @return
     */
    boolean getBooleanValue(String sceneKey, String pathKey, boolean defaultValue);

    /**
     * 根据场景key获取Integer类型值
     *
     * @param sceneKey     配置开关配置key
     * @param pathKey      字段key,支持路径path查找;字段是”$."前缀表示,则优先使用JSONPATH解析<a  href="https://gotest.hz.netease.com/doc/jie-kou-ce-shi/xin-zeng-yong-li/can-shu-xiao-yan/jsonpi-pei/jsonpathyu-fa.html">jsonpath使用用例</a>
     * @param defaultValue 默认值
     * @return
     */
    Integer getIntegerValue(String sceneKey, String pathKey, Integer defaultValue);

    /**
     * 根据场景key获取Long类型值
     *
     * @param sceneKey     配置开关配置key
     * @param pathKey      字段key,支持路径path查找;字段是”$."前缀表示,则优先使用JSONPATH解析<a  href="https://gotest.hz.netease.com/doc/jie-kou-ce-shi/xin-zeng-yong-li/can-shu-xiao-yan/jsonpi-pei/jsonpathyu-fa.html">jsonpath使用用例</a>
     * @param defaultValue 默认值
     * @return
     */
    Long getLongValue(String sceneKey, String pathKey, Long defaultValue);

    /**
     * 根据场景key获取String类型值
     *
     * @param sceneKey     配置开关配置key
     * @param pathKey      字段key,支持路径path查找;字段是”$."前缀表示,则优先使用JSONPATH解析<a  href="https://gotest.hz.netease.com/doc/jie-kou-ce-shi/xin-zeng-yong-li/can-shu-xiao-yan/jsonpi-pei/jsonpathyu-fa.html">jsonpath使用用例</a>
     * @param defaultValue 默认值
     * @return
     */
    Double getDoubleValue(String sceneKey, String pathKey, Double defaultValue);

    /**
     * 根据场景key获取BigDecimal类型值
     *
     * @param sceneKey 配置开关配置key
     * @param pathKey  字段key,支持路径path查找;字段是”$."前缀表示,则优先使用JSONPATH解析<a  href="https://gotest.hz.netease.com/doc/jie-kou-ce-shi/xin-zeng-yong-li/can-shu-xiao-yan/jsonpi-pei/jsonpathyu-fa.html">jsonpath使用用例</a>
     * @param scale    精度
     * @return
     */
    BigDecimal getBigDecimalValue(String sceneKey, String pathKey, int scale);


    /**
     * 根据场景key获取String类型值
     *
     * @param sceneKey 配置开关配置key
     * @param pathKey  字段key,支持路径path查找;字段是”$."前缀表示,则优先使用JSONPATH解析<a  href="https://gotest.hz.netease.com/doc/jie-kou-ce-shi/xin-zeng-yong-li/can-shu-xiao-yan/jsonpi-pei/jsonpathyu-fa.html">jsonpath使用用例</a>
     * @return
     */
    JSONArray getJSONArrayValue(String sceneKey, String pathKey);

    /**
     * 获取配置JSONObject
     *
     * @param sceneKey
     * @return
     */
    JSONObject getConfigJSONObject(String sceneKey);


    /**
     * 获取配置反序列化对象
     *
     * @param sceneKey
     * @param targetClass
     * @param <T>
     * @return
     */
    <T> T getConfigObject(String sceneKey, Class<T> targetClass);
}

五、稳定的百分比流量调控

我们在场景使用过程中需要实现精确到万分之一的比例调控,session会话稳定,同样的上下文多次请求,命中的结果不变。设计开1万个分桶,分桶算法的实现使用了murmur3_32。

14.jpg

/**
 * 高性能的hash分桶算法
 */
private static final HashFunction murmur3 = Hashing.murmur3_32();

/**
 * 100_00个分桶
 */
private static final int bucket = 100_00;
/**
 * 稳定的流量分桶算法
 * 用来调控灰度流量比例
 *
 * @param attrsContext
 * @param bucketKey
 * @param rate
 * @return
 */
public boolean hitBucket(Map<String, Object> attrsContext, String bucketKey, Integer rate) {
    // 全量
    if (Objects.equals(Optional.ofNullable(rate).orElse(bucket), bucket)) {
        return true;
    }
    // 灰度流量比例为0
    if (Objects.equals(Optional.ofNullable(rate).orElse(bucket), 0)) {
        return false;
    }
    String bucketValue = Optional.ofNullable(attrsContext)
            .map(v -> v.get(bucketKey))
            .filter(Objects::nonNull)
            .map(String::valueOf)
            .filter(StringUtils::isNotBlank)
            .orElse(String.valueOf(System.currentTimeMillis()));
    // [0,10000)
    int hash = Hashing.consistentHash(murmur3.hashString(bucketValue, Charsets.UTF_8), bucket);
    int rateLimit = bucket;
    if (rate >= 0 && rate <= bucket) {
        rateLimit = rate;
    }
    return hash <= rateLimit;
}

六、灰度分组设计

使用场景一:自定义的灰度规则有多个,每一组都是或的关系,我们需要设计多个灰度分组条件命中;

使用场景二:当有多个灰度分组实验的时候,我们需要指定命中那个条件分组下的规则。

相关的灰度模型设计参考如下:

public class Toggle implements Serializable {

    /**
     * 规则列表,每一项都是或的关系
     */
    private List<Rule> rules;

    /**
     * 规则列表
     * 每一项是或的关系
     */
    @Data
    public static class Rule implements Serializable {
      ..............
    }


    /**
     * 条件列表
     */
    @Data
    public static class Condition implements Serializable {

       .......
    }
}

15.jpg

七、白名单设计

有些场景可能需要命中白名单的规则命中全量,无需按照具体的条件命中,这个时候设计一个优先级最高的白名单机制就显得尤为重要。相关数据模型参考如下:

public class Toggle implements Serializable {
    /**
     * 灰度白名单列表
     */
    private List<WhiteList> whiteLists;

    /**
     * 白名单
     * 每一项是或的关系
     */
    @Data
    public static class WhiteList implements Serializable {
        /**
         * 白名单解析字段
         *
         * @see GrayFields
         */
        private String subject;

        /**
         * 白名单内容
         */
        private List<Object> values;
    }
}

16.jpg

八、其他非功能设计

还有一些非功能性的设计更多是和现有系统能力集成,对接成本和每个公司的技术栈有差异,在此就不展开描述,有兴趣的同学可以自行设计。

  • 灰度数据大数据埋点服务集成,提供可视化的数据分析能力

  • 灵活的权限控制

  • 集成发布平台管控能力

  • 全生命周期灰度场景下线机制

  • 集成完整的ABTest能力

九、展望和总结

本文介绍了我们团队所开发的轻量级灰度及配置平台,以及它所带来的创新和优势。通过对业务灰度发布的需求和挑战进行深入剖析,我们意识到传统的灰度服务存在着一系列问题,诸如定制化程度高、缺乏灵活性、无法应对自定义场景等。为解决这些问题,我们研发出了一个高性能轻量级的SDK集成工具,它具有强大的功能,利用表单化的方式实现了灰度服务的产品化能力,使得灰度规则的配置和管理变得简单而直观,大大降低了研发和业务使用的门槛。这个集成工具已经成为我们研发过程中稳定性的三大保障之一,不仅得到了团队的认可,也申请通过了相关专利,目前已被集成到了日常的运营平台中。它的存在使得研发人员和业务使用者能够更轻松地享受到灰度服务的益处。

通过本文,我们希望这个轻量级的灰度配置平台的设计思路对你有所帮助。整体的设计思路非常轻量级,还有很多用户体验和非功能的设计没有在本文中展开描述,这些也并不是这个平台的核心设计内容。如果您有兴趣,可以自行补充;如需进一步的帮助或有其他需求,欢迎随时联系我们。

*文 / feel

本文属得物技术原创,更多精彩文章请看:得物技术

未经得物技术许可严禁转载,否则依法追究法律责任!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2033668.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

x264 编码器 PSNR算法源码分析

PSNR PSNR(Peak Signal-to-Noise Ratio,峰值信噪比)是一种常用的图像质量评价指标,用于衡量图像或视频的清晰度和质量。PSNR是基于信号的最大可能功率与影响信号的噪声功率之间的比率。在图像处理领域,PSNR通常用来评估图像压缩或图像增强算法的效果。 PSNR的计算公式是…

思科CCNP最新考证流程

CCNP CCNP全称思科网络高级工程师认证&#xff08;Cisco Certified Network Professional&#xff09;&#xff0c;是Cisco思科认证中的中级认证。获得ccnp证书表示着资深网络工程师具有对100个节点到超过500个节点的融合局域网和广域网进行安装、配置和故障排除的能力。能够管…

LeetCode257 二叉树的所有路径

前言 题目&#xff1a; 257. 二叉树的所有路径 文档&#xff1a; 代码随想录——二叉树的所有路径 编程语言&#xff1a; C 解题状态&#xff1a; 没思路&#xff0c;简单题强度好高… 思路 本题利用了递归加回溯的思路。 这道题目要求从根节点到叶子的路径&#xff0c;所以需…

一个Indie Hacker的微SaaS技术栈

如今&#xff0c;可用的技术非常多&#xff0c;我们每个月都会看到各种新的 JS 框架发布&#xff0c;有时&#xff0c;如果你一开始没有选择正确的技术堆栈&#xff0c;以后扩展起来就会很困难。因此&#xff0c;在今天的文章中&#xff0c;我将与你分享我用于开发微型 SaaS 的…

vue使用富文本编辑器+自由伸缩图片

首先要下载依赖&#xff0c;下方是本人使用的package.json&#xff0c;下载完依赖如果有启动项目失败的情况&#xff0c;建议将依赖版本降低或使用和下方一样的版本 package.json代码 {"name": "l","version": "0.1.0","privat…

Linux中线程常用接口(创建,等待,退出,取消)

pthread_create #include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); Compile and link with -pthread. 编译时应注意。 #include<iostream> #in…

使用Playwright解决reCAPTCHA的分步指南

您是否在您的网络爬虫中遇到过CAPTCHA&#xff1f;许多网站使用CAPTCHA系统&#xff08;最常见的是reCAPTCHA&#xff09;来防止自动化访问。但是&#xff0c;本文将指导您使用Playwright&#xff08;一种强大的浏览器自动化工具&#xff09;和CapSolver&#xff08;一个设计用…

# 利刃出鞘_Tomcat 核心原理解析(二)

利刃出鞘_Tomcat 核心原理解析&#xff08;二&#xff09; 一、 Tomcat专题 - Tomcat架构 - HTTP工作流程 1、Http 工作原理 HTTP 协议&#xff1a;是浏览器与服务器之间的数据传送协议。作为应用层协议&#xff0c;HTTP 是基于 TCP/IP 协议来传递数据的&#xff08;HTML文件…

AI 的偏见来自数据集,而数据集的偏见来自人类 | Open AGI Forum

作者 | Annie Xu 采访、责编 | Eric Wang 出品丨GOSIM 开源创新汇 Richard Vencu&#xff0c;现任 Stability AI 机器学习运维负责人、LAION 工程负责人兼创始人&#xff0c;他的人生可谓十分精彩。 已过知天命之年的他是个中国通&#xff0c;极其热爱中国的武术、茶叶、诱人…

BugKu CTF Misc:被勒索了 disordered_zip simple MQTT 请攻击这个压缩包

前言 BugKu是一个由乌云知识库&#xff08;wooyun.org&#xff09;推出的在线漏洞靶场。乌云知识库是一个致力于收集、整理和分享互联网安全漏洞信息的社区平台。 BugKu旨在提供一个实践和学习网络安全的平台&#xff0c;供安全爱好者和渗透测试人员进行挑战和练习。它包含了…

03. 剑指offer刷题-二叉树篇(第二部分)

class Solution { public:TreeNode* Convert(TreeNode* pRootOfTree) {if(pRootOfTree nullptr) return nullptr;vector<TreeNode*> cur traversal(pRootOfTree);return cur[0];}// 这道题需要用到「分解问题」的思维&#xff0c;想把整棵链表&#xff0c;可以先把左右…

[upload]-做题笔记

项目下载地址&#xff1a;https://github.com/c0ny1/upload-labs 第一关 查看源代码&#xff0c;可以看到是前端js限制上传jpg,png,gif后缀文件 function checkFile() {var file document.getElementsByName(upload_file)[0].value;if (file null || file "") …

Unity读取Android外部文件

最近近到个小需求,需要读Android件夹中的图片.在这里做一个记录. 首先读写部分,这里以图片为例子: 一读写部分 写入部分: 需要注意的是因为只有这个地址支持外部读写,所以这里用到的地址都以 :Application.persistentDataPath为地址起始. private Texture2D __CaptureCamera…

促进服务消费高质量发展虽好,但不能缺钱

近日&#xff0c;国务院印发《关于促进服务消费高质量发展的意见》&#xff0c;提出6方面20项重点任务。 百度图片&#xff1a;2024讲党课ppt国务院关于促进服务消费高质量发展​ 一是挖掘餐饮住宿、家政服务、养老托育等基础型消费潜力&#xff1b; 二是激发文化娱乐、旅游、…

Upload 上传图标不显示

el-upload如果在使用 Element UI 的 <el-upload> 组件时上传图标不显示&#xff0c;可能是由几个不同的原因造成的。以下是一些排查和解决这个问题的步骤&#xff1a; 如果在使用 Element UI 的 <el-upload> 组件时上传图标不显示&#xff0c;可能是由几个不同的原…

antd react echarts地图组件及使用

地图组件&#xff1a; import { useRef, useEffect } from "react"; import * as echarts from "echarts"; import chinaJson from ./chinaJson;const MapIndex ({option,width "100%",height "100%", }) > {const ref useRef…

08:【stm32】中断二:EXTI(外部中断)

EXTI&#xff08;外部中断&#xff09; 1、EXTI简介2、EXTI的内部结构2.1、EXTI通道2.2、内部寄存器 3、EXTI的编写程序3.1、EXTI的编程接口3.1.1、EXTI_Init 4、编写实验 1、EXTI简介 外部中断控制器&#xff0c;能够检测外部输入信号的变化边沿并由此产生中断。通过检测上升沿…

BugKu CTF Misc:密室逃脱 铁子,来一道 想要种子吗 哥哥的秘密

前言 BugKu是一个由乌云知识库&#xff08;wooyun.org&#xff09;推出的在线漏洞靶场。乌云知识库是一个致力于收集、整理和分享互联网安全漏洞信息的社区平台。 BugKu旨在提供一个实践和学习网络安全的平台&#xff0c;供安全爱好者和渗透测试人员进行挑战和练习。它包含了…

Sql语句出现ORA-00933: SQL command not properly ended的解决方法

目录 1. 问题所示2. 原理分析3. 解决方法1. 问题所示 执行sql语句的时候出现如下问题: ORA-00933: SQL command not properly ended截图如下所示: 2. 原理分析 ORA-00933: SQL command not properly ended 是 Oracle 数据库中的错误,指示 SQL 语句存在语法问题 MySQL 和…

聚观早报 | 马斯克xAI新计划;iPhone SE 4将配A18芯片

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 8月13日消息 马斯克xAI新计划 iPhone SE 4将配A18芯片 真我GT7 Pro参数曝光 谷歌Pixel 9 Pro Fold最新配色 苹果…