当前位置: 首页 > news >正文

C++项目实战——基于多设计模式下的同步异步日志系统-⑧-日志落地类设计

文章目录

  • 专栏导读
  • 抽象基类
  • StdoutSink类设计
  • FileSink类设计
  • RollBySizeSink类设计
  • 日志落地工厂类设计
  • 日志落地类整理
  • 日志落地拓展测试
    • RollByTimeSink类设计
    • 测试代码
    • 测试完整代码

专栏导读

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

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

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

在这里插入图片描述

日志落地类主要负责将日志消息输出到指定的位置。目前实现了三个日志落地方向:

  • 标准输出:StdoutSink;
  • 固定文件:FileSink;
  • 滚动文件(文件按照时间/大小进行滚动切换):RollSink;

同时,日志落地类还应提供可扩展落地方向的功能。用户可以自己编写一个新的落地模块,将日志进行其他方向的落地。

  • 实现思想:
    • 抽象出落地模块类
    • 不同落地方向从基类进行派生
    • 使用工厂模式进行创建与表示分离

抽象基类

  • 提供一个智能指针对象方便管理;
  • 将日志输函数log作与析构函数设置为虚函数
class LogSink
{
public:using ptr = std::shared_ptr<LogSink>;LogSink() {}virtual ~LogSink() {}virtual void log(const char *data, size_t len) = 0;
};

StdoutSink类设计

// 落地方向:标准输出
class StdOutSink : public LogSink
{
public:void log(const char *data, size_t len){std::cout.write(data, len);}
};

FileSink类设计

类中包含两个成员:

  • pathname:文件名,用来指定日志消息输出到哪个文件;
  • ofs:文件输出类对象,进行输出操作;

在C++中,ofstream 是用于文件输出的类,它是 C++ 标准库中的一部分,通常与 ifstream(用于文件输入)一起使用。ofstream 类允许你创建、打开、写入和关闭文本文件。你可以使用它来将数据写入文件,例如文本、数字或二进制数据。

// 落地方向:指定文件
class FileSink : public LogSink
{
public:FileSink(const std::string &pathname):_pathname(pathname){// 1.创建日志文件所在目录util::File::createDirectory(util::File::path(pathname));// 2.创建并打开文件_ofs.open(_pathname, std::ios::binary | std::ios::app);assert(_ofs.is_open());}void log(const char *data, size_t len){_ofs.write(data, len);assert(_ofs.good());}
private:std::string _pathname;std::ofstream _ofs;
};

RollBySizeSink类设计

日志文件滚动的条件有两个:文件大小和时间。我们可以选择:

  • 日志文件在大于1GB的时候会更换新的文件;
  • 每天定点滚动一个日志文件。

本项目基于文件大小的判断滚动生成新的文件。

滚动文件输出的必要性

  • 由于磁盘空间有限,我们不可能一直无限的向一个文件中增加数据;
  • 如果一个日志文件的体积太大,一方面是不好打开,另一方面是即使打开了,由于包含数据巨大,也不利于查找我们需要的信息;
  • 所以实际开发中会对单个日志文件的大小也会做一些限制,即当大小超过了某个大小时(如1GB),我们就重新创建一个新的日志文件来滚动写日志。对于那些过期的文件,大部分企业内都有专门的运维人员去定时清理过期的日志文件,或者设置定时任务,定时清理过期日志。
// 落地方向:滚动文件
class RollBySizeSink : public LogSink
{
public:RollBySizeSink(const std::string &basename, size_t max_size): _basename(basename),_max_fsize(max_size),_cur_fsize(0),_name_count(0){std::string pathname = createNewFile();// 1.创建日志文件所在的目录util::File::createDirectory(util::File::path(pathname));// 2.创建并打开日志文件_ofs.open(pathname, std::ios::binary | std::ios::app);assert(_ofs.is_open());}void log(const char *data, size_t len){if (_cur_fsize >= _max_fsize){_ofs.close(); // 关闭原来已经打开的文件std::string pathname = createNewFile();_ofs.open(pathname, std::ios::binary | std::ios::app);assert(_ofs.is_open());_cur_fsize = 0;}_ofs.write(data, len);assert(_ofs.good());_cur_fsize += len;}private:// 切换文件后,以时间格式创建新的文件名std::string createNewFile(){time_t t = util::Date::getTime();struct tm lt;localtime_r(&t, &lt);std::stringstream filename;filename << _basename;filename << lt.tm_year + 1900;filename << lt.tm_mon + 1;filename << lt.tm_mday;filename << lt.tm_hour;filename << lt.tm_min;filename << lt.tm_sec;filename << "-";filename << _name_count++;filename << ".log";return filename.str();}private:size_t _name_count;std::string _basename;std::ofstream _ofs;size_t _max_fsize; // 日志文件最大大小size_t _cur_fsize; // 已经写入的文件大小
};

日志落地工厂类设计

  • 为了避免用户将来实现自己的落地方向时需要修改源代码,这违背了开闭原则,所以我们采用工厂类的设计;
  • 由于不同的落地方向如StdoutSinkFileSinkRollBySizeSink,它们各自的构造函数所需参数并不相同,无法统一的管理,所以我们采用参数包的方式来解决。
class SinkFactory
{
public:template <typename SinkType, typename... Args>static LogSink::ptr create(Args &&...args){return std::make_shared<SinkType>(std::forward<Args>(args)...);}
};

日志落地类整理

#ifndef __M_SINK_H__
#define __M_SINK_H__#include "util.hpp"
#include <memory>
#include <sstream>
#include <fstream>
#include <cassert>namespace LOG
{class LogSink{public:using ptr = std::shared_ptr<LogSink>;LogSink() {}virtual ~LogSink() {}virtual void log(const char *data, size_t len) = 0;};// 落地方向:标准输出class StdOutSink : public LogSink{public:void log(const char *data, size_t len){std::cout.write(data, len);}};// 落地方向:指定文件class FileSink : public LogSink{public:FileSink(const std::string &pathname):_pathname(pathname){// 1.创建日志文件所在目录util::File::createDirectory(util::File::path(pathname));// 2.创建并打开文件_ofs.open(_pathname, std::ios::binary | std::ios::app);assert(_ofs.is_open());}void log(const char *data, size_t len){_ofs.write(data, len);assert(_ofs.good());}private:std::string _pathname;std::ofstream _ofs;};// 落地方向:滚动文件class RollBySizeSink : public LogSink{public:RollBySizeSink(const std::string &basename, size_t max_size): _basename(basename),_max_fsize(max_size),_cur_fsize(0),_name_count(0){std::string pathname = createNewFile();// 1.创建日志文件所在的目录util::File::createDirectory(util::File::path(pathname));// 2.创建并打开日志文件_ofs.open(pathname, std::ios::binary | std::ios::app);assert(_ofs.is_open());}void log(const char *data, size_t len){if (_cur_fsize >= _max_fsize){_ofs.close(); // 关闭原来已经打开的文件std::string pathname = createNewFile();_ofs.open(pathname, std::ios::binary | std::ios::app);assert(_ofs.is_open());_cur_fsize = 0;}_ofs.write(data, len);assert(_ofs.good());_cur_fsize += len;}private:std::string createNewFile(){time_t t = util::Date::getTime();struct tm lt;localtime_r(&t, &lt);std::stringstream filename;filename << _basename;filename << lt.tm_year + 1900;filename << lt.tm_mon + 1;filename << lt.tm_mday;filename << lt.tm_hour;filename << lt.tm_min;filename << lt.tm_sec;filename << "-";filename << _name_count++;filename << ".log";return filename.str();}private:size_t _name_count;std::string _basename;std::ofstream _ofs;size_t _max_fsize; // 日志文件最大大小size_t _cur_fsize; // 已经写入的文件大小};class SinkFactory{public:template <typename SinkType, typename... Args>static LogSink::ptr create(Args &&...args){return std::make_shared<SinkType>(std::forward<Args>(args)...);}};
}
#endif

日志落地拓展测试

本小节主要内容为测试日志落地类是否支持扩展功能。我们新增一个基于时间的滚动文件类RollByTimeSink

RollByTimeSink类设计

#include <unistd.h>enum class TimeGap
{GAP_SECOND,GAP_MINUTE,GAP_HOUR,GAP_DAY
};class RollByTimeSink : public LOG::LogSink
{
public:// 构造时传入文件名,并打开文件,将操作句柄管理起来RollByTimeSink(const std::string &basename, TimeGap gap_type) : _basename(basename){switch(gap_type){case TimeGap::GAP_SECOND: _gap_size = 1; break;case TimeGap::GAP_MINUTE: _gap_size = 60; break;case TimeGap::GAP_HOUR: _gap_size = 3600; break;case TimeGap::GAP_DAY: _gap_size = 3600 * 24; break;}_cur_gap = _gap_size == 1? LOG::util::Date::getTime() : LOG::util::Date::getTime() % _gap_size; // 获取当前是第几个时间段std::string filename = createNewFile();LOG::util::File::createDirectory(LOG::util::File::path(filename));_ofs.open(filename, std::ios::binary | std::ios::app);assert(_ofs.is_open());}// 将日志消息写入到标准输出,判断当前时间是否是当前文件的时间段,不是则切换文件void log(const char* data, size_t len){time_t cur = LOG::util::Date::getTime();if((cur % _gap_size) != _cur_gap){_ofs.close();std::string filename = createNewFile();_ofs.open(filename, std::ios::binary | std::ios::app);assert(_ofs.is_open());}_ofs.write(data, len);assert(_ofs.good());}
private:std::string createNewFile(){time_t t = LOG::util::Date::getTime();struct tm lt;localtime_r(&t, &lt);std::stringstream filename;filename << _basename;filename << lt.tm_year + 1900;filename << lt.tm_mon + 1;filename << lt.tm_mday;filename << lt.tm_hour;filename << lt.tm_min;filename << lt.tm_sec;filename << "-";filename << ".log";return filename.str();}
private:std::string _basename;size_t _gap_size; // 时间段的大小int _cur_gap; // 当前是第几个时间段std::ofstream _ofs;
};

测试代码

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);}
}

测试结果
在这里插入图片描述

测试完整代码

test.cc

#include "LogSink.hpp"
#include "util.hpp"
#include <unistd.h>enum class TimeGap
{GAP_SECOND,GAP_MINUTE,GAP_HOUR,GAP_DAY
};class RollByTimeSink : public LOG::LogSink
{
public:// 构造时传入文件名,并打开文件,将操作句柄管理起来RollByTimeSink(const std::string &basename, TimeGap gap_type) : _basename(basename){switch(gap_type){case TimeGap::GAP_SECOND: _gap_size = 1; break;case TimeGap::GAP_MINUTE: _gap_size = 60; break;case TimeGap::GAP_HOUR: _gap_size = 3600; break;case TimeGap::GAP_DAY: _gap_size = 3600 * 24; break;}_cur_gap = _gap_size == 1? LOG::util::Date::getTime() : LOG::util::Date::getTime() % _gap_size; // 获取当前是第几个时间段std::string filename = createNewFile();LOG::util::File::createDirectory(LOG::util::File::path(filename));_ofs.open(filename, std::ios::binary | std::ios::app);assert(_ofs.is_open());}// 将日志消息写入到标准输出,判断当前时间是否是当前文件的时间段,不是则切换文件void log(const char* data, size_t len){time_t cur = LOG::util::Date::getTime();if((cur % _gap_size) != _cur_gap){_ofs.close();std::string filename = createNewFile();_ofs.open(filename, std::ios::binary | std::ios::app);assert(_ofs.is_open());}_ofs.write(data, len);assert(_ofs.good());}
private:std::string createNewFile(){time_t t = LOG::util::Date::getTime();struct tm lt;localtime_r(&t, &lt);std::stringstream filename;filename << _basename;filename << lt.tm_year + 1900;filename << lt.tm_mon + 1;filename << lt.tm_mday;filename << lt.tm_hour;filename << lt.tm_min;filename << lt.tm_sec;filename << "-";filename << ".log";return filename.str();}
private:std::string _basename;size_t _gap_size; // 时间段的大小int _cur_gap; // 当前是第几个时间段std::ofstream _ofs;
};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);}
}

在这里插入图片描述

相关文章:

C++项目实战——基于多设计模式下的同步异步日志系统-⑧-日志落地类设计

文章目录 专栏导读抽象基类StdoutSink类设计FileSink类设计RollBySizeSink类设计日志落地工厂类设计日志落地类整理日志落地拓展测试RollByTimeSink类设计测试代码测试完整代码 专栏导读 &#x1f338;作者简介&#xff1a;花想云 &#xff0c;在读本科生一枚&#xff0c;C/C领…...

从零开始探索C语言(八)----指针

文章目录 1. 什么是指针&#xff1f;2. 如何使用指针&#xff1f;3. NULL 指针4. 指针的算术运算5. 指针数组6. 指向指针的指针7. 传递指针给函数8. 从函数返回指针 有人说&#xff0c;指针是C语言的灵魂&#xff0c;所以学习C语言&#xff0c;学习指针是很有必要的。 通过指针…...

SpringMVC 的三种异常处理方式详解

目录 1. 什么是异常 2. 为什么要全局异常处理 3. SpringMVC异常分类 4. 异常处理思路 5. 三种异常处理方式示例 ① 配置 SimpleMappingExceptionResolver 处理器 ② 实现 HandlerExceptionResolver 接口 ③ 使用ControllerAdviceExceptionHandler实现全局异常 6. 响应…...

莫比乌斯召回系统介绍

当前召回系统只能召回相关性高的广告&#xff0c;但不能保证该广告变现能力强。莫比乌斯做了如下两点创新&#xff1a; 在召回阶段&#xff0c;引入CPM等业务指标作为召回依据在召回阶段&#xff0c;引入CTR模型&#xff0c;从而召回更多相关性高且变现能力强的广告 参考 百度…...

使用ASM修改组件化 ARouter

工程目录图 1. apt生成的字节码文件 2. asm 生成的代码 请点击下面工程名称&#xff0c;跳转到代码的仓库页面&#xff0c;将工程 下载下来 Demo Code 里有详细的注释 代码&#xff1a;TestCompont...

第21章_瑞萨MCU零基础入门系列教程之事件链接控制器ELC

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…...

(二十八)大数据实战——Flume数据采集之kafka数据生产与消费集成案例

前言 本节内容我们主要介绍一下flume数据采集和kafka消息中间键的整合。通过flume监听nc端口的数据&#xff0c;将数据发送到kafka消息的first主题中&#xff0c;然后在通过flume消费kafka中的主题消息&#xff0c;将消费到的消息打印到控制台上。集成使用flume作为kafka的生产…...

vue3:22、vue-router的使用

import { createRouter, createWebHistory } from vue-router//history模式&#xff1a;createWebHistory //hash模式&#xff1a;createWebHashHistory//vite中的环境变量 import.meta.env.BASE_URL 就是vite.config.js中的base配置项 const router createRouter({history:…...

深入理解JVM虚拟机第五篇:一些常用的JVM虚拟机(二)

文章目录 一&#xff1a;JRockit VM的介绍 二&#xff1a;J9 VM的介绍 三&#xff1a;KVM和CDC/CLDC Hotspot 四&#xff1a;Azul VM的介绍 五&#xff1a;Liquid VM的介绍 六&#xff1a;Apache Harmoney 七&#xff1a;Microsoft JVM 八&#xff1a;Taobao JVM 九&a…...

导数公式及求导法则

目录 基本初等函数的导数公式 求导法则 有理运算法则 复合函数求导法 隐函数求导法 反函数求导法 参数方程求导法 对数求导法 基本初等函数的导数公式 基本初等函数的导数公式包括&#xff1a; C0(x^n)nx^(n-1)(a^x)a^x*lna(e^x)e^x(loga(x))1/(xlna)(lnx)1/x(sinx)cos…...

SpringMVC系列(六)之JSON数据返回以及异常处理机制

目录 前言 一. JSON概述 二. JSON数据返回 1. 导入pom依赖 2. 添加配置文件&#xff08;spring-mvc.xml&#xff09; 3. ResponseBody注解使用 4. 效果展示 5. Jackson介绍 三. 全局异常处理 1. 为什么要全局异常处理 2. 异常处理思路 3. 异常处理方式一 4. 异常处…...

民安智库(北京第三方窗口测评)开展汽车消费者焦点小组座谈会调查

民安智库近日开展了一场汽车消费者焦点小组座谈会&#xff0c;旨在深入了解目标消费者对汽车功能的需求和消费习惯&#xff0c;为汽车企业提供有针对性的解决方案。 在焦点小组座谈会中&#xff0c;民安智库公司&#xff08;第三方市容环境指数测评&#xff09;邀请了一群具有…...

【CVPR2021】MVDNet论文阅读分析与总结

Challenge&#xff1a; 现有的目标检测器主要融合激光雷达和相机&#xff0c;通常提供丰富和冗余的视觉信息 利用最先进的成像雷达&#xff0c;其分辨率比RadarNet和LiRaNet中使用的分辨率要细得多&#xff0c;提出了一种有效的深度后期融合方法来结合雷达和激光雷达信号。 MV…...

IDEA指定Maven settings file文件未生效

背景&#xff1a;在自己电脑上配置的时候&#xff0c;由于公司项目和我自己的项目的Maven仓库不一致&#xff0c;我就在项目中指定了各自的Maven配置文件。但是我发现公司的项目私有仓库地址IDEA总是识别不到&#xff01; 俩个配置文件分别是&#xff1a; /Users/sml/Mine/研发…...

swift UI 和UIKIT 如何配合使用

SwiftUI和UIKit可以在同一个iOS应用程序中配合使用。它们是两个不同的用户界面框架&#xff0c;各自有自己的优势和特点。在现实开发中&#xff0c;很多iOS应用程序并不是一开始就完全采用SwiftUI或UIKit&#xff0c;而是根据需要逐步引入SwiftUI或者使用两者共存。 SwiftUI的…...

c语言练习题55:IP 地址⽆效化

IP 地址⽆效化 题⽬描述&#xff1a; 给你⼀个有效的 IPv4 地址 address &#xff0c;返回这个 IP 地址的⽆效化版本。 所谓⽆效化 IP 地址&#xff0c;其实就是⽤ "[.]" 代替了每个 "."。 • ⽰例 1&#xff1a; 输⼊&#xff1a;address "1.1.1.…...

nvidia-persistenced 常驻

本文地址&#xff1a;blog.lucien.ink/archives/542 发现每次执行 nvidia-smi 都特别慢&#xff0c;发现是需要 nvidia-persistenced 常驻才可以&#xff0c;这个并不会在安装完驱动之后自动配置&#xff0c;需要手动设置一个自启。 cat <<EOF >> /etc/systemd/sy…...

leetcode 42, 58, 14(*)

42. Trapping Rain Water 1.暴力解法&#xff08;未通过&#xff09; class Solution { public:int trap(vector<int>& height) {int n height.size();int res 0;for(int i0; i<n; i){int r_max 0, l_max 0;for(int j i; j<n; j)r_max max(r_max, heigh…...

SpringCloud-微服务CAP原则

接上文 SpringCloud-Config配置中心 到此部分即微服务的入门。 总的来说&#xff0c;数据存放的节点数越多&#xff0c;分区容忍性就越高&#xff0c;但要复制更新的次数就越多&#xff0c;一致性就越难保证。同时为了保证一致性&#xff0c;更新所有节点数据所需要的时间就…...

K8S:Yaml文件详解

目录 一.Yaml文件详解 1.Yaml文件格式 2.YAML 语法格式 二.Yaml文件编写及相关概念 1.查看 api 资源版本标签 2.yaml编写案例 &#xff08;2&#xff09;Deployment类型编写nginx服务 (3&#xff09;k8s集群中的port介绍 &#xff08;5&#xff09;快速编写yaml文件 …...

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

力扣-35.搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...