C++项目实战——基于多设计模式下的同步异步日志系统-⑨-同步日志器类与日志器建造者类设计

news2024/11/29 13:45:22

文章目录

  • 专栏导读
  • Logger类设计
  • 同步日志器类设计
  • 同步日志器测试
  • 日志器建造者模式设计
    • 抽象日志器建造者类
    • 派生局部日志器建造者
    • 日志器建造者类测试
  • 同步日志器类与日志器建造者类整理

专栏导读

🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。

🌸专栏简介:本文收录于 C++项目——基于多设计模式下的同步与异步日志系统

🌸相关专栏推荐:C语言初阶系列C语言进阶系列C++系列数据结构与算法Linux

在这里插入图片描述
日志器主要是用来与前端交互,当我们需要使用日志系统打印日志消息时,只需要创建Logger对象,调用该对象的debuginfowarnerrorfatal等方法输出自己想要打印的日志消息即可。支持解析可变参数列表和输出格式,就可以做到像printf函数一样打印日志。

因为日志器模块是对前边所有模块的一个整合,所以Logger类管理的成员有:

  • 日志器名称(日志器的唯一标识);
  • 格式化模块对象(Formatter);
  • 落地模块对象数组(一个日志器可能会向多个位置进行日志输出);
  • 默认的输出限制等级(控制达到指定等级的日志才可以输出);
  • 互斥锁(保证日志输出过程是线程安全的,不会出现交叉日志);

Logger类提供的操作有:

  • debug等级日志的输出操作;
  • info等级日志的输出操作;
  • warn等级日志的输出操作;
  • error等级日志的输出操作;
  • fatal等级日志的输出操作;

当前日志系统支持同步日志和异步日志两种方式,两个不同的日志器唯一的区别是它们在日志落地方式上有所不同:

  • 同步日志器:直接对日志消息进行输出;
  • 异步日志器:将日志消息放入缓冲区,由异步线程进行输出。

因此日志器在设计的时候先设计一个Logger基类,在Logger基类的基础上继承出SyncLogger同步日志器AsyncLogger异步日志器

Logger类设计

  • debuginfo等接口在设计时,需要传递参数有文件名、行号、参数包。至于为什么要传递文件名与行号,因为要避免获取文件名和行号时是在本函数内部;
  • 将参数包进行内容提取后保存在字符串中,交由serialize进行处理;
  • serialize函数的功能是,将字符串中的内容进行日志消息格式化,并进行落地操作
class Logger
{
public:
    using ptr = std::shared_ptr<Logger>;

    Logger(const std::string &logger_name,
           LogLevel::value level,
           Formatter::ptr &formatter,
           std::vector<LogSink::ptr> &sinks) : 
           _logger_name(logger_name),
           _limit_level(level),
           _formatter(formatter),
           _sinks(sinks.begin(), sinks.end())
    {}
	// 获取日志器名称
    const std::string& name(){ return _logger_name; }
    
    void debug(const std::string &file, size_t line, const std::string &fmt, ...)
    {
        // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
        // 判断当前的日志是否达到了输出等级
        if (LogLevel::value::DEBUG < _limit_level)
        {
            return;
        }

        // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
        va_list ap;
        va_start(ap, fmt);
        char *res;
        int ret = vasprintf(&res, fmt.c_str(), ap);
        if (ret == 1)
        {
            std::cout << "vasprintf failed\n";
            return;
        }
        va_end(ap);
        serialize(LogLevel::value::DEBUG, file, line, res);
        free(res);
    }
    void info(const std::string &file, size_t line, const std::string &fmt, ...)
    {
        // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
        if (LogLevel::value::INFO < _limit_level)
        {
            return;
        }

        // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
        va_list ap;
        va_start(ap, fmt);
        char *res;
        int ret = vasprintf(&res, fmt.c_str(), ap);
        if (ret == 1)
        {
            std::cout << "vasprintf failed\n";
            return;
        }
        va_end(ap);
        serialize(LogLevel::value::INFO, file, line, res);
        free(res);
    }
    void warn(const std::string &file, size_t line, const std::string &fmt, ...)
    {
        // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
        if (LogLevel::value::WARN < _limit_level)
        {
            return;
        }

        // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
        va_list ap;
        va_start(ap, fmt);
        char *res;
        int ret = vasprintf(&res, fmt.c_str(), ap);
        if (ret == 1)
        {
            std::cout << "vasprintf failed\n";
            return;
        }
        va_end(ap);
        serialize(LogLevel::value::WARN, file, line, res);
        free(res);
    }
    void error(const std::string &file, size_t line, const std::string &fmt, ...)
    {
        // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
        if (LogLevel::value::ERROR < _limit_level)
        {
            return;
        }

        // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
        va_list ap;
        va_start(ap, fmt);
        char *res;
        int ret = vasprintf(&res, fmt.c_str(), ap);
        if (ret == 1)
        {
            std::cout << "vasprintf failed\n";
            return;
        }
        va_end(ap);
        serialize(LogLevel::value::ERROR, file, line, res);
        free(res);
    }
    void fatal(const std::string &file, size_t line, const std::string &fmt, ...)
    {
        // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
        if (LogLevel::value::FATAL < _limit_level)
        {
            return;
        }

        // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
        va_list ap;
        va_start(ap, fmt);
        char *res;
        int ret = vasprintf(&res, fmt.c_str(), ap);
        if (ret == 1)
        {
            std::cout << "vasprintf failed\n";
            return;
        }
        va_end(ap);
        serialize(LogLevel::value::FATAL, file, line, res);
        free(res);
    }

protected:
    void serialize(LogLevel::value level, const std::string &file, size_t line, char *str)
    {
        // 构造LogMsg对象
        LogMsg msg(level, line, file, _logger_name, str);
        // 通过格式化工具对LogMsg进行格式化, 得到格式化后的日志字符串
        std::stringstream ss;
        _formatter->format(ss, msg);
        // 对日志进行落地
        log(ss.str().c_str(), ss.str().size());
    }
    virtual void log(const char *data, size_t len) = 0;

protected:
    std::mutex _mutex;
    std::string _logger_name;                  // 日志器名称
    std::atomic<LogLevel::value> _limit_level; // 限制输出等级
    Formatter::ptr _formatter;
    std::vector<LogSink::ptr> _sinks; // 落地方向数组
};

同步日志器类设计

同步日志器设计较为简单,设计思想是:

  • 遍历日志落地数组,以数组中的各种落地方式进行落地操作;
class SyncLogger : public Logger
{
public:
    SyncLogger(const std::string &logger_name,
               LogLevel::value level,
               LOG::Formatter::ptr &formatter,
               std::vector<LogSink::ptr> &sinks) 
               : Logger(logger_name, level, formatter, sinks)
    {
    }

protected:
    void log(const char *data, size_t len)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        if (_sinks.empty())
            return;
        for (auto &sink : _sinks)
        {
            sink->log(data, len);
        }
    }
};

同步日志器测试

int main()
{
	LOG::LogMsg msg(LOG::LogLevel::value::INFO, 53, "main.cc", "root", "格式化功能测试...");
    LOG::Formatter fmt;
    std::string str = fmt.format(msg);
    LOG::LogSink::ptr time_lsp = LOG::SinkFactory::create<RollByTimeSink>("./logfile/roll-", TimeGap::GAP_SECOND);
    time_t old = LOG::util::Date::getTime();
    while(LOG::util::Date::getTime() < old + 5)
    {
        time_lsp->log(str.c_str(), str.size());
        sleep(1);
    }

    std::string logger_name = "sync_logger";
    LOG::LogLevel::value limit = LOG::LogLevel::value::WARN;
    LOG::Formatter::ptr fmt(new LOG::Formatter("[%d{%H:%M:%S}][%c][%f:%l][%p]%T%m%n"));
    LOG::LogSink::ptr stdout_lsp = LOG::SinkFactory::create<LOG::StdOutSink>();
    LOG::LogSink::ptr file_lsp = LOG::SinkFactory::create<LOG::FileSink>("./logfile/test.log");
    LOG::LogSink::ptr roll_lsp = LOG::SinkFactory::create<LOG::RollBySizeSink>("./logfile/test.log", 1024*1024);
    std::vector<LOG::LogSink> sinks = {stdout_lsp, file_lsp, roll_lsp};
    LOG::Logger::ptr logger(new LOG::SyncLogger(logger_name, limit, fmt, sinks));

    logger->debug(__FILE__, __LINE__, "%s", "测试日志");
    logger->info(__FILE__, __LINE__, "%s", "测试日志");
    logger->warn(__FILE__, __LINE__, "%s", "测试日志");
    logger->error(__FILE__, __LINE__, "%s", "测试日志");
    logger->fatal(__FILE__, __LINE__, "%s", "测试日志");

    size_t cursize = 0, count = 0;
    while(cursize < 1024*1024*10)
    {   
        logger->fatal(__FILE__, __LINE__, "测试日志-%d", count++);
        cursize+=20;
    }
	return 0;
}

日志器建造者模式设计

观察上一小节中的日志器测试代码,在构建一个同步日志器时,需要先设置很多的零部件。这对于用户来说未免有些繁琐

我们需要使用建造者模式来建造日志器,而不要让用户直接去构造日志器,以简化用户的使用复杂度

设计思想:

  • 抽象一个日志器建造者类
    • 设置日志器类型;
    • 将不同类型(同步&异步)日志器的创建放到同一个日志器建造者类中完成。
  • 派生出具体的建造者类----局部日志器建造者 & 全局日志器建造者类(后面添加了全局单例管理器之后,将日志器添加全局管理)。

抽象日志器建造者类

  • 建造者类中包含成员:
    • logger_type 日志器类型;
    • logger_name 日志器名称;
    • limit_level 日志输出限制等级;
    • formatter 格式化对象;
    • sinks 日志落地数组;
  • 还有构建各个零件的函数;
enum class LoggerType
{
    LOGGER_SYNC,
    LOGGER_ASYNC
};
// 1.抽象一个日志器建造者类(完成日志器所需零部件的构建 & 日志器的构建)
class LoggerBuilder
{
public:
    LoggerBuilder() : _logger_type(LoggerType::LOGGER_SYNC),
                      _limit_level(LogLevel::value::DEBUG)
    {}
    void buildLoggerType(LoggerType type) { _logger_type = type; }
    void buildLoggerName(const std::string &name) { _logger_name = name; }
    void buildLoggerLevel(LogLevel::value level) { _limit_level = level; }
    void buildFormatter(const std::string &pattern)
    {
        _formatter = std::make_shared<Formatter>(pattern);
    }
    template <typename SinkType, typename... Args>
    void buildSink(Args &&...args)
    {
        LogSink::ptr psink = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
        _sinks.push_back(psink);
    }
    virtual Logger::ptr build() = 0;

protected:
    LoggerType _logger_type;
    std::string _logger_name;
    std::atomic<LogLevel::value> _limit_level;
    Formatter::ptr _formatter;
    std::vector<LogSink::ptr> _sinks;
};

派生局部日志器建造者

/*2.派生出具体的建造者类---局部日志器的建造者 & 全局日志器的建造者*/
class LocalLoggerBuilder : public LoggerBuilder
{
public:
    Logger::ptr build() override
    {
        assert(_logger_name.empty() == false);
        if (_formatter.get() == nullptr)
        {
            _formatter = std::make_shared<Formatter>();
        }

        if (_sinks.empty())
        {
            buildSink<StdOutSink>();
        }

        if (_logger_type == LoggerType::LOGGER_ASYNC)
        {
            // 后面实现异步日志器后再完善...
        }
        return std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sinks);
    }
};

日志器建造者类测试

int main()
{
	std::unique_ptr<LOG::LoggerBuilder> builder(new LOG::GlobalLoggerBuilder());
    builder->buildLoggerName("sync_logger");
    builder->buildLoggerLevel(LOG::LogLevel::value::WARN);
    builder->buildFormatter("[%c][%f:%l]%m%n");
    builder->buildLoggerType(LOG::LoggerType::LOGGER_SYNC);
    builder->buildEnableUnSafeAsync();
    builder->buildSink<LOG::FileSink>("./logfile/async.log");
    builder->buildSink<LOG::StdOutSink>();
    LOG::Logger::ptr = builder->build();

    logger->debug(__FILE__, __LINE__, "%s", "测试日志");
    logger->info(__FILE__, __LINE__, "%s", "测试日志");
    logger->warn(__FILE__, __LINE__, "%s", "测试日志");
    logger->error(__FILE__, __LINE__, "%s", "测试日志");
    logger->fatal(__FILE__, __LINE__, "%s", "测试日志");

    size_t cursize = 0, count = 0;
    while(cursize < 1024*1024*10)
    {   
        logger->fatal(__FILE__, __LINE__, "测试日志-%d", count++);
        cursize+=20;
    }
	return 0;
}

同步日志器类与日志器建造者类整理

#ifndef __M_LOGGER_H__
#define __M_LOGGER_H__
#include "util.hpp"
#include "level.hpp"
#include "format.hpp"
#include "sink.hpp"
#include "looper.hpp"
#include <cstdarg>
#include <atomic>
#include <thread>
#include <mutex>
#include <unordered_map>

namespace LOG
{
    class Logger
    {
    public:
        using ptr = std::shared_ptr<Logger>;

        Logger(const std::string &logger_name,
               LogLevel::value level,
               Formatter::ptr &formatter,
               std::vector<LogSink::ptr> &sinks) : 
               _logger_name(logger_name),
               _limit_level(level),
               _formatter(formatter),
               _sinks(sinks.begin(), sinks.end())
        {
        }

        const std::string& name(){ return _logger_name; }
        void debug(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
            // 判断当前的日志是否达到了输出等级
            if (LogLevel::value::DEBUG < _limit_level)
            {
                return;
            }

            // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == 1)
            {
                std::cout << "vasprintf failed\n";
                return;
            }
            va_end(ap);
            serialize(LogLevel::value::DEBUG, file, line, res);
            free(res);
        }
        void info(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
            if (LogLevel::value::INFO < _limit_level)
            {
                return;
            }

            // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == 1)
            {
                std::cout << "vasprintf failed\n";
                return;
            }
            va_end(ap);
            serialize(LogLevel::value::INFO, file, line, res);
            free(res);
        }
        void warn(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
            if (LogLevel::value::WARN < _limit_level)
            {
                return;
            }

            // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == 1)
            {
                std::cout << "vasprintf failed\n";
                return;
            }
            va_end(ap);
            serialize(LogLevel::value::WARN, file, line, res);
            free(res);
        }
        void error(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
            if (LogLevel::value::ERROR < _limit_level)
            {
                return;
            }

            // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == 1)
            {
                std::cout << "vasprintf failed\n";
                return;
            }
            va_end(ap);
            serialize(LogLevel::value::ERROR, file, line, res);
            free(res);
        }
        void fatal(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
            if (LogLevel::value::FATAL < _limit_level)
            {
                return;
            }

            // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == 1)
            {
                std::cout << "vasprintf failed\n";
                return;
            }
            va_end(ap);
            serialize(LogLevel::value::FATAL, file, line, res);
            free(res);
        }

    protected:
        void serialize(LogLevel::value level, const std::string &file, size_t line, char *str)
        {
            // 构造LogMsg对象
            LogMsg msg(level, line, file, _logger_name, str);
            // 通过格式化工具对LogMsg进行格式化, 得到格式化后的日志字符串
            std::stringstream ss;
            _formatter->format(ss, msg);
            // 对日志进行落地
            log(ss.str().c_str(), ss.str().size());
        }
        virtual void log(const char *data, size_t len) = 0;

    protected:
        std::mutex _mutex;
        std::string _logger_name;                  // 日志器名称
        std::atomic<LogLevel::value> _limit_level; // 限制输出等级
        Formatter::ptr _formatter;
        std::vector<LogSink::ptr> _sinks;
    };

    class SyncLogger : public Logger
    {
    public:
        SyncLogger(const std::string &logger_name,
                   LogLevel::value level,
                   LOG::Formatter::ptr &formatter,
                   std::vector<LogSink::ptr> &sinks) 
                   : Logger(logger_name, level, formatter, sinks)
        {
        }

    protected:
        void log(const char *data, size_t len)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            if (_sinks.empty())
                return;
            for (auto &sink : _sinks)
            {
                sink->log(data, len);
            }
        }
    };
    // 1.抽象一个日志器建造者类(完成日志器所需零部件的构建 & 日志器的构建)
    //  1.设置日志器类型
    //  2.将不同类型的日志器的创建放到同一个日志器建造者类中完成
    enum class LoggerType
    {
        LOGGER_SYNC,
        LOGGER_ASYNC
    };

    class LoggerBuilder
    {
    public:
        LoggerBuilder() : _logger_type(LoggerType::LOGGER_SYNC),
                          _limit_level(LogLevel::value::DEBUG)
        {}
        void buildLoggerType(LoggerType type) { _logger_type = type; }
        void buildEnableUnSafeAsync() { _looper_type = AsyncType::ASYNC_UNSAFE; }
        void buildLoggerName(const std::string &name) { _logger_name = name; }
        void buildLoggerLevel(LogLevel::value level) { _limit_level = level; }
        void buildFormatter(const std::string &pattern)
        {
            _formatter = std::make_shared<Formatter>(pattern);
        }
        template <typename SinkType, typename... Args>
        void buildSink(Args &&...args)
        {
            LogSink::ptr psink = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
            _sinks.push_back(psink);
        }
        virtual Logger::ptr build() = 0;

    protected:
        LoggerType _logger_type;
        std::string _logger_name;
        std::atomic<LogLevel::value> _limit_level;
        Formatter::ptr _formatter;
        std::vector<LogSink::ptr> _sinks;
    };

    /*2.派生出具体的建造者类---局部日志器的建造者 & 全局日志器的建造者*/
    class LocalLoggerBuilder : public LoggerBuilder
    {
    public:
        Logger::ptr build() override
        {
            assert(_logger_name.empty() == false);
            if (_formatter.get() == nullptr)
            {
                _formatter = std::make_shared<Formatter>();
            }

            if (_sinks.empty())
            {
                buildSink<StdOutSink>();
            }

            if (_logger_type == LoggerType::LOGGER_ASYNC)
            {}
            return std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sinks);
        }
    };
}
#endif

在这里插入图片描述

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

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

相关文章

将阿里云盘挂载到本地磁盘-CloudDrive工具使用教程

CloudDrive是什么&#xff1f; 支持将115、沃家云盘、天翼云盘、阿里云盘、WebDAV挂载到本地并创建本地磁盘。 CloudDrive是一个全方位的云存储管理平台&#xff0c;旨在无缝集成多个云存储服务&#xff0c;将它们统一整合到一个界面中。 使用CloudDrive&#xff0c;您可以轻松…

Python 图形化界面基础篇:监听按钮点击事件

Python 图形化界面基础篇&#xff1a;监听按钮点击事件 引言 Tkinter 库简介步骤1&#xff1a;导入 Tkinter 模块步骤2&#xff1a;创建 Tkinter 窗口步骤3&#xff1a;创建按钮和定义事件处理函数步骤4&#xff1a;创建显示文本的标签步骤5&#xff1a;启动 Tkinter 主事件循环…

杂牌行车记录仪删除后覆盖恢复案例

行车记录仪从一开始的新鲜设备&#xff0c;到现在汽车必备&#xff0c;有的厂商甚至直接出厂就带了行车记录仪&#xff0c;正因为如此重要所以市场上充斥着很多记录仪品牌。下边我们来看看这个杂牌的记录仪恢复案例。 故障存储:8G microSD卡 故障现象: 8G算是小卡&#xff0…

pta java版

7-1 厘米换算英尺英寸 如果已知英制长度的英尺foot和英寸inch的值&#xff0c;那么对应的米是(footinch/12)0.3048。现在&#xff0c;如果用户输入的是厘米数&#xff0c;那么对应英制长度的英尺和英寸是多少呢&#xff1f;别忘了1英尺等于12英寸。 思路&#xff1a; 1英尺12英…

将近 5 万字讲解 Java Web / Servlet 网络编程超级详细概念原理知识点

1. Web 基本概念 首先 Web 网页 / 网站的意思&#xff08;例如&#xff1a;百度 www.baidu.com&#xff09; Web 分类&#xff1a;静态 Web / 动态 Web&#xff08;技术栈 Servlet / JSP、ASP、PHP&#xff09; 动态 web 在 java 中叫 javaweb BS (Browser / Server&#xff…

伙伴云连续2年入选Gartner《中国分析平台市场指南》,数据分析能力遥遥领先

伙伴云作为中国分析与商业智能平台代表性厂商&#xff0c;因出色的数据分析能力&#xff0c;入选Gartner2023《中国分析平台市场指南》&#xff08;《Market Guide for Analytics Platforms, China》&#xff0c;以下简称“指南”&#xff09;&#xff0c;成为入选该报告中唯一…

内外统一的边缘原生云基础设施架构——火山引擎边缘云

近日&#xff0c;火山引擎边缘云边缘计算架构师郭少巍在LiveVideoStack Con 2023上海站围绕火山引擎边缘云海量分布式节点和上百T带宽&#xff0c;结合边缘计算在云基础设施架构方面带来的挑战&#xff0c;分享了面对海量数据新的应用形态对低时延和分布式架构的需求&#xff0…

揭秘跑腿小程序开发中的5个关键技巧,让你的应用一炮而红

作为专注于跑腿小程序开发多年的领域专家&#xff0c;我深知在如今激烈的市场竞争中&#xff0c;如何打造一个引人注目且成功的跑腿小程序是至关重要的。在本文中&#xff0c;我将为大家揭秘跑腿小程序开发中的5个关键技巧&#xff0c;助你的应用一炮而红。无论你是一个初学者还…

解决Java类加载异常:java.lang.ClassNotFoundException

在Java开发过程中&#xff0c;有时会遇到类加载异常&#xff0c;其中之一是java.lang.ClassNotFoundException异常。这个异常通常出现在缺少相关依赖库或配置问题的情况下。本文将介绍如何解决这个问题&#xff0c;并以一个具体案例来说明。 问题描述 在开发过程中&#xff0…

CocosCreator3.8研究笔记(十五)CocosCreator 资源管理Asset Bundle

在资源管理模块中有一个很重要的功能&#xff1a; Asset Bundle&#xff0c;那什么是Asset Bundle &#xff1f;有什么作用&#xff1f;怎么使用 Asset Bundle呢 &#xff1f; 一、什么是Asset Bundle &#xff1f;有什么作用&#xff1f; 在日常游戏开发过程中&#xff0c;为了…

基于Android 语音朗读书籍管理系统

视频演示&#xff1a; 基于Android 语音朗读书籍管理系统 基于 Android 的语音朗读书籍管理系统可以提供用户管理书籍、朗读书籍的功能。以下是一个简单的步骤和功能列表&#xff1a; 用户注册和登录功能&#xff1a; 用户可以注册新账号或使用现有账号登录系统。用户信息可以包…

Redis从入门到精通(四:持久化)

持久化简介 什么是持久化 利用永久性存储介质将数据进行保存&#xff0c;在特定的时间将保存的数据进行恢复的工作机制称为持久化 持久化用于防止数据的意外丢失&#xff0c;确保数据安全性 为什么持久化 redis是将数据存储在内存上的&#xff0c;一旦断电&#xff0c;或者因…

嵌入式学习笔记(23)通信的基础概念

5.1.1通信的发展历史 &#xff08;1&#xff09;烽火台、狼烟&#xff1b;信件&#xff1b;电子通信&#xff08;电报、电话、网络信号&#xff09; &#xff08;2&#xff09;通信中最重要的两个方面&#xff1a;信息表示、解析方法 信息的传输方法 &#xff08;3&#xf…

04-前端基础CSS第二天

01-CSS第二天导读 目标&#xff1a; 能使用emmet语法能够使用CSS复合选择器能够写出伪类选择器的使用规范能够说出元素有几种显示模式能够写出元素显示模式的相互转换代码能够写出背景图片的设置方式能够计算CSS的权重 目录&#xff1a; Emmet语法CSS的复合选择器CSS的元素…

[.NET 6] IHostedService 的呼叫等等我的爱——等待Web应用准备就绪

📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!📢本文作者:由webmote 原创📢作者格言:新的征程,我们面对的不是技术而是人心,人心不可测,海水不可量,唯有技术,才是深沉黑夜中的一座闪烁的灯塔 !序言 在这篇文章中,我将介绍如何等…

搜狐验证码

嗯。。。。我又来了。百度这段时间老搞事情&#xff0c;做seo的兄弟们该何去何从。准备转战sh,xl&#xff0c;谷歌了。 先来看看搜狐两种验证类型。 滑块&#xff1a; 点选&#xff1a; 目前好像就只有这两种类型。感兴趣的可以去看看这个。不难。作者踩坑的地方。加密少了个…

【LeetCode-简单题】27. 移除元素

文章目录 题目方法一&#xff1a;快慢指针 题目 方法一&#xff1a;快慢指针 int fast 0;// 快指针 用于扫描需要的元素int slow 0;//慢指针 用于记录需要存放元素的位置class Solution { // 快慢指针public int removeElement(int[] nums, int val) {int fast 0;// 快指针…

Kubernetes Dashboard安装部署

Kubernetes Dashboard安装部署 1. 下载Dashboard 部署文件2. 修改yaml配置文件3. 应用安装&#xff0c;查看pod和svc4. 创建dashboard服务账户5. 创建admin-user用户的登录密钥6. 登录6.1 使用token登录(1) 短期token(2) token长期有效 6.2 使用 Kubeconfig 文件登录 7.安装met…

Linux内核4.14版本——drm框架分析(12)——DRM_IOCTL_MODE_SETCRTC(drm_mode_setcrtc)

目录 1. drm_mode_setcrtc 1.1 根据应用传入的crtc_id找到crtc 1.2 根据应用传入的fb_id,找到对应的drm_framebuffer 1.3 根据应用传入的mode&#xff0c;创建一个drm_display_mode 1.4 根据传入的set_connectors_ptr&#xff0c;找到驱动对应的connector 1.5 将以上信息…

【题解】2596. 检查骑士巡视方案

题解&#xff1a; class Solution {int n,m;bool st[100][100];int flag;int dx[8]{-1,-2,-2,-1,1,2,2,1};int dy[8]{-2,-1,1,2,2,1,-1,-2}; public:bool checkValidGrid(vector<vector<int>>& grid) {m grid.size();n grid[0].size();dfs(grid,0,0,0);ret…