基于C++实现工业级线程安全日志系统

news2026/5/21 6:51:18
在服务端开发级中小型应用中稳定、易用、带自动切割与过期清理的日志模块是必需的本文基于C17及以上标准实现一款单例模式、线程安全、控制台彩色输出、按时间/大小自动切分、过期日志自动清理的企业级日志系统代码可直接集成到项目中使用。一、日志系统作用日志系统就是把程序运行过程中发生的所有事情记录下来控制台打印存本地文件。那么日志到底有什么用1.排查Bug程序崩溃、报错、卡死、逻辑错误不可能时刻盯着代码运行检查。例如数据库连接不上接口请求失败逻辑异常如果没有日志那就是瞎猜根本不知道错在哪里有日志直接定位到文件、哪一行、哪个函数一目了然很方便的就能定位错误。2.记录运行状态程序干了什么全部留下记录。例如程序什么时候启动什么时候关闭是否初始化成功后期维护、交接、复盘全靠日志3.线上不能打断点调试本地开发可以debug、打断点但是在服务器或者线上环境不能调试、不能打断点。线上出问题唯一能看的只有日志文件。4.安全审计、问题追溯谁操作了什么什么时候执行的如果出了事故通过日志可以追溯原因、定责、复盘5.长期保存不会丢失控制台打印程序一关记录全没所以日志通常写在文件里过期自动清理几天半个月的记录随时能翻看。二、日志系统要求企业对日志有不同要求整体大致是相同的。例如日志系统要求如下要求1日志存放位置默认为系统根目录的logs日志类里可以定义但可以支持存储到指定文件统一存储服务器。要求2日志文件名格式系统名-子系统名如果没有则默认为x-一级模块名-年月日.txt/.log要求3日志内容格式日志内容格式[时间] [日志级别] [程序文件名称/模块名称][类名-函数名/线程名/函数名/其他] [扩展内容可自定义比如子系统编号] : 内容。egcrm-x-jcrmh_payroll-2026-4-21-16.txt/.logcrm-123456-payroll-2026-4-21-16.txt/.logegcrm-x-jcrmh_payroll-2026-4-21-16.txt/.logcrm-123456-payroll-2026-4-21-16.txt/.log要求4日志支持大小上限过大造成读写操作消耗太多资源比如大小默认1个G大于则备份为文件名-数字序号。egcrm-x-jcrmh_payroll-2026-4-21-16-1.txt/.log要求5日志支持有效期用于只保存近期日志比如默认15天超过有效期就清理。三、核心设计目标单例模式全局唯一实例避免多日志对象冲突线程安全多线程并发写日志不错乱彩色控制台输出不同级别日志区分颜色便于调试文件持久化自动创建目录按小时生成日志文件大小切割单个日志文件超出上限自动备份避免读写资源操作消耗资源过期清理启动时自动删除超过指定天数的历史日志格式化输出支持可变参数高容错日志模块不影响主程序运行四、整体架构组件核心类与功能划分Logger单例类日志洗头工的核心负责控制台输出、文件写入、日志切割、过期清理日志级别定义8级日志INFO/DEBUG/NOTICE/WARNING/ERROR/CRITICAL/ALERT/EMERGENCY宏定义封装简化调用自动携带文件名、函数名等。关键技术点单例模式线程安全的懒汉模式std::mutex保证多线程日志写入安全C17 std::filesystem跨平台文件/目录操作可变参数va_list实现格式化日志输出控制台ANSI颜色码跨平台彩色输出可变参数va_list实现格式化日志输出五、日志代码实现Logger.h#pragma once #includeiostream #includestring #includecstdarg #includestdio.h #includechrono #includemutex #includefstream #includefilesystem namespace fs std::filesystem; // eg: LOG_INFO(子系统编号,日志内容) #define LOG_INFO(extendId,format,...)\ Logger::getInstance().log(INFO,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) #define LOG_DEBUG(extendId,format,...)\ Logger::getInstance().log(DEBUG,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) #define LOG_NOTICE(extendId,format,...)\ Logger::getInstance().log(NOTICE,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) #define LOG_WARNING(extendId,format,...)\ Logger::getInstance().log(WARNING,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) #define LOG_ERROR(extendId,format,...)\ Logger::getInstance().log(ERROR,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) #define LOG_CRITICAL(extendId,format,...)\ Logger::getInstance().log(CRITICAL,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) #define LOG_ALERT(extendId,format,...)\ Logger::getInstance().log(ALERT,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) #define LOG_EMERGENCY(extendId,format,...)\ Logger::getInstance().log(EMERGENCY,__FILE__,__FUNCTION__,extendId,format,##__VA_ARGS__) // 定义日志级别 enum LogLevel { INFO, // 普通信息有意义的事件 DEBUG, // 调式信息详细的调试信息 NOTICE, // 提示信息正常但是值得注意的事件 WARNING, // 警告信息异常事件但是并不是错误 ERROR, // 错误信息运行时的错误不需要立即注意到但是需要被专门记录并监控到 CRITICAL, // 严重信息边界条件 ALERT, // 警报信息必须立即采取行动 EMERGENCY // 紧急请求系统不可用了 }; // 定义日志内容颜色全局为绿色日志级别为高亮色 #define COLOR_RESET \033[0m // 重置 #define GREEN \033[32m // 整体绿色 #define COLOR_INFO \033[1;34m // 亮蓝 #define COLOR_DEBUG \033[1;37m // 亮白 #define COLOR_NOTICE \033[1;36m // 亮青 #define COLOR_WARNING \033[1;33m // 亮黄 #define COLOR_ERROR \033[1;31m // 亮红 #define COLOR_CRITICAL \033[1;35m // 亮紫 #define COLOR_ALERT \033[1;30m // 亮黑色 #define COLOR_EMERGENCY \033[1;41m // 红底高亮 // 日志全局可配置 #define LOG_SYSTEM_NAME crm // 系统名称 #define LOG_SUB_SYSTEM_NAME x // 子系统名称没有则填x #define LOG_MODULE_NAME jrcmh_payroll // 一级模块名称 #define LOG_DIR ./logs/ // 日志默认存放位置可自定义修改存储位置 #define LOG_MAX_SIZE (1024*1024*1024) // 单个日志文件最大大小默认1GB #define LOG_EXPIRE_DAYS 15 // 日志保留天数超过默认天数15天自动删除 // 日志类实现为单例 class Logger { public: static Logger getInstance(); // 获取日志唯一实例 std::string getNowTime(); // 获取人类可读的当前时间年-月-日 时:分:秒) std::string levelToString(LogLevel logLevel); // 日志级别转字符串 std::string getLevelColor(LogLevel loglevel); // 获取日志级别颜色 void log(LogLevel level, const char* file, const char* func, const std::string extendId, const char* format, ...); // 写日志 Logger(const Logger) delete; Logger operator(const Logger) delete; private: Logger(std::string sysName, std::string subSysName, std::string moduleName, std::string logDir, size_t maxSize, int expireDays); void createDir(const std::string dir); // 创建日志目录不存在则创建 void cleanExpiredLog(); // 清理过期日志超过指定天数默认15天自动删除 void consoleOutput(const std::string time, // 输出到控制台带颜色 const std::string level, const std::string color, const char* file, const char* func, const std::string extendId, const std::string content); void fileOutPut(const std::string time, // 输出到日志文件自动切割、自动清理过期日志 const std::string level, const char* file, const char* func, const std::string extendId, const std::string content); std::string generateLogFileName(); // 生成今天/当前小时的日志文件名 void rotateLogFile(); // 日志文件切割超过大小自动备份 std::mutex mutex_; std::string sysName_; // 系统名 std::string subSysName_; // 子系统名 std::string moduleName_; // 模块名 std::string logDir_; // 日志存放目录 size_t maxSize_; // 单个日志文件最大大小 int expireDays_; // 日志有效期 std::ofstream ofs_; std::string currentFile_; // 当前正在写入的日志文件名 };Logger.cpp#define _CRT_SECURE_NO_WARNINGS #includeLogger.h Logger::Logger(std::string sysName, std::string subSysName, std::string moduleName, std::string logDir, size_t maxSize, int expireDays) :sysName_(sysName), subSysName_(subSysName), moduleName_(moduleName), logDir_(logDir), maxSize_(maxSize), expireDays_(expireDays) { createDir(logDir_); // 初始化时创建logs目录 cleanExpiredLog(); // 初始化时自动清理有效期的旧日志 } Logger Logger::getInstance() { static Logger logger( LOG_SYSTEM_NAME, LOG_SUB_SYSTEM_NAME, LOG_MODULE_NAME, LOG_DIR, // 默认日志存放目录程序根目录 / logs LOG_MAX_SIZE, LOG_EXPIRE_DAYS); return logger; } std::string Logger::getNowTime() { time_t now time(0); struct tm* time_t localtime(now); char buffer[128] { 0 }; snprintf(buffer, sizeof(buffer), %4d-%02d-%02d %02d:%02d:%02d, time_t-tm_year 1900, time_t-tm_mon 1, time_t-tm_mday, time_t-tm_hour, time_t-tm_min, time_t-tm_sec); return buffer; } std::string Logger::levelToString(LogLevel logLevel) { switch (logLevel) { case(INFO): return INFO; break; case(DEBUG): return DEBUG; break; case(NOTICE): return NOTICE; break; case(WARNING): return WARNING; break; case(ERROR): return ERROR; break; case(CRITICAL): return CRITICAL; break; case(ALERT): return ALERT; break; case(EMERGENCY): return EMERGENCY; break; default: return UNKNOWN; } } std::string Logger::getLevelColor(LogLevel loglevel) { switch (loglevel) { case(INFO): return COLOR_INFO; break; case(DEBUG): return COLOR_DEBUG; break; case(NOTICE): return COLOR_NOTICE; break; case(WARNING): return COLOR_WARNING; break; case(ERROR): return COLOR_ERROR; break; case(CRITICAL): return COLOR_CRITICAL; break; case(ALERT): return COLOR_ALERT; break; case(EMERGENCY): return COLOR_EMERGENCY; break; default: return COLOR_RESET; } } // 日志内容格式[时间][日志级别][文件路径][函数名][扩展编号]:内容 // eg[2026-4-17 16:49:32][INFO][mian.cpp][main][1001]:服务启动成功 void Logger::log(LogLevel level, const char* file, const char* func, const std::string extendId, const char* format, ...) { std::lock_guardstd::mutex lock(mutex_); // 1.生成日志内容 std::string timeStr getNowTime(); std::string levelStr levelToString(level); std::string levelColor getLevelColor(level); // 2.格式化日志内容 char buffer[1024] { 0 }; va_list args; va_start(args, format); vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); std::string content buffer; // 3.输出到控制台带颜色 consoleOutput(timeStr, levelStr, levelColor, file, func, extendId, content); // 4.输出到文件不带颜色纯文本 fileOutPut(timeStr, levelStr, file, func, extendId, content); } void Logger::createDir(const std::string dir) { if (!fs::exists(dir)) { fs::create_directories(dir); } } void Logger::cleanExpiredLog() { try { // 遍历logs目录下所有文件 for (auto p : fs::directory_iterator(logDir_)) { // 只处理普通文件 if (!p.is_regular_file()) continue; // 获取文件最后修改时间 auto lastWrite fs::last_write_time(p); // 获取文件当前时间 auto now fs::file_time_type::clock::now(); // 计算小时差 auto hours std::chrono::duration_caststd::chrono::hours(now - lastWrite).count(); // 转成天数 int days hours / 24; // 超过有效期删除文件 if (days expireDays_) fs::remove(p); } } catch (...) { //异常不处理防止日志模块崩溃影响主程序 } } void Logger::consoleOutput(const std::string time, const std::string level, const std::string color, const char* file, const char* func, const std::string extendId, const std::string content) { std::cout GREEN; // 全局绿色 std::cout [ time ]; std::cout [ color level GREEN]; std::cout [ file ] [ func ]; if (!extendId.empty()) { std::cout [ extendId ]; } std::cout : content COLOR_RESET std::endl; } void Logger::fileOutPut(const std::string time, const std::string level, const char* file, const char* func, const std::string extendId, const std::string content) { // 1.生成今天/当前小时的日志文件名 std::string newFile generateLogFileName(); if (currentFile_ ! newFile || !ofs_.is_open()) // 如果文件切换了或者文件没打开则重新打开新文件 { if (ofs_.is_open()) ofs_.close(); currentFile_ newFile; ofs_.open(currentFile_, std::ios::app); // 追加模式打开 } // 2.判断文件大小是否超过上限超过则切割备份 if (fs::exists(currentFile_) fs::file_size(currentFile_) maxSize_) { ofs_.close(); rotateLogFile(); // 文件重命名备份 ofs_.open(currentFile_, std::ios::app); // 重新创建一个空白的源文件名日志文件 } // 3.写入日志内容纯文本 ofs_ [ time ] [ level ] [ file ] [ func ]; if (!extendId.empty()) { ofs_ [ extendId ]; } ofs_ : content std::endl; // 4.立即刷新到磁盘防止丢失 ofs_.flush(); } // 生成日志文件名 // 格式系统名-子系统名-模块名-年-月-日-时.log std::string Logger::generateLogFileName() { time_t now time(0); struct tm* time_t localtime(now); char name[256]; snprintf(name, sizeof(name), %s-%s-%s-%04d-%02d-%02d-%02d.log, sysName_.c_str(), subSysName_.c_str(), moduleName_.c_str(), time_t-tm_year 1900, time_t-tm_mon 1, time_t-tm_mday, time_t-tm_hour); return logDir_ / name; } // 日志文件切割超过大小自动备份 // egoa-web-org-2024-09-03-11.txt/.log - oa-web-org-2024-09-03-11-1.txt void Logger::rotateLogFile() { // 去掉后缀.log std::string base currentFile_.substr(0, currentFile_.find_last_of(.)); int index 1; std::string target; // 找一个不存在的序号 while (true) { target base - std::to_string(index) .log; if (!fs::exists(target)) break; index; } // 重命名备份 fs::rename(currentFile_, target); }六、核心功能讲解1.日志文件管理自动创建目录程序启动时自动创建./logs目录无需手动创建。按小时命名文件名格式系统名-子系统名-模块名-年月日-小时.log方便按时间检索。按大小切割文件超出上限自动重命名备份。2.过期日志自动清理程序初始化时自动遍历logs日志目录计算天数自动删除超过保留天数的日志文件避免日志占用大量磁盘空间且清理逻辑带异常捕获日志模块不影响主程序。3.控制台彩色输出不同级别日志使用不同颜色便于调试。4.日志调用宏简化调用方式自动携带文件名、函数名等无需手动传递一行代码即可输出标准化日志。七、日志使用示例Test.cpp#define _CRT_SECURE_NO_WARNINGS #includeLogger.h int main() { LOG_INFO(1001, 记录工单查询数据: {\模块\:\工单\,\操作\:\查询\,\部门名称\:\123456\,\用户名\:\市长热线\,\工号\:\5600\}); LOG_WARNING(1002, 连接失败%s, 网络超时); LOG_DEBUG(1003, 调试信息: %s, 详细的调试信息); LOG_NOTICE(1004, 提示信息: %s, 正常,但是值得注意); LOG_ERROR(1005, 错误信息: %s, 运行时的错误需要被记录); LOG_CRITICAL(1006, 严重信息); LOG_ALERT(1007, 警报信息); LOG_EMERGENCY(1008, 紧急请求: %s, 系统不可用了); return 0; }八、日志使用测试测试大小切割超过单个日志文件大小上限则重命名备份。单个日志文件大小为1024字节时#define LOG_MAX_SIZE 1024写入大小未超过上限8条记录正常写入文件不备份。第二次写入时先判断日志大小是否超过上限先写入1条记录未上限写完成此时大小已经上限但是已经将这条记录写完再写第2条记录时大小超过上限执行切割重命名备份将原文件里的9条记录全部备份到-1.log文件再创建一个空白的原文件名日志文件继续写写完剩余7条记录。本文实现的 C 日志系统兼顾了易用性、稳定性、高性能完全满足工业级项目的日志需求。代码结构清晰、无第三方依赖、跨平台兼容可直接集成到各类 C 项目中省去重复造轮子的成本。九、日志扩展优化1、增加日志级别过滤只输出指定级别以上的日志。2、实现异步日志将写入操作放到后台线程提升性能。3、支持配置文件加载动态修改日志路径、大小、保留天数。4、增加日志压缩功能减少磁盘占用。

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…