C++项目实战——基于多设计模式下的同步异步日志系统-⑦-日志输出格式化类设计

news2024/11/24 14:03:34

文章目录

  • 专栏导读
  • 日志格式化类成员介绍
    • patrern
    • items
  • 格式化子项类的设计
      • 抽象格式化子项基类
      • 日志主体消息子项
      • 日志等级子项
      • 时间子项
        • localtime_r介绍
        • strftime介绍
      • 源码文件名子项
      • 源码文件行号子项
      • 线程ID子项
      • 日志器名称子项
      • 制表符子项
      • 换行符子项
      • 原始字符串子项
  • 日志格式化类的设计
    • 设计思想
    • 接口实现
      • Formatter
      • format
      • parsePattern
      • createItem
  • 日志输出格式化类整理

专栏导读

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

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

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

在这里插入图片描述

日志格式化类成员介绍

日志消息格式化类主要负责将日志消息进行格式化。类中包含以下成员:

patrern

  • pattern:保存格式化规则字符串
    • 默认日志输出格式[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n
    • %d 表示日期,包含子格式 {%H:%M:%S}
    • %T 表示缩进;
    • %t 表示线程ID;
    • %c 表示日志器名称;
    • %f 表示源码文件名;
    • %l 表示源码行号;
    • %p 表示日志级别;
    • %m 表示主体消息;
    • %n 表示换行;

格式化规则字符串控制了日志的输出格式。定义格式化字符,就是为了方便让用户自己决定以何种形式将日志消息进行输出。

例如,在默认输出格式下,输出的日志消息为:
在这里插入图片描述

items

  • std::vector< FormatItem::ptr > items:用于按序保存格式化字符串对应的格式化子项对象。
    • MsgFormatItem:表示要从LogMsg中取出有效载荷;
    • LevelFormatItem:表示要从LogMsg中取出日志等级;
    • LoggerFormatItem:表示要从LogMsg中取出日志器名称;
    • ThreadFormatItem:表示要从LogMsg中取出线程ID;
    • TimeFormatItem:表示要从LogMsg中取出时间戳并按照指定格式进行格式化;
    • FileFormatItem:表示要从LogMsg中取出源码所在文件名;
    • LineFormatItem:表示要从LogMsg中取出源码所在行号;
    • TabFormatItem:表示一个制表符缩进;
    • NLineFormatItem:表示一个换行;
    • OtherFormatItem:表示非格式化的原始字符串;

一个日志消息对象包含许多元素,如时间、线程ID、文件名等等,我们针对不同的元素设计不同的格式化子项。

换句话说,不同的格式化子项从日志消息对象中提取出指定元素,转换为规则字符串并按顺序保存在一块内存空间中。

/*
    %d 表示日期,包含子格式 {%H:%M:%S}
    %t 表示线程ID
    %c 表示日志器名称
    %f 表示源码文件名
    %l 表示源码行号
    %p 表示日志级别
    %m 表示主体消息
    %n 表示换行
*/
class Formatter
{
public:
	// ...
private:
    std::string _pattern; // 格式化规则字符串
    std::vector<FormatItem::ptr> _items;
};

格式化子项类的设计

刚才提到对于一条日志消息message,其中包含很多元素(时间、线程ID等)。我们通过设计不同的格式化子项来取出指定的元素,并将它们追加到一块内存空间中。

但是由于不同的格式化子项类对象类型也各不相同,我们就采用多态的思想,抽象出一个格式化子项基类,基于基类派生出不同的格式化子项类。这样就可以定义父类指针的数组,指向不同的格式化子项子类对象。

抽象格式化子项基类

class FormatItem
{
public:
    using ptr = std::shared_ptr<FormatItem>;
    virtual void format(std::ostream &out, const LogMsg &msg) = 0;
};
  • 在基类中定义一个智能指针对象,方便管理;
  • format的参数为一个IO流对象,一个LogMsg对象。作用为提取LogMsg对象中的指定元素追加到流对象中。

日志主体消息子项

class MsgFormatItem : public FormatItem
{
public:
    void format(std::ostream &out, const LogMsg &msg) override
    {
        out << msg._payload;
    }
};

日志等级子项

class MsgFormatItem : public FormatItem
{
public:
    void format(std::ostream &out, const LogMsg &msg) override
    {
        out << msg._payload;
    }
};

时间子项

class TimeFormatItem : public FormatItem
{
public:
    TimeFormatItem(const std::string &fmt = "%H:%M:%S") : _time_fmt(fmt) {}

    void format(std::ostream &out, const LogMsg &msg) override
    {
        struct tm t;
        localtime_r(&msg._ctime, &t);
        char tmp[32] = {0};
        strftime(tmp, 31, _time_fmt.c_str(), &t);
        out << tmp;
    }

private:
    std::string _time_fmt; // %H:%M:%S
};
  • 时间子项可以设置子格式,在构造函数中需要传递一个子格式字符串来控制时间子格式;

localtime_r介绍

在LogMsg对象中,时间元素是一个时间戳数字,不方便观察时间信息。我们需要将该时间戳转化为易于观察的时分秒的格式。

localtime_r函数是C标准库中的一个函数,用于将时间戳(表示自1970年1月1日以来的秒数)转换为本地时间的表示。这个函数是线程安全的版本,它接受两个参数:一个指向时间戳的指针和一个指向struct tm类型的指针,它会将转换后的本地时间信息存储在struct tm结构中。

函数原型如下:

struct tm *localtime_r(const time_t *timep, struct tm *result);
  • timep参数是指向时间戳的指针;
  • result参数是指向struct tm类型的指针,用于存储转换后的本地时间信息;
  • localtime_r函数返回一个指向struct tm结构的指针,同时也将结果存储在result参数中;
  • struct tm结构包含了年、月、日、时、分、秒等时间信息的成员变量,可用于格式化和输出时间;

struct tm类型
struct tm是C语言中的一个结构体类型,用于表示日期和时间的各个组成部分。

struct tm结构包含以下成员变量:

struct tm
{
	int tm_sec; // 秒(0-59)
	int tm_min; // 分钟(0-59)
	int tm_hour; // 小时(0-23)
	int tm_mday; // 一个月中的日期(1-31)
	int tm_mon; // 月份(0-11,0代表1月)
	int tm_year; // 年份(从1900年起的年数,例如,121表示2021年)
	int tm_wday; // 一周中的星期几(0-6,0代表星期日)
	int tm_yday; // 一年中的第几天(0-365)
	int tm_isdst; // 是否为夏令时(正数表示是夏令时,0表示不是,负数表示夏令时信息不可用)
}

strftime介绍

strftime函数是C标准库中的一个函数,用于将日期和时间按照指定的格式进行格式化,并将结果存储到一个字符数组中。这个函数在C语言中非常常用,特别是在需要将日期和时间以不同的格式输出到屏幕、文件或其他输出设备时。

函数原型如下:

size_t strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr);
  • s:一个指向字符数组的指针,用于存储格式化后的日期和时间字符串;
  • maxsize:指定了字符数组 s 的最大容量,以防止缓冲区溢出;
  • format:一个字符串,用于指定日期和时间的输出格式。该字符串可以包含- 各种格式化控制符,例如%Y表示年份,%m表示月份等等;
  • timeptr:一个指向struct tm 结构的指针,表示待格式化的日期和时间;

返回值:

  • strftime函数返回生成的字符数(不包括空终止符\0),如果生成的字符数大于 maxsize,则返回0,表示字符串无法完全存储在给定的缓冲区中。

源码文件名子项

class FileFormatItem : public FormatItem
{
public:
    void format(std::ostream &out, const LogMsg &msg) override
    {
        out << msg._file;
    }
};

源码文件行号子项

class LineFormatItem : public FormatItem
{
public:
    void format(std::ostream &out, const LogMsg &msg) override
    {
        out << msg._line;
    }
};

线程ID子项

class ThreadFormatItem : public FormatItem
{
public:
    void format(std::ostream &out, const LogMsg &msg) override
    {
        out << msg._tid;
    }
};

日志器名称子项

class LoggerFormatItem : public FormatItem
{
public:
    void format(std::ostream &out, const LogMsg &msg) override
    {
        out << msg._logger;
    }
};

制表符子项

class TabFormatItem : public FormatItem
{
public:
    void format(std::ostream &out, const LogMsg &msg) override
    {
        out << "\t";
    }
};

换行符子项

class NLineFormatItem : public FormatItem
{
public:
    void format(std::ostream &out, const LogMsg &msg) override
    {
        out << "\n";
    }
};

原始字符串子项

class OtherFormatItem : public FormatItem
{
public:
    OtherFormatItem(const std::string &str) : _str(str) {}
    void format(std::ostream &out, const LogMsg &msg) override
    {
        out << _str;
    }

private:
    std::string _str;
};

解释一下原始字符串:

  • 例如一个格式化字符串为[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n,其中[]:等符号是不属于上述任何子项的,这些符号不需要被解析,它们会作为日志内容被直接输出。

日志格式化类的设计

设计思想

日志格式化Formatter类中提供四个接口:

class Formatter
{
public:
    using ptr = std::shared_ptr<Formatter>;
    Formatter(const std::string &pattern = "[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n");
    void format(std::ostream &out, const LogMsg &msg);
    std::string format(const LogMsg &msg);
private:
    bool parsePattern();
    // 根据不同的格式化字符创建不同得格式化子项对象
    FormatItem::ptr createItem(const std::string &key, const std::string &val);
private:
    std::string _pattern; // 格式化规则字符串
    std::vector<FormatItem::ptr> _items;
};
  • Formatter:构造函数,构造一个formatter对象。函数参数为一个格式化字符串用来初始化成员pattern
  • format:提供两个重载函数,函数作用为将LogMsg中元素提取出来交由对应的格式化子项处理;可以将LogMsg进行格式化,并追加到流对象当中,也可以直接返回格式化后的字符串;
  • parsePattern:用于解析规则字符串_pattern
  • createItem:用于根据不同的格式化字符串创建不同的格式化子项对象

接口实现

Formatter

// 时间{年-月-日 时:分:秒}缩进 线程ID 缩进 [日志级别] 缩进 [日志名称] 缩进 文件名:行号 缩进 消息换行
Formatter(const std::string &pattern = "[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n")
    : _pattern(pattern)
{
    assert(parsePattern()); // 确保格式化字符串有效
}

format

// 对msg进行格式化
void format(std::ostream &out, const LogMsg &msg)
{
    for (auto &item : _items)
    {
        item->format(out, msg);
    }
}

std::string format(const LogMsg &msg)
{
    std::stringstream ss;
    format(ss, msg);
    return ss.str();
}

parsePattern

函数设计思想

  • 函数的主要逻辑是从前往后的处理格式化字符串。以默认格式化字符串"[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n"为例:
    • 从前往后遍历,如果没有遇到%则说明之前的字符都是原始字符串;
    • 遇到%,则看紧随其后的是不是另一个%,如果是,则认为%就是原始字符串;
    • 如果%后面紧挨着的是格式化字符(c、f、l、S等),则进行处理;
    • 紧随格式化字符之后,如果有{,则认为在{之后、}之前都是子格式内容;

在处理过程中,我们需要将得到的结果保存下来,于是我们可以创建一个vector,类型为一个键值对(key,val)。如果是格式化字符,则key为该格式化字符valnull;若为原始字符串keynullval原始字符串内容

得到数组之后,根据数组内容,调用createItem函数创建对应的格式化子项对象,添加到items成员中。

bool parsePattern()
{
    std::vector<std::pair<std::string, std::string>> fmt_order;
    size_t pos = 0;
    std::string key, val;
    while (pos < _pattern.size())
    {
        if (_pattern[pos] != '%')
        {
            val.push_back(_pattern[pos++]);
            continue;
        }

        if (pos + 1 < _pattern.size() && _pattern[pos + 1] == '%')
        {
            val.push_back('%');
            pos += 2;
            continue;
        }

        if (val.empty() == false)
        {
            fmt_order.push_back(std::make_pair("", val));
            val.clear();
        }

        pos += 1;
        if (pos == _pattern.size())
        {
            std::cout << "%之后没有格式化字符\n";
            return false;
        }

        key = _pattern[pos];
        pos += 1;

        if (pos < _pattern.size() && _pattern[pos] == '{')
        {
            pos += 1;
            while (pos < _pattern.size() && _pattern[pos] != '}')
            {
                val.push_back(_pattern[pos++]);
            }

            if (pos == _pattern.size())
            {
                std::cout << "子规则{}匹配出错\n";
                return false;
            }

            pos += 1;
        }

        fmt_order.push_back(std::make_pair(key, val));
        key.clear();
        val.clear();
    }

    for (auto &it : fmt_order)
    {
        _items.push_back(createItem(it.first, it.second));
    }
    return true;
}

createItem

  • 根据不同的格式化字符创建不同得格式化子项对象;
// 根据不同的格式化字符创建不同得格式化子项对象
FormatItem::ptr createItem(const std::string &key, const std::string &val)
{
    if (key == "d")
        return std::make_shared<TimeFormatItem>(val);
    if (key == "t")
        return std::make_shared<ThreadFormatItem>();
    if (key == "c")
        return std::make_shared<LoggerFormatItem>();
    if (key == "f")
        return std::make_shared<FileFormatItem>();
    if (key == "l")
        return std::make_shared<LineFormatItem>();
    if (key == "p")
        return std::make_shared<LevelFormatItem>();
    if (key == "T")
        return std::make_shared<TabFormatItem>();
    if (key == "m")
        return std::make_shared<MsgFormatItem>();
    if (key == "n")
        return std::make_shared<NLineFormatItem>();
    if (key == "")
        return std::make_shared<OtherFormatItem>(val);
    std::cout << "没有对应的格式化字符串:%" << key << std::endl;
    abort();
}

至此,日志消息格式化类已经全部实现完毕。


日志输出格式化类整理

#ifndef __M_FMT_H__
#define __M_FMT_H__

#include "level.hpp"
#include "message.hpp"
#include <vector>
#include <sstream>
#include <ctime>
#include <cassert>

namespace LOG
{
    // 抽象格式化子项基类
    class FormatItem
    {
    public:
        using ptr = std::shared_ptr<FormatItem>;
        virtual void format(std::ostream &out, const LogMsg &msg) = 0;
    };

    class MsgFormatItem : public FormatItem
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << msg._payload;
        }
    };

    class LevelFormatItem : public FormatItem
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << LogLevel::tostring(msg._level);
        }
    };

    class TimeFormatItem : public FormatItem
    {
    public:
        TimeFormatItem(const std::string &fmt = "%H:%M:%S") : _time_fmt(fmt) {}

        void format(std::ostream &out, const LogMsg &msg) override
        {
            struct tm t;
            localtime_r(&msg._ctime, &t);
            char tmp[32] = {0};
            strftime(tmp, 31, _time_fmt.c_str(), &t);
            out << tmp;
        }

    private:
        std::string _time_fmt; // %H:%M:%S
    };

    class FileFormatItem : public FormatItem
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << msg._file;
        }
    };

    class LineFormatItem : public FormatItem
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << msg._line;
        }
    };

    class ThreadFormatItem : public FormatItem
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << msg._tid;
        }
    };

    class LoggerFormatItem : public FormatItem
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << msg._logger;
        }
    };

    class TabFormatItem : public FormatItem
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << "\t";
        }
    };

    class NLineFormatItem : public FormatItem
    {
    public:
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << "\n";
        }
    };

    class OtherFormatItem : public FormatItem
    {
    public:
        OtherFormatItem(const std::string &str) : _str(str) {}
        void format(std::ostream &out, const LogMsg &msg) override
        {
            out << _str;
        }

    private:
        std::string _str;
    };

    /*
        %d 表示日期,包含子格式 {%H:%M:%S}
        %t 表示线程ID
        %c 表示日志器名称
        %f 表示源码文件名
        %l 表示源码行号
        %p 表示日志级别
        %m 表示主体消息
        %n 表示换行
    */
    class Formatter
    {
    public:
        using ptr = std::shared_ptr<Formatter>;
        // 时间{年-月-日 时:分:秒}缩进 线程ID 缩进 [日志级别] 缩进 [日志名称] 缩进 文件名:行号 缩进 消息换行
        Formatter(const std::string &pattern = "[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n")
            : _pattern(pattern)
        {
            assert(parsePattern());
        }

        // 对msg进行格式化
        void format(std::ostream &out, const LogMsg &msg)
        {
            for (auto &item : _items)
            {
                item->format(out, msg);
            }
        }

        std::string format(const LogMsg &msg)
        {
            std::stringstream ss;
            format(ss, msg);
            return ss.str();
        }

    private:
        // 解析格式化字符串
        bool parsePattern()
        {
            std::vector<std::pair<std::string, std::string>> fmt_order;
            size_t pos = 0;
            std::string key, val;
            while (pos < _pattern.size())
            {
                if (_pattern[pos] != '%')
                {
                    val.push_back(_pattern[pos++]);
                    continue;
                }

                if (pos + 1 < _pattern.size() && _pattern[pos + 1] == '%')
                {
                    val.push_back('%');
                    pos += 2;
                    continue;
                }

                if (val.empty() == false)
                {
                    fmt_order.push_back(std::make_pair("", val));
                    val.clear();
                }

                pos += 1;
                if (pos == _pattern.size())
                {
                    std::cout << "%之后没有格式化字符\n";
                    return false;
                }

                key = _pattern[pos];
                pos += 1;

                if (pos < _pattern.size() && _pattern[pos] == '{')
                {
                    pos += 1;
                    while (pos < _pattern.size() && _pattern[pos] != '}')
                    {
                        val.push_back(_pattern[pos++]);
                    }

                    if (pos == _pattern.size())
                    {
                        std::cout << "子规则{}匹配出错\n";
                        return false;
                    }

                    pos += 1;
                }

                fmt_order.push_back(std::make_pair(key, val));
                key.clear();
                val.clear();
            }

            for (auto &it : fmt_order)
            {
                _items.push_back(createItem(it.first, it.second));
            }
            return true;
        }

        // 根据不同的格式化字符创建不同得格式化子项对象
        FormatItem::ptr createItem(const std::string &key, const std::string &val)
        {
            if (key == "d")
                return std::make_shared<TimeFormatItem>(val);
            if (key == "t")
                return std::make_shared<ThreadFormatItem>();
            if (key == "c")
                return std::make_shared<LoggerFormatItem>();
            if (key == "f")
                return std::make_shared<FileFormatItem>();
            if (key == "l")
                return std::make_shared<LineFormatItem>();
            if (key == "p")
                return std::make_shared<LevelFormatItem>();
            if (key == "T")
                return std::make_shared<TabFormatItem>();
            if (key == "m")
                return std::make_shared<MsgFormatItem>();
            if (key == "n")
                return std::make_shared<NLineFormatItem>();
            if (key == "")
                return std::make_shared<OtherFormatItem>(val);
            std::cout << "没有对应的格式化字符串:%" << key << std::endl;
            abort();
        }

    private:
        std::string _pattern; // 格式化规则字符串
        std::vector<FormatItem::ptr> _items;
    };
}

#endif

在这里插入图片描述

⭐本期赠书⭐

《数据结构与算法分析》
《计算机网络:自顶向下法》
《现代操作系统》
《深入理解计算机系统》
参与方式:评论区回复“C永不落幕”

在这里插入图片描述

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

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

相关文章

2100. 适合打劫银行的日子;2080. 区间内查询数字的频率;1774. 最接近目标价格的甜点成本

2100. 适合打劫银行的日子 核心思想&#xff1a;枚举前后缀。我们只需要预处理到第i天的前后缀&#xff0c;然后枚举一边i即可。最开始的时候我是分开求前后缀的&#xff0c;这里可以把前后缀一起进行处理。 2080. 区间内查询数字的频率 核心思想&#xff1a;二分。其实有一种…

Postman抓包网页请求

安装postman Interceptor谷歌插件 1.点击软件右下角Capture 2.启用代理 设置端口 3.手动点开谷歌插件&#xff0c;开始获取 4.获取结果可以点击进入详情页&#xff0c;查看接口信息

通过Python行命令搭建HTTP服务器结合内网穿透实现外网访问

文章目录 1.前言2.本地http服务器搭建2.1.Python的安装和设置2.2.Python服务器设置和测试 3.cpolar的安装和注册3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 Python作为热度比较高的编程语言&#xff0c;其语法简单且语句清晰&#xff0c;而且python有…

Vue项目中全局变量process的用法解析

一、什么是process process对象是一个全局变量&#xff0c;提供了有关当前Node.js进程的信息并对其进行控制。常用于Vue项目中环境区分&#xff0c;对不同环境的配置不同&#xff0c;例如&#xff1a;根据全局变量区分请求的url地址、是否开始eslint、不同环境的特殊配置等等。…

AiDB: 一个集合了6大推理框架的AI工具箱 | 加速你的模型部署

首发于GiantPandaCV公众号 项目地址: https://github.com/TalkUHulk/ai.deploy.box 网页体验: https://www.hulk.show/aidb-webassembly-demo/ PC: https://github.com/TalkUHulk/aidb_qt_demo Android: https://github.com/TalkUHulk/aidb_android_demo Go Server: https://gi…

[ Linux Audio 篇 ] 音频软件 Audacity 指导手册

音频软件 Audacity 指导手册 背景修订记录双声道提取到单声道查看频响特性查看增益导出Raw数据与作者沟通 背景 对于经常调试音频的开发人员来说&#xff0c;使用Audacity分析音频数据已经成为家常便饭。尤其对于日常使用Ubuntu / Linux等操作系统的开发人员来说&#xff0c;拥…

从构建者到设计者的低代码之路

低代码开发技术&#xff0c;是指无需编码或通过少量代码就可以快速生成应用程序的工具&#xff0c;一方面可降低企业应用开发人力成本和对专业软件人才的需求&#xff0c;另一方面可将原有数月甚至数年的开发时间成倍缩短&#xff0c;帮助企业实现降本增效、灵活迭代。那么&…

华为HCIA学习(一)

文章目录 一.根据考试题总结知识点&#xff08;一题一点&#xff09;二.上午学习三.下午学习四.今天只做了70题&#xff0c;需要的可以找我 一.根据考试题总结知识点&#xff08;一题一点&#xff09; 二.上午学习 ① VRP系统是VRP是华为公司从低端到高端的全系列路由器、交换…

MySQL数据库20G数据迁移至其他服务器的MySQL库或者云MySQL库

背景&#xff1a;20G的MySQL数据迁移至火山云MySQL库&#xff0c;使用navicat的数据传输工具迁移速度耗费时间过长。 方案一&#xff1a;使用火山云提供的MySQL数据迁移服务&#xff08;其他大厂应该提供的也有&#xff09; 方案二&#xff1a;使用数据迁移工具kettle&#x…

【记录贴】使用项目管理软件管理大型复杂项目是种什么体验?

随着手上的几个项目陆续验收交付&#xff0c;现在我又接了一个新项目&#xff0c;这次是一个中大型的软件开发项目。大型项目具有规模大、周期长、团队成员构成复杂、影响因素多等特征&#xff0c;所以我在项目推进过程中遇到了很多困难&#xff0c;想跟大家分享交流下&#xf…

深圳唯创知音电子将参加IOTE 2023第二十届国际物联网展•深圳站

​ 2023年9月20~22日&#xff0c;深圳唯创知音电子将在 深圳宝安国际会展中心&#xff08;9号馆9B1&#xff09;为您全面展示最新的芯片产品及应用方案&#xff0c;助力传感器行业的发展。 作为全球领先的芯片供应商之一&#xff0c;深圳唯创知音电子一直致力于为提供高质量、…

Python基于Flask的高校舆情分析,舆情监控可视化系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W,Csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 运行效果图 基于Python的微博大数据舆情分析&#xff0c;舆论情感分析可视化系统 系统介绍 微博舆情分析系…

Pytest系列-内置标签skip和skipif 跳过测试用例的详细使用(5)

简介 skip和skipif&#xff0c;见名知意就是跳过测试&#xff0c;主要用于不想执行的代码&#xff0c;标记后&#xff0c;标记的代码不执行。希望满足某些条件才执行某些测试用例&#xff0c;否则pytest会跳过运行该测试用例实际常见场景&#xff1a;根据平台不同执行测试、跳…

互联网医院系统|互联网医院监管平台的作用

互联网医院系统已经逐渐成为了人们就医、看病、买药等方面的重要选择。而这种新型医疗模式对传统医疗行业带来了巨大的冲击和变革。那么互联网医院系统为什么要对接监管平台呢&#xff1f;接下来小编就给大家介绍下。 一、政策必要性 根据《互联网医院管理办法&#xff08;试行…

【Linux环境】基础开发工具的使用:yum软件安装、vim编辑器的使用

​&#x1f47b;内容专栏&#xff1a; Linux操作系统基础 &#x1f428;本文概括&#xff1a; yum软件包管理、vim编辑器的使用。 &#x1f43c;本文作者&#xff1a; 阿四啊 &#x1f438;发布时间&#xff1a;2023.9.12 Linux软件包管理 yum 什么是软件包 在Linux下安装软件…

vue2+three.js实现宇宙(进阶版)

2023.9.12今天我学习了vue2three.js实现一个好看的动态效果&#xff1a; 首先是安装&#xff1a; npm install three 相关代码如下&#xff1a; <!--3d宇宙效果--> <template><div><div id"content" /></div> </template> <…

无涯教程-JavaScript - RATE函数

描述 RATE函数返回年金每个周期的利率。 RATE通过迭代计算得出,可以有零个或多个解。如果RATE的连续输出在20次迭代后未收敛到0.0000001以内,则RATE返回#NUM!错误值。 语法 RATE (nper, pmt, pv, [fv], [type], [guess])有关参数nper,pmt,pv,fv和type的完整说明,请参见PV Fu…

二刷力扣--链表

链表 链表类型&#xff1a; 单链表&#xff08;可以访问后面的一个节点&#xff09; 双链表&#xff08;可以访问前后节点&#xff09; 循环链表&#xff08;最后一个节点指向首节点&#xff09; 在Python中定义单链表节点&#xff1a; class ListNode:def __init__(self, v…

TypeScript泛型

什么是泛型&#xff1f; "泛"就是广泛的意思&#xff0c;"型"就是数据类型。顾名思义&#xff0c;泛型就是适用于多种数据类型的一种类型。 泛型的作用 它能够帮助我们构建出复用性更强的代码 function getResult(value: number): number {return value…

高效办公必备,批量重命名与翻译一气呵成

在电脑使用中&#xff0c;我们常常需要批量修改文件名或对文件进行翻译。这时候&#xff0c;有一个得力的工具可以助你一臂之力&#xff0c;那就是“固乔文件管家”。下面就教你如何使用这个软件&#xff0c;轻松完成批量重命名和翻译大量文件的操作。 首先&#xff0c;你需要下…