sylar高性能服务器-日志(P15-P20)内容记录

news2024/11/16 13:54:23

以下内容是sylar高性能服务器视频的简单记录,如果你在调试代码时遇到了困难那么下面内容会有帮助。

文章目录

    • p15:配置变更事件
      • 一、函数
      • 二、结果展示
    • p16:日志系统的整合1
      • 一、函数
      • 二、小结
    • p17-18:日志系统的整合2、3
      • 一、函数
      • 二、结果展示
    • P19:日志系统的整合4
      • 一、打印格式冗余问题
      • 二、由于配置文件的存在,后期改变了formatter不会生效
      • 三、文件名不应该输出全部路径
    • p20:日志系统的整合5

p15:配置变更事件

​ 配置系统部分的最后一节,本节主要内容满足下列功能:

  • 当服务器在运行过程中,如果配置的内容进行了更改,那么程序会返回更改前的值和新值,对更改配置行为进行监听

一、函数

  1. 使用typedef定义一个回调函数,std::function是可调用对象的wrapper,它可以包装函数、lambda表达式、bind表达式、函数对象、成员函数指针、成员变量指针,定义在头文件<functional>

    typedef std::function<void (const T& old_value, const T& new_value)> on_change_cb;
    
  2. 使用map存放监听函数,这里使用map作为存储结构是因为functional没有比较函数,如果要删除一个function,或者在容器里面判断是否是一个相等的函数是无法做到的,key值就可以辅助我们删除,要求唯一可以使用hash

    std::map<uint64_t,on_change_cb> m_cbs;
    
  3. 其它监听器功能函数

    	//  添加监听函数
        void addListener(uint64_t key,on_change_cb cb) {
            m_cbs[key] = cb;
        }
        // 删除监听函数
        void delListener(uint64_t key) {
            m_cbs.erase(key);
        }
        // 获取监听函数
        on_change_cb getListener(uint64_t key) {
            auto it = m_cbs.find(key);
            return it == m_cbs.end() ? nullptr : it->second;
        }
        // 清空所以监听器
        void clearListener() {
            m_cbs.clear();
        }
    
  4. 更改ConfigVar的赋值函数

    void setValue(const T& v) { 
        	// 注意之前我们定义的自定义类型Person没有重载==,需要添加
            if(v == m_val) {        // 如果新值和原值一样,说明无变化直接返回
                return;
            }
            for(auto& i : m_cbs) {  // 有变化则通过map中key找到对应配置更改
                i.second(m_val, v); // 传入定义的监听器函数,具体做什么由该监听器决定
            }
            m_val = v;
        }
    

二、结果展示

  1. 定义监听器

    // 监控配置的使用
        g_person->addListener(10,[](const Person& old_vale, const Person& new_vale){
             SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "oldVale: " << old_vale.toString()
                << " newValue: " << new_vale.toString();
        });
    
  2. 结果:可以看到当我们通过配置文件改变参数后,程序进行了提示

    image-20231221152400954

p16:日志系统的整合1

​ 本节将开启日志系统的整合,将会回顾修改很多以前的代码,建议大家可以看看之前的笔记,反正我是忘得差不多了。本节主要内容如下:

  • 当用户定义一个logger而日志管理容器中没有时,不用赋予其m_root,而是创建一个新的logger,用m_root的变量去初始化logger的空变量。

一、函数

  1. 日志格式

    logs:
        - name: root
          level: (debug,info,warn,error,fatal)
          formatter: '%d%T%m%n'
          appenders:
              - type: (FileLogAppender, StdoutLogAppender)
              	level: (debug....)
                file: /logs/xxx.log
    
  2. 新增查找logger的宏函数

    #define SYLAR_LOG_NAME(name) sylar::LoggerMgr::GetInstance()->getLogger()
    
  3. 提出本节需要解决的问题,当程序运行时我们定义的了一个log,但是yml文件中没有名为system,那么以前的程序就会把root赋予给我们。因此我们希望这种情况下程序应该创建一个新的日志system返回给我们,这样我们后期想要往system写东西时就可以做到。

    sylar::Logger g_log = sylar::LoggerMgr::GetInstance()->getLogger("system");
    SYLAR_LOG_INFO(g_logger) << "xxx log";
    
  4. 修改Logger日志输出器,新增成员变量;让Logger成为LoggerManager的友元函数

    Logger::ptr m_root; 
    
    friend class LoggerManager;
    
  5. 修改LoggerManagergetLogger方法,这里我们就已经创建了一个用户指定名称的log,并且存放在m_loggers中(<string,looger>)。

    Logger::ptr LoggerManager::getLogger(const std::string& name) {
        auto it = m_loggers.find(name);
        if(it != m_loggers.end()) {
            return it->second;
        }
        // 不存在则创建
        Logger::ptr logger(new Logger(name));
        // 创建的logger什么都没有,则根据root赋值
        logger->m_root = m_root;
        m_loggers[name] = logger;
        return logger;
    }
    
  6. 修改Loggerlog方法,新增功能:如果当前日志的appender为空,则使用m_rootappender打印,否则正常打印。

    void Logger::log(LogLevel::Level level, LogEvent::ptr event)  {
        if(level >= m_level) {
            auto self = shared_from_this();
            if(!m_appender.empty()) { // 判断当前日志的appender是否为空
                for(auto &i : m_appender) {
                    i->log(self,level, event);
                } 
            } else if(m_root){
                m_root->log(level,event);
            }
            
        }
    }
    
  7. LoggerManager的构造函数,新增init()方法,本节还未实现

  8. 新增结构体LogDefine

    struct LogDefine{
        std::string name;
        LogLevel::Level level = LogLevel::UNKNOW;
        std::string formatter;
        std::vector<LogAppenderDefine> appenders;
    
        bool operator==(const LogDefine& oth) const{
            return name == oth.name
                && level == oth.level
                && formatter == oth.formatter
                && appenders == oth.appenders;
        }
    
        bool operator<(const LogDefine& oth) const {
            return name < oth.name;
        }
    };
    
  9. 新增结构体LogAppenderDefine

    struct LogAppenderDefine {
        int type = 0;   // 1 File. 2 Stdout
        LogLevel::Level level = LogLevel::UNKNOW;
        std::string formatter;
        std::string file;
    
        bool operator==(const LogAppenderDefine& oth) const {
            return type ==oth.type
                && level == oth.level
                && formatter == oth.formatter
                && file == oth.file;
        }
    
    };
    

二、小结

​ 以上就是本节的内容,一些函数具体的实现过程还未实现,我自己在写的时候不太明白一些函数的实际用途,看后续章节的实例应该会好点。

p17-18:日志系统的整合2、3

​ 上一节声明了日志系统整合需要的基础类,本节主要在此基础上设计偏特化实现配置文件与String之间的转化,以及绑定监听事件。重点注意sylar在p17暂停录制去改代码,然后改完也没讲,如果你跟着视频敲完出现segmentation fault (core dumped)的问题就是因为某些函数没有修改。目前写到这里我自己感觉理解得很乱,所以下面只是一些简单的记录,等以后二刷再来深究。不过我会把调试遇到的问题都罗列出来,可以减少大家的调试时间,比如本节sylar改了代码不说,debug也找不到的位置(不过了解了gdb中使用core文件找bug,可以去百度一下,加强对gdb的理解也是非常重要),浪费我整整3个小时,这两节视频还重新看了一遍。

一、函数

​ 注意我不会把所有更改都贴出来,只是展示几个核心函数。

LogInit

​ 该函数的作用主要是当读取用户的yml配置文件时如果发生了变化进行相应的处理。

struct LogIniter {
    LogIniter() {
        // 给日志绑定一个事件
          g_log_defines->addListener(1213,[](const std::set<LogDefine>& old_value,
                    const std::set<LogDefine>& new_value){
            SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "on_logger_conf_changed";
            for(auto& i : new_value) {
                auto it = old_value.find(i);
                sylar::Logger::ptr logger;
                if(it == old_value.end()) {
                    //新增logger
                    logger = SYLAR_LOG_NAME(i.name);
                } else {
                    if(!(i == *it)) {
                        //修改的logger
                        logger = SYLAR_LOG_NAME(i.name);
                    } else {
                        continue;
                    }
                }
                logger->setLevel(i.level);
                if(!i.formatter.empty()) {
                    logger->setFormatter(i.formatter);
                }

                logger->clrAppender();
                for(auto& a : i.appenders) {
                    sylar::LogAppender::ptr ap;
                    if(a.type == 1) {
                        ap.reset(new FileLogAppender(a.file));
                    } else if(a.type == 2) {
                        ap.reset(new StdoutAppender);
                    }
                    ap->setLevel(a.level);
                    if(!a.formatter.empty()) {
                        LogFormatter::ptr fmt(new LogFormatter(a.formatter));
                        if(!fmt->isError()) {
                            ap->setFormatter(fmt);
                        } else {
                            std::cout << "log.name=" << i.name << " appender type=" << a.type
                                      << " formatter=" << a.formatter << " is invalid" << std::endl;
                        }
                    }
                    logger->addAppender(ap);
                }
            }

            for(auto& i : old_value) {
                auto it = new_value.find(i);
                if(it == new_value.end()) {
                    //删除logger
                    auto logger = SYLAR_LOG_NAME(i.name);
                    logger->setLevel((LogLevel::Level)0);
                    logger->clrAppender();
                }
            }
        });
    }
};

偏特化

​ 和在P15里写的自定义配置类型很相似。

template<>
class LexicalCast<std::string, LogDefine> {
public:
    LogDefine operator()(const std::string& v) {
        YAML::Node n = YAML::Load(v);
        LogDefine ld;
        if(!n["name"].IsDefined()) {
            std::cout << "log config error: name is null, " << n
                      << std::endl;
            throw std::logic_error("log config name is null");
        }
        ld.name = n["name"].as<std::string>();
        ld.level = LogLevel::FromString(n["level"].IsDefined() ? n["level"].as<std::string>() : "");
        if(n["formatter"].IsDefined()) {
            ld.formatter = n["formatter"].as<std::string>();
        }

        if(n["appenders"].IsDefined()) {
            for(size_t x = 0; x < n["appenders"].size(); ++x) {
                auto a = n["appenders"][x];
                if(!a["type"].IsDefined()) {
                    std::cout << "log config error: appender type is null, " << a
                              << std::endl;
                    continue;
                }
                std::string type = a["type"].as<std::string>();
                LogAppenderDefine lad;
                if(type == "FileLogAppender") {
                    lad.type = 1;
                    if(!a["file"].IsDefined()) {
                        std::cout << "log config error: fileappender file is null, " << a
                              << std::endl;
                        continue;
                    }
                    lad.file = a["file"].as<std::string>();
                    if(a["formatter"].IsDefined()) {
                        lad.formatter = a["formatter"].as<std::string>();
                    }
                } else if(type == "StdoutLogAppender") {
                    lad.type = 2;
                    if(a["formatter"].IsDefined()) {
                        lad.formatter = a["formatter"].as<std::string>();
                    }
                } else {
                    std::cout << "log config error: appender type is invalid, " << a
                              << std::endl;
                    continue;
                }

                ld.appenders.push_back(lad);
            }
        }
        return ld;
    }
};

template<>
class LexicalCast<LogDefine, std::string> {
public:
    std::string operator()(const LogDefine& i) {
        YAML::Node n;
        n["name"] = i.name;
        if(i.level != LogLevel::UNKNOW) {
            n["level"] = LogLevel::ToString(i.level);
        }
        if(i.formatter.empty()) {
            n["formatter"] = i.formatter;
        }

        for(auto& a : i.appenders) {
            YAML::Node na;
            if(a.type == 1) {
                na["type"] = "FileLogAppender";
                na["file"] = a.file;
            } else if(a.type == 2) {
                na["type"] = "StdoutLogAppender";
            }
            if(a.level != LogLevel::UNKNOW) {
                na["level"] = LogLevel::ToString(a.level);
            }

            if(!a.formatter.empty()) {
                na["formatter"] = a.formatter;
            }

            n["appenders"].push_back(na);
        }
        std::stringstream ss;
        ss << n;
        return ss.str();
    }
};

Config修改

​ 在配置管理类Config中之前我们定义了一个静态变量s_datas,而同时在该类中还有一个Lookup,有一个问题就是静态成员它们的初始化顺序是随机的,而在Lookup中使用了s_datas,如果此时s_datas并没有初始化,运行程序就会报错。所以把该静态成员放在静态函数里,Lookup中通过该函数来获取s_datas,就一定能提前初始化s_datas

​ 下面是gdb调试的截图,就这个报错信息死活都找不到。

image-20240110135435732

```
     static typename ConfigVar<T>::ptr Lookup(const std::string& name,
            const T& default_value, const std::string& description = "")
```
static ConfigVarMap& GetDatas() {
        static ConfigVarMap s_datas;
        return s_datas;
    }

// config.cc文件有些地方也需要更改,使用方式:`GetDatas().find(name);`
ConfigvarBase::ptr Config::LookupBase(const std::string& name) {
    auto it = GetDatas().find(name);
    return it == GetDatas().end() ? nullptr : it->second;
}

二、结果展示

测试文件

void test_log() {
    static sylar::Logger::ptr system_log = SYLAR_LOG_NAME("system");
    SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;
    std::cout << sylar::LoggerMgr::GetInstance()->toYamlString() << std::endl;
    YAML::Node root = YAML::LoadFile("/root/Web-learning/sylar/bin/conf/log.yml");
    sylar::Config::LoadFromYaml(root);
    std::cout << "======================" << std::endl;
    std::cout << sylar::LoggerMgr::GetInstance()->toYamlString() << std::endl;
    SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;
}

int main(int argc, char** argv) {
    test_log();
    return 0;
}

测试log

logs:
    - name: root
      level: INFO
      formatter: '%d%T%m%n'
      appenders:
          - type: FileLogAppender
            file: /root/Web-learning/sylar/log.txt
          - type: StdoutLogAppender
    - name: system
      level: DEBUG
      formatter: '%d%T%m%n'
      appenders:
          - type: FileLogAppender
            file: /root/Web-learning/sylar/system.txt
          - type: StdoutLogAppender

结果

​ 可以看到我们以yml的方式从文件中加载了配置项并对默认的配置进行了修改

image-20240110135350757

P19:日志系统的整合4

​ 本节主要内容主要是解决下列3个问题

  • 打印格式冗余问题
  • 由于配置文件的存在,后期改变了formatter不会生效
  • 文件名不应该输出全部路径

一、打印格式冗余问题

​ 如下图,当我们读取配置文件log.yml的信息并打印到控制台,发现及时我们没有在配置中给出appender的打印格式,但是由于继承的关系拥有了父类的格式,所以我们想做的是如果appender没有格式则不打印。

image-20240110153348245

首先在类LogAppender中新增一个成员变量m_hasFormatter,并把该类作为logger的友元类:friend class Logger;

 bool m_hasFormatter = false;                                        // 是否有自己的日志格式器

之后我们在设置appender的格式时,就需要根据是否有格式更新m_hasFormatter的值

void LogAppender::setFormatter(LogFormatter::ptr val) {
    m_formatter = val;
    if(m_formatter) {
        m_hasFormatter = true;
    } else {
        m_hasFormatter = false;
    }
}

最后来到AppendertoYamlString函数,就可以根据m_hasFormatter的值来决定是否给当前appender设置打印格式

std::string FileLogAppender::toYamlString() {
    YAML::Node node;
    node["type"] = "FileLogAppender";
    node["file"] = m_filename;
    if(m_level != LogLevel::UNKNOW) {
        node["level"] = LogLevel::ToString(m_level);
    }
    if(m_hasFormatter && m_formatter) {
        node["formatter"] = m_formatter->getPattern();
    }
    std::stringstream ss;
    ss << node;
    return ss.str();
}

然后看看效果,现在如果不在log.yml中给appender设置打印格式,就不会直接基础父类的打印格式

image-20240110154114339

二、由于配置文件的存在,后期改变了formatter不会生效

下列测试代码我在最后更改了log的打印格式,但是控制台却还是保留配置文件中的打印格式。

void test_log() {
    static sylar::Logger::ptr system_log = SYLAR_LOG_NAME("system");
    SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;
    std::cout << sylar::LoggerMgr::GetInstance()->toYamlString() << std::endl;
    YAML::Node root = YAML::LoadFile("/root/Web-learning/sylar/bin/conf/log.yml");
    sylar::Config::LoadFromYaml(root);
    std::cout << "======================" << std::endl;
    std::cout << sylar::LoggerMgr::GetInstance()->toYamlString() << std::endl;
    std::cout << "======================" << std::endl;
    std::cout << root << std::endl;
    SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;
    //
    system_log->setFormatter("%d - %m%n");
    SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;
}

与之前类似,更改LoggersetFormatter方法,防止它们直接基础父类的打印格式从而忽略掉后期格式的修改

void Logger::setFormatter(LogFormatter::ptr val) {
    m_formatter = val;
    // 防止所有的appender在任何情况下都直接继承log的格式
    for(auto& i : m_appender) { // 遍历该log的appender
        if(!i->m_hasFormatter) { // 如果当前appender没有自己的格式,才则继承log的格式
            i->m_formatter = m_formatter;
        }
    }
}
void Logger::setFormatter(const std::string& val) {
    // 把字符串解析成格式
    sylar::LogFormatter::ptr new_val(new sylar::LogFormatter(val));
    if(new_val->isError()) {
        std::cout << "Logger setFormatter name = " << m_name
                << " value = " << val << " invalid formatter"
                << std:: endl;
        return;
    }
    setFormatter(new_val);
}

测试结果:

image-20240110154704914

三、文件名不应该输出全部路径

image-20240110154846456

该问题主要是对CmakeList.txt的修改,首先在cmake文件夹下引入了一个文件utils.cmake

function(force_redefine_file_macro_for_sources targetname)
    get_target_property(source_files "${targetname}" SOURCES)
    foreach(sourcefile ${source_files})
        # Get source file's current list of compile definitions.
        get_property(defs SOURCE "${sourcefile}"
            PROPERTY COMPILE_DEFINITIONS)
        # Get the relative path of the source file in project directory
        get_filename_component(filepath "${sourcefile}" ABSOLUTE)
        string(REPLACE ${PROJECT_SOURCE_DIR}/ "" relpath ${filepath})
        list(APPEND defs "__FILE__=\"${relpath}\"")
        # Set the updated compile definitions on the source file.
        set_property(
            SOURCE "${sourcefile}"
            PROPERTY COMPILE_DEFINITIONS ${defs}
            )
    endforeach()
endfunction()

function(ragelmaker src_rl outputlist outputdir)
    #Create a custom build step that will call ragel on the provided src_rl file.
    #The output .cpp file will be appended to the variable name passed in outputlist.

    get_filename_component(src_file ${src_rl} NAME_WE)

    set(rl_out ${outputdir}/${src_file}.rl.cc)

    #adding to the list inside a function takes special care, we cannot use list(APPEND...)
    #because the results are local scope only
    set(${outputlist} ${${outputlist}} ${rl_out} PARENT_SCOPE)

    #Warning: The " -S -M -l -C -T0  --error-format=msvc" are added to match existing window invocation
    #we might want something different for mac and linux
    add_custom_command(
        OUTPUT ${rl_out}
        COMMAND cd ${outputdir}
        COMMAND ragel ${CMAKE_CURRENT_SOURCE_DIR}/${src_rl} -o ${rl_out} -l -C -G2  --error-format=msvc
        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${src_rl}
        )
    set_source_files_properties(${rl_out} PROPERTIES GENERATED TRUE)
endfunction(ragelmaker)

function(protobufmaker src_proto outputlist outputdir)
    #Create a custom build step that will call ragel on the provided src_rl file.
    #The output .cpp file will be appended to the variable name passed in outputlist.

    get_filename_component(src_file ${src_proto} NAME_WE)
    get_filename_component(src_path ${src_proto} PATH)

    set(protobuf_out ${outputdir}/${src_path}/${src_file}.pb.cc)

    #adding to the list inside a function takes special care, we cannot use list(APPEND...)
    #because the results are local scope only
    set(${outputlist} ${${outputlist}} ${protobuf_out} PARENT_SCOPE)

    add_custom_command(
        OUTPUT ${protobuf_out}
        COMMAND protoc --cpp_out=${outputdir} -I${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${src_proto}
        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${src_proto}
        )
    set_source_files_properties(${protobuf_out} PROPERTIES GENERATED TRUE)
endfunction(protobufmaker)


function(sylar_add_executable targetname srcs depends libs)
    add_executable(${targetname} ${srcs})
    add_dependencies(${targetname} ${depends})
    force_redefine_file_macro_for_sources(${targetname})
    target_link_libraries(${targetname} ${libs})
endfunction()

然后在``CmakeList.txt`进行修改

# 加入-Wno-builtin-macro-redefined去掉警告
set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -rdynamic -O0 -g -std=c++11 -Wall -Wno-deprecated -Werror -Wno-unused-function -Wno-builtin-macro-redefined")
# 引入刚才的文件
include (cmake/utils.cmake)

# 哪个文件需要参数名就是谁
force_redefine_file_macro_for_sources(sylar) #__FILE__ 重定义该宏为相对路径
force_redefine_file_macro_for_sources(test) #__FILE__ 重定义该宏为相对路径
force_redefine_file_macro_for_sources(test_config) #__FILE__ 重定义该宏为相对路径

结果:

image-20240110155130311

p20:日志系统的整合5

​ 该节是日志系统的一个总结,到这里基础的日志系统就已经搭建好了,后续章节也会再次对其进行完善。

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

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

相关文章

网络命令行工具nc的使用复习

之前写过nc的博文&#xff1b;下面复习一下&#xff1b; 可以把nc放到C:\Windows\System32下&#xff1b; nc -l -p 9007&#xff0c;-l 是监听模式&#xff0c;-p指定端口&#xff0c;作为服务端监听9007端口&#xff1b; nc 127.0.0.1 9007&#xff0c;作为客户端去连接指定…

基于算术电路的全同态加密方案介绍

基于算术电路的全同态加密方案介绍 摘 要&#xff1a; 云计算技术目前已经发展得相对成熟&#xff0c;应用也逐步得到普及&#xff0c;它所具有的强大的数据处理能力&#xff0c;能够帮助个体用户计算复杂的数据。但它带来便利的同时&#xff0c;也催生了一系列用户隐私数据保…

Vue2.组件通信

样式冲突 写在组件中的样式默认会全局生效。容易造成多个组件之间的样式冲突问题。 可以给组件加上scoped属性&#xff0c;让样式只作用于当前组件。 原理&#xff1a; 给当前组件模板的所有元素&#xff0c;加上一个自定义属性data-v-hash值&#xff0c;用以区分不同的组件。…

七大排序(含快排+归并的递归版和非递归版)

文章目录 前言一、冒泡排序二、选择排序三、插入排序四、希尔排序五、堆排序六、快速排序快排的递归方式快排的非递归方式 七、归并排序自上而下的递归自下而上的迭代 总结 前言 排序&#xff1a; 所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的…

二分图带权最大匹配-KM算法详解

文章目录 零、前言一、红娘再牵线二、二分图带权最大完备匹配2.1二分图带权最大匹配2.2概念2.3KM算法2.3.1交错树2.3.2顶标2.3.3相等子图2.3.4算法原理2.3.5算法实现 三、OJ练习3.1奔小康赚大钱3.2Ants 零、前言 关于二分图&#xff1a;二分图及染色法判定-CSDN博客 关于二分…

Vue + JS + tauri 开发一个简单的PC端桌面应用程序

Vue JS tauri 开发一个简单的PC端桌面应用程序 文章目录 Vue JS tauri 开发一个简单的PC端桌面应用程序1. 环境准备1.1 安装 Microsoft Visual Studio C 生成工具[^2]1.2 安装 Rust[^3] 2. 使用 vite 打包工具创建一个 vue 应用2.1 使用Vite创建前端Vue项目2.2 更改Vite打包…

vue中使用高德地图渲染多个不同类型的点,根据勾选数据 类型不同打点显示隐藏

一、在index.html文件中引入高德地图JavaScript API的2.0版本SDK <script src"https://webapi.amap.com/maps?v2.0&key你的高德地图Key"></script>二、创建一个Vue组件&#xff0c;用于渲染地图和点位 html <template><div class"m…

Axure全面指南:正确打开并高效使用的步骤!

AxureRP是目前流行的设计精美的用户界面和交互软件。AxureRP根据其应用领域提供了一组丰富的UI控制。作为Axure的国内替代品&#xff0c;即时设计可以在线协作&#xff0c;浏览器可以在无需下载客户端的情况下打开和使用。如果以前使用Axure&#xff0c;很容易切换到即时设计。…

记录el-select+el-tree复选框,支持模糊查询,懒加载,树父子节点不关联,不全选

需求&#xff1a;一个机构下拉菜单&#xff0c;一个人员下拉菜单&#xff0c;默认带入当前登录用户的机构和人员。机构下拉菜单为两个接口&#xff0c;模糊查询为一个接口不包含懒加载&#xff0c;默认非模糊查询情况下为一个接口&#xff0c;点击节点懒加载。机构下拉菜单数据…

探索FTP:原理、实践与安全优化

引言 在正式开始讲解之前&#xff0c;首先来了解一下文件存储的类型有哪些。 DAS、SAN和NAS是三种不同的存储架构&#xff0c;分别用于解决不同场景下的数据存储需求。 DAS (Direct Attached Storage 直接附加存储)&#xff1a;DAS 是指将存储设备&#xff08;如硬盘&#x…

线程之间如何传递上下文信息

文章目录 源码解读1. 扩展ThreadPoolExecutor2. 扩展Runnable3. 整体流程 源于工作中一个业务场景的需求。 源码 话不多说&#xff0c;先贴完整的源码&#xff1a; public class ContextPassingBetweenThread {private static ThreadLocal<String> CONTEXT new Thread…

倍福嵌入式PLC开发团队建设

倍福嵌入式PLC开发工程师确实比较难找&#xff0c;这是因为这个领域需要具备丰富的专业知识和技能&#xff0c;而且经验越丰富的工程师越难找到。以下是一些可能导致倍福嵌入式PLC开发工程师难找的原因&#xff1a; 具备相关技能的工程师数量相对较少&#xff1a;嵌入式PLC开发…

Linux平台建立GB28181设备模拟器

目录 下载模拟器解决动态库缺少问题运行模拟器抓包参考资料 在没有GB28181摄像机的情况下,在Linux虚拟机中模拟出一台GB28181摄像机用于调试和学习. 下载模拟器 到网站下载Linux 平台版本: https://www.happytimesoft.com/download.html tar -zxvf happytime-gb28181-device…

Python简介-Python入门到精通

Python的创始人为荷兰人吉多范罗苏姆&#xff08;Guido van Rossum&#xff09;。1989年圣诞节期间&#xff0c;在阿姆斯特丹&#xff0c;Guido为了打发圣诞节的无趣&#xff0c;决心开发一个新的脚本解释程序&#xff0c;作为ABC语言的一种继承。之所以选中Python&#xff08;…

Python中元组解构的技巧

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python中&#xff0c;元组&#xff08;tuple&#xff09;是一种常用的数据结构&#xff0c;它可以将多个值组合在一起。元组的解构是一项强大的特性&#xff0c;快速、方便地将元组中的值分配给多个变量。本文…

【电脑技巧】Win11关闭自动更新

要想彻底关闭Windows电脑的自动更新&#xff0c;仅仅从系统设置里面选择暂停更新是完全不够用的&#xff0c;只有将windows自动更新的服务关闭掉&#xff0c;才能有效阻止其更新。 关闭win11电脑自动更新的办法&#xff0c;具体操作如下&#xff1a; 1.在winr运行框中输入servi…

vivado 使用IP Integrator源

使用IP Integrator源 在Vivado Design Suite中&#xff0c;您可以在RTL中添加和管理IP子系统块设计&#xff08;.bd&#xff09;项目或设计。使用Vivado IP集成程序&#xff0c;您可以创建IP子系统块设计。IP集成程序使您能够通过实例化和将Vivado IP目录中的多个IP核互连。可…

运筹说 第56期 | 整数规划的数学模型割平面法

前几章讨论过的线性规划问题的一个共同特点是&#xff1a;最优解的取值可以是分数或者小数。然而&#xff0c;在许多实际问题中&#xff0c;决策者要求最优解必须是整数&#xff0c;例如公交车的车辆数、员工的人数、机器的台数、产品的件数等。那么&#xff0c;我们能否将得到…

Zynq7020 使用 Video Processing Subsystem 实现图像缩放

1、前言 没玩过图像缩放都不好意思说自己玩儿过FPGA&#xff0c;这是CSDN某大佬说过的一句话&#xff0c;鄙人深信不疑。。。 目前市面上主流的FPGA图像缩放方案如下&#xff1a;1&#xff1a;Xilinx的HLS方案&#xff0c;该方案简单&#xff0c;易于实现&#xff0c;但只能用…

【RTOS】快速体验FreeRTOS所有常用API(4)队列

目录 四、队列2.1 概念2.2 创建队列2.3 写队列2.4 读队列2.5 队列集&#xff08;可跳过&#xff09; 四、队列 该部分在上份代码基础上修改得来&#xff0c;代码下载链接&#xff1a; https://wwzr.lanzout.com/iBNAS1l75bvc 密码:7xy2 该代码尽量做到最简&#xff0c;不添加多…