iOS开发-CocoaLumberjack日志库实现Logger日志功能
iOS开发-Logger日志功能实现
在iOS开发中,常用CocoaLumberjack来作为日志功能实现的日志框架
一、CocoaLumberjack是什么?
CocoaLumberjack 是 支持 iOS 和 Mac 平台的日志框架,使用简单,功能强大且不失灵活,它的主要功能就是:支持不同 Level 的 Log 信息输出到各个渠道,包括苹果日志系统、Xcode 控制台、本地文件或者数据库。
二、简单实用CocoaLumberjack
在工程的Podfile文件中引入CocoaLumberjack与SSZipArchive
pod 'SSZipArchive' # 压缩pod 'CocoaLumberjack' # 日志库
CocoaLumberjack是通过DDLog的addLogger:(id )logger方法添加Log数据输出的渠道
有一下三种渠道
- DDTTYLogger(Xcode 控制台)
- DDASLLogger(苹果日志系统)
- DDFileLogger(本地文件)
示例代码如下
[DDLog addLogger:[DDTTYLogger sharedInstance]]; // TTY = Xcode console
[DDLog addLogger:[DDASLLogger sharedInstance]]; // ASL = Apple System LogsDDFileLogger *fileLogger = [[DDFileLogger alloc] init]; // File Logger
fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling
fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
[DDLog addLogger:fileLogger];
当然日志还需要我们区分Logger Level,CocoaLumberjack也为我们提供了相应的宏定义方法
如:
DDLogVerbose(@"Verbose"); // Log 信息
DDLogDebug(@"Debug");
DDLogInfo(@"Info");
DDLogWarn(@"Warn");
DDLogError(@"Error");
三、实现Logger输出文件及archive
有时候我们遇到应用出现问题,需要将一些关键点进行埋点,将关键信息保存到文件,带需要的时候将日志archive上传到服务器,以便分析出现的问题。
定义日志输出的格式代码
SDLogFormatter.h
#import <UIKit/UIKit.h>
#import "CocoaLumberjack.h"NS_ASSUME_NONNULL_BEGIN@interface SDLogFormatter : NSObject<DDLogFormatter>@endNS_ASSUME_NONNULL_END
SDLogFormatter.m
#import "SDLogFormatter.h"@implementation SDLogFormatter- (NSString *)formatLogMessage:(DDLogMessage *)logMessage{NSString *logLevel = nil;switch (logMessage.flag){case DDLogFlagError:logLevel = @"[ERROR] > ";break;case DDLogFlagWarning:logLevel = @"[WARN] > ";break;case DDLogFlagInfo:logLevel = @"[INFO] > ";break;case DDLogFlagDebug:logLevel = @"[DEBUG] > ";break;default:logLevel = @"[VBOSE] > ";break;}NSString *formatStr = [NSString stringWithFormat:@"[APP:%@ VERSION:%@] > [T:%@] > %@[%@ %@][line %lu] %@", [self appName], [self appVersion], [self currentDate],logLevel, logMessage.fileName, logMessage.function,(unsigned long)logMessage.line, logMessage.message];return formatStr;
}#pragma mark - App常用设置
- (NSString *)appName {return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
}- (NSString *)appVersion {return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
}- (NSString *)currentDate {static NSDateFormatter *formatter = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{formatter = [[NSDateFormatter alloc] init];formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";});return [formatter stringFromDate:[NSDate date]];
}@end
实现日志本地输出log文件中及打包上传代码
SDLogger.h
#import <Foundation/Foundation.h>
#import "CocoaLumberjack.h"
#import "SDLogFormatter.h"
#import "SDLoggerDefine.h"
#import "SDSystemCatchCrash.h"@interface SDLogger : NSObject+ (instancetype)sharedInstance;/**初始化logger*/
- (void)initLogger;/**删除压缩上传后的zip目录@param zipPath zipPath目录*/
- (void)deleteZip:(NSString *)zipPath;/**获取zip地址,从那一天到哪一天的日志的zip@param fromDateString fromDateString 格式为“2022-01-19”@param toDateString toDateString 格式为“2022-01-19”*/
- (NSString *)getLogsZipPath:(NSString *)fromDateString toDate:(NSString *)toDateString;/**沙盒document目录@return document目录*/
- (NSString *)documentPath;/**沙盒cache目录@return cache目录*/
- (NSString *)cachePath;/**临时文件路径@return 临时文件路径*/
- (NSString *)dirTmpPath;/**创建目录文件路径@return 创建目录*/
- (NSString *)creatFolder:(NSString *)folderName at:(NSString *)dirPath;@end
SDLogger.m
/**CocoaLumberjack包含几个对象分别可以把Log输出到不同的地方:DDASLLogger 输出到Console.appDDTTYLogger 输出到Xcode控制台DDLogFileManager 输出到文件DDAbstractDatabaseLogger 输出到DB通过ddLogLevel的int型变量或常量来定义打印等级LOG_LEVEL_OFF 关闭LogLOG_LEVEL_ERROR 只打印Error级别的LogLOG_LEVEL_WARN 打印Error和Warning级别的LogLOG_LEVEL_INFO 打印Error、Warn、Info级别的LogLOG_LEVEL_DEBUG 打印Error、Warn、Info、Debug级别的LogLOG_LEVEL_VERBOSE 打印Error、Warn、Info、Debug、Verbose级别的Log使用不同的宏打印不同级别的LogDDLogError(frmt, …) 打印Error级别的LogDDLogWarn(frmt, …) 打印Warn级别的LogDDLogInfo(frmt, …) 打印Info级别的LogDDLogDebug(frmt, …) 打印Debug级别的LogDDLogVerbose(frmt, …) 打印Verbose级别的Log*/#import "SDLogger.h"
#import "SSZipArchive.h"
#import "SDSystemCatchCrash.h"static NSUncaughtExceptionHandler *previousUncaughtExceptionHandler;@interface SDLogger ()@property (nonatomic, strong) SDLogFormatter *logFormatter;@property (nonatomic, strong) DDFileLogger *fileLogger;@end@implementation SDLogger+ (instancetype)sharedInstance {static SDLogger *logger;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{logger = [[SDLogger alloc] init];logger.logFormatter = [[SDLogFormatter alloc] init];logger.fileLogger = [[DDFileLogger alloc] init];});return logger;
}/**初始化logger*/
- (void)initLogger {//新版本的OSlog,输出到到Consoleif (@available(iOS 10.0, *)) {[[DDOSLogger sharedInstance] setLogFormatter:self.logFormatter];[DDLog addLogger:[DDOSLogger sharedInstance] withLevel:DDLogLevelDebug];} else {//添加输出到Console[[DDASLLogger sharedInstance] setLogFormatter:self.logFormatter];[DDLog addLogger:[DDASLLogger sharedInstance] withLevel:DDLogLevelDebug];} // Uses os_log//添加文件输出self.fileLogger.rollingFrequency = 60 * 60 * 24; // 一个LogFile的有效期长,有效期内Log都会写入该LogFileself.fileLogger.logFileManager.maximumNumberOfLogFiles = 7;//最多LogFile的数量[self.fileLogger setLogFormatter:self.logFormatter];[DDLog addLogger:self.fileLogger withLevel:DDLogLevelInfo];//保存之前的HandlerpreviousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();// 将下面C函数的函数地址当做参数NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
}/**删除压缩上传后的zip目录@param zipPath zipPath目录*/
- (void)deleteZip:(NSString *)zipPath {// 判断文件夹是否存在 不存在创建BOOL exits = [[NSFileManager defaultManager] fileExistsAtPath:zipPath];if (exits) {// 文件存在, 删除文件NSError *deError;[[NSFileManager defaultManager] removeItemAtPath:zipPath error:&deError];NSLog(@"deleteZipPath:%@", deError);}
}/**获取zip地址,从那一天到哪一天的日志的zip@param fromDateString fromDateString@param toDateString toDateString*/
- (NSString *)getLogsZipPath:(NSString *)fromDateString toDate:(NSString *)toDateString {if (!((fromDateString && [fromDateString isKindOfClass:[NSString class]] && fromDateString.length > 0) &&(toDateString && [toDateString isKindOfClass:[NSString class]] && toDateString.length > 0))) {return nil;}NSDate *fromDate = [[self dateFormatter] dateFromString:fromDateString];NSDate *toDate = [[self dateFormatter] dateFromString:toDateString];NSArray *dateList = [self getMonthArrayWithBeginDate:fromDate EndDate:toDate];NSLog(@"dateList:%@", dateList);// 根据日期,获取时间间隔from、to之间的log,将其存到tmp或者cache目录后中的tmpLog目录,将tmpLog目录压缩到tmp或者cache目录后的ziplog目录NSArray *logFilePaths = [self.fileLogger.logFileManager sortedLogFilePaths];NSArray *logFileNames = [self.fileLogger.logFileManager sortedLogFileNames];NSLog(@"logFilePaths:%@", logFilePaths);NSLog(@"logFileNames:%@", logFileNames);// 创建zip日志目录NSString *zipDir = [self creatFolder:@"logZips" at:[self cachePath]];// 创建tmpLog目录NSString *tmpLogDir = [self creatFolder:@"tmpLog" at:[self cachePath]];NSMutableArray *toFilePaths = [NSMutableArray arrayWithCapacity:0];for (NSString *day in dateList) {for (NSString *filePath in logFilePaths) {// filePathNSString *content = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];NSLog(@"content:%@", content);if ([filePath containsString:day]) {[toFilePaths addObject:filePath];}}}for (NSString *filePath in toFilePaths) {NSString *fileName = [filePath lastPathComponent];NSString *toFilePath = [tmpLogDir stringByAppendingPathComponent:fileName];// 判断文件夹是否存在 不存在创建BOOL exits = [[NSFileManager defaultManager] fileExistsAtPath:toFilePath];if (exits) {// 文件存在, 删除文件NSError *dError;[[NSFileManager defaultManager] removeItemAtPath:toFilePath error:&dError];NSLog(@"removeItemAtPath:%@", dError);}BOOL copyResult = [[NSFileManager defaultManager] copyItemAtPath:filePath toPath:toFilePath error:nil];NSLog(@"copyItemAtPath:%@", (copyResult?@"YES":@"NO"));}//获取日志本地路径NSString *zipNameString = [NSString stringWithFormat:@"%@-%@.zip",fromDateString, toDateString];NSString *zipPath = [zipDir stringByAppendingPathComponent:zipNameString];[self deleteZip:zipPath];//压缩用户日志BOOL success = [SSZipArchive createZipFileAtPath:zipPath withContentsOfDirectory:tmpLogDir withPassword:nil];if (success) {// 压缩成功return zipPath;}return nil;
}/**沙盒document目录@return document目录*/
- (NSString *)documentPath {NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;return path;
}/**沙盒cache目录@return cache目录*/
- (NSString *)cachePath {NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject;return path;
}/**临时文件路径@return 临时文件路径*/
- (NSString *)dirTmpPath {return NSTemporaryDirectory();
}/**创建目录文件路径@return 创建目录*/
- (NSString *)creatFolder:(NSString *)folderName at:(NSString *)dirPath {NSString *temDirectory = [dirPath stringByAppendingPathComponent:folderName];NSFileManager *fileManager = [NSFileManager defaultManager];// 创建目录BOOL res=[fileManager createDirectoryAtPath:temDirectory withIntermediateDirectories:YES attributes:nil error:nil];if (res) {return temDirectory;}else{return temDirectory;}
}#pragma mark - Private
- (NSMutableArray *)getMonthArrayWithBeginDate:(NSDate *)beginDate EndDate:(NSDate *)endDate {// 计算两个时间的差值NSCalendar *calendar = [NSCalendar currentCalendar];// 需要对比的时间数据 NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;NSCalendarUnit unit = NSCalendarUnitDay;// 对比时间差NSDateComponents *dateCom = [calendar components:unit fromDate:beginDate toDate:endDate options:0];// 两个日期之间所有天数组成的数组NSMutableArray *allDayArr = [NSMutableArray new];for (int i = 0 ; i < dateCom.day+1 ; i ++) {// n天后的天数int days = i;// 一天一共有多少秒NSTimeInterval oneDay = 24 * 60 * 60;// 指定的日期NSDate *appointDate = [NSDate dateWithTimeInterval:oneDay * days sinceDate:beginDate];// 转字符串NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];[dateFormatter setDateFormat:@"yyyy-MM-dd"]; // @"yyyy-MM-dd"NSString *appointTime = [dateFormatter stringFromDate:appointDate];[allDayArr addObject:appointTime];}return allDayArr;
}- (NSDateFormatter *)dateFormatter {static NSDateFormatter *formatter = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{formatter = [[NSDateFormatter alloc] init];formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];formatter.dateFormat = @"yyyy-MM-dd";});return formatter;
}@end
四、获取crash一场并且输出到日志
当应用出现奔溃时候,出现crash,需要获取crash的错误信息,输出到日志Logger中。
这里使用的是uncaughtExceptionHandler方法
SDSystemCatchCrash.h
#import <Foundation/Foundation.h>
#import "SDLoggerDefine.h"NS_ASSUME_NONNULL_BEGIN@interface SDSystemCatchCrash : NSObjectvoid uncaughtExceptionHandler(NSException *exception);@endNS_ASSUME_NONNULL_END
SDSystemCatchCrash.m
#import "SDSystemCatchCrash.h"
#import "CocoaLumberjack.h"@implementation SDSystemCatchCrash//在AppDelegate中注册后,程序崩溃时会执行的方法
void uncaughtExceptionHandler(NSException *exception)
{//获取系统当前时间,(注:用[NSDate date]直接获取的是格林尼治时间,有时差)NSDateFormatter *formatter =[[NSDateFormatter alloc] init];[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];NSString *crashTime = [formatter stringFromDate:[NSDate date]];//异常的堆栈信息NSArray *stackArray = [exception callStackSymbols];//出现异常的原因NSString *reason = [exception reason];//异常名称NSString *name = [exception name];//拼接错误信息NSString *exceptionInfo = [NSString stringWithFormat:@"crashTime: %@ Exception reason: %@\nException name: %@\nException stack:%@", crashTime, name, reason, stackArray];DDLogError(@"exceptionInfo:%@", exceptionInfo);//把错误信息保存到本地文件,设置errorLogPath路径下//并且经试验,此方法写入本地文件有效。
// NSString *errorLogPath = [NSString stringWithFormat:@"%@/Documents/error.log", NSHomeDirectory()];
// NSError *error = nil;
// BOOL isSuccess = [exceptionInfo writeToFile:errorLogPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
// if (!isSuccess) {
// DLog(@"将crash信息保存到本地失败: %@", error.userInfo);
// }
}@end
五、为了方便使用不同level Logger输出
为了方便使用不同level Logger输出,这里使用宏定义方法
SDLoggerDefine.h
#ifndef SDLoggerDefine_h
#define SDLoggerDefine_h#ifdef __OBJC__#import <UIKit/UIKit.h>#import <Foundation/Foundation.h>#import "DDLog.h"
#endif#ifdef DEBUGstatic const int ddLogLevel = DDLogLevelVerbose;
#elsestatic const int ddLogLevel = DDLogLevelWarning;
#endif#define SDErrorLogger(frmt, ...) LOG_MAYBE(NO, LOG_LEVEL_DEF, DDLogFlagError, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
#define SDWarnLogger(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagWarning, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
#define SDInfoLogger(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagInfo, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
#define SDDebugLogger(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagDebug, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
#define SDVerboseLogger(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagVerbose, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)#endif /* SDLoggerDefine_h */
六、小结
iOS开发-Logger日志功能实现。常用CocoaLumberjack来作为日志功能实现的日志框架,输出到DDTTYLogger(Xcode 控制台)、DDASLLogger(苹果日志系统)、DDFileLogger(本地文件)。通过不同的Logger Level如Verbose、Debug、Info、Warn、Error等日志输出到日志以便分析出现问题的代码。
学习记录,每天不停进步。
相关文章:
iOS开发-CocoaLumberjack日志库实现Logger日志功能
iOS开发-Logger日志功能实现 在iOS开发中,常用CocoaLumberjack来作为日志功能实现的日志框架 一、CocoaLumberjack是什么? CocoaLumberjack 是 支持 iOS 和 Mac 平台的日志框架,使用简单,功能强大且不失灵活,它的主…...

深度学习(34)—— StarGAN(2)
深度学习(34)—— StarGAN(2) 完整项目在这里:欢迎造访 文章目录 深度学习(34)—— StarGAN(2)1. build model(1)generator(2&#…...
use lua
-- basic.lua print("hello ".."world") local a 1 --only this file can see b 2 -- global see -- not declare vaiable all asign to nil print(fuck) -- 字符串可以"" , ,[[]] -- 一些数值运算支持,进制数,科学数&a…...

网络——初识网络
网络基础 文章目录 网络基础计算机网络产生的背景认识网络协议网络协议初识协议分层OSI七层模型TCP/IP四层模型网络传输基本流程协议报头 认识IP地址认识MAC地址ifconfig查看主机地址ifconfig查看主机地址 计算机网络产生的背景 独立模式:计算机之间相互独立 早期的…...

调试技巧(2)
6. 如何写出好(易于调试)的代码 6.1 优秀的代码: 代码运行正常bug很少效率高可读性高可维护性高注释清晰文档齐全 常见的coding技巧: 使用assert尽量使用const养成良好的编码风格添加必要的注释避免编码的陷阱。 这里讲一下assert…...

骨传导耳机真不伤耳吗?骨传导耳机有什么好处?
骨传导耳机真不伤耳吗?骨传导耳机有什么好处? 我先来说说骨传导耳机的工作原理吧,骨传导是一种传声方式,声波通过颅骨、颌骨等头部骨头的振动,将声音传到内耳。其实骨传导的现象我们很常见,就像我们平时嗑瓜…...

mac切换jdk版本
查询mac已有版本 1、打开终端,输入: /usr/libexec/java_home -V注意:输入命令参数区分大小写(必须是-V) 2.目前本地装有两个版本的jdk xxxxedydeMacBook-Pro-9 ~ % /usr/libexec/java_home -V Matching Java Virtual Machines (2):20.0.1 (…...

go 基本语法(简单案例)
!注: go中 对变量申明很是严格,申明了,在没有使用的情况下,也会产生编译错误 1.行分隔符 一行就是代码,无;分割,如果需要在一行展示,需要以;分割,…...

Permute 3 for mac音视频格式转换
Permute是一款Mac平台上的媒体格式转换软件,由Chaotic Software开发。它可以帮助用户快速地将各种音频、视频和图像文件转换成所需格式,并提供了一些常用工具以便于用户进行编辑和处理。 Permute的主要特点包括: - 支持大量格式:支…...

线程概念linux
何为线程: 线程是程序中负责执行的单位,它可以被看作是进程的一部分,是进程的子任务。线程与进程的区别在于,进程是一个资源单位,而线程是进程的一部分,它只有栈这个独立的资源,其他资源如代码…...

【Yolov5+Deepsort】训练自己的数据集(1)| 目标检测追踪 | 轨迹绘制
📢前言:本篇是关于如何使用YoloV5Deepsort训练自己的数据集,从而实现目标检测与目标追踪,并绘制出物体的运动轨迹。本章讲解的为第一个内容:简单介绍YoloV5Deepsort中所用到的目标检测,追踪及sort&Depp…...

express学习笔记4 - 热更新以及express-boom
我们每次改动代码的时候都要重启项目,现在我们给项目添加一个热更新 npm install --save-dev nodemon # or using yarn: yarn add nodemon -D 在package.json添加一行代码 "dev": "nodemon ./bin/www" 重启项目 然后随便做改动ÿ…...
Ajax_02学习笔记(源码 + 图书管理业务 + 以及 个人信息修改功能)
Ajax_02 01_Bootstrap框架-控制弹框的使用 代码 <!-- 引入bootstrap.css --> <link href"https://cdn.jsdelivr.net/npm/bootstrap5.2.2/dist/css/bootstrap.min.css" rel"stylesheet"><button type"button" class"btn btn…...
Python-数据类型转换
当涉及数据类型转换时,Python提供了多种内置函数来执行不同类型之间的转换 以下是每个方法的详细说明和示例案例 整数和浮点数转换: int(x, base10): 将给定的参数x转换为整数。x可以是一个整数、浮点数或字符串。如果x是字符串,则可以提供…...

DASCTF 2023 0X401七月暑期挑战赛 Web方向 EzFlask ez_cms MyPicDisk 详细题解wp
EzFlask 源码直接给了 CtrlU查看带缩进的源码 import uuidfrom flask import Flask, request, session # 导入黑名单列表 from secret import black_list import jsonapp Flask(__name__) # 为 Flask 应用设置一个随机的 secret_key app.secret_key str(uuid.uuid4())# 检查…...

数据结构-链表
🗡CSDN主页:d1ff1cult.🗡 🗡代码云仓库:d1ff1cult.🗡 🗡文章栏目:数据结构专栏🗡 目录 目录 代码总览: 接口slist.h: slist.c: 1.什么是链表 1.1链…...

大数据Flink(五十五):Flink架构体系
文章目录 Flink架构体系 一、 Flink中的重要角色 二、Flink数据流编程模型 三、Libraries支持...
使用矢量数据库打造全新的搜索引擎
在技术层面上,矢量数据库采用了一种名为“矢量索引”的技术,这是一种组织和搜索矢量数据的方法,可以快速找到相似矢量。其中关键的一环是“距离函数”的概念,它可以衡量两个矢量的相似程度。 1.矢量数据库简介 矢量数据库是专门…...

算法提高-树状数组
算法提高-树状数组 241. 楼兰图腾(区间求和 单点修改)242. 一个简单的整数问题(差分推公式 实现 维护区间修改单点求和)243. 一个简单的整数问题2(区间修改和区间求和)AcWing 244. 谜一样的牛(…...
Django ORM详解:最全面的数据库处理指南
概要 深度探讨Django ORM的概念、基础使用、进阶操作以及详细解析在实际使用中如何处理数据库操作。这篇文章旨在帮助大家全面掌握Django ORM,理解其如何简化数据库操作,并透过表象理解其内部工作原理。 Django ORM简介 在深入讨论Django的ORMÿ…...

springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...

23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...

Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...