// // HttpdnsDB.m // NewHttpDNS // // Created by xuyecan on 2025/3/15. // Copyright © 2025 new-inc.com. All rights reserved. // #import "HttpdnsDB.h" #import "HttpdnsPersistenceUtils.h" #import // 表名 static NSString *const kTableName = @"httpdns_cache_table"; // 列名 static NSString *const kColumnId = @"id"; static NSString *const kColumnCacheKey = @"cache_key"; static NSString *const kColumnHostName = @"host_name"; static NSString *const kColumnCreateAt = @"create_at"; static NSString *const kColumnModifyAt = @"modify_at"; static NSString *const kColumnClientIp = @"client_ip"; static NSString *const kColumnV4Ips = @"v4_ips"; static NSString *const kColumnV4Ttl = @"v4_ttl"; static NSString *const kColumnV4LookupTime = @"v4_lookup_time"; static NSString *const kColumnV6Ips = @"v6_ips"; static NSString *const kColumnV6Ttl = @"v6_ttl"; static NSString *const kColumnV6LookupTime = @"v6_lookup_time"; static NSString *const kColumnExtra = @"extra"; @interface HttpdnsDB () @property (nonatomic, assign) sqlite3 *db; @property (nonatomic, copy) NSString *dbPath; @property (nonatomic, strong) dispatch_queue_t dbQueue; @end @implementation HttpdnsDB - (instancetype)initWithAccountId:(NSInteger)accountId { self = [super init]; if (self) { // 创建数据库目录 NSString *dbDir = [HttpdnsPersistenceUtils httpdnsDataDirectory]; // 设置数据库路径 _dbPath = [dbDir stringByAppendingPathComponent:[NSString stringWithFormat:@"%ld_v20250406.db", (long)accountId]]; // 创建专用队列确保线程安全 _dbQueue = dispatch_queue_create("com.new.httpdns.db", DISPATCH_QUEUE_SERIAL); // 打开数据库 __block BOOL success = NO; dispatch_sync(_dbQueue, ^{ success = [self openDB]; }); if (!success) { return nil; } } return self; } - (void)dealloc { if (_db) { sqlite3_close(_db); _db = NULL; } } #pragma mark - Public Methods - (BOOL)createOrUpdate:(HttpdnsHostRecord *)record { if (!record || !record.cacheKey) { return NO; } __block BOOL result = NO; dispatch_sync(_dbQueue, ^{ // 检查记录是否存在,以便正确处理createAt HttpdnsHostRecord *existingRecord = [self selectByCacheKeyInternal:record.cacheKey]; // 准备要保存的记录 NSDate *now = [NSDate date]; HttpdnsHostRecord *recordToSave; if (existingRecord) { // 更新记录,保留原始的createAt,更新modifyAt为当前时间 recordToSave = [[HttpdnsHostRecord alloc] initWithId:record.id cacheKey:record.cacheKey hostName:record.hostName createAt:existingRecord.createAt // 保留原始createAt modifyAt:now // 更新modifyAt clientIp:record.clientIp v4ips:record.v4ips v4ttl:record.v4ttl v4LookupTime:record.v4LookupTime v6ips:record.v6ips v6ttl:record.v6ttl v6LookupTime:record.v6LookupTime extra:record.extra]; } else { // 新记录,设置createAt和modifyAt为当前时间 recordToSave = [[HttpdnsHostRecord alloc] initWithId:record.id cacheKey:record.cacheKey hostName:record.hostName createAt:now // 新记录的createAt modifyAt:now // 新记录的modifyAt clientIp:record.clientIp v4ips:record.v4ips v4ttl:record.v4ttl v4LookupTime:record.v4LookupTime v6ips:record.v6ips v6ttl:record.v6ttl v6LookupTime:record.v6LookupTime extra:record.extra]; } // 使用INSERT OR REPLACE语法保存记录 result = [self saveRecord:recordToSave]; }); return result; } - (nullable HttpdnsHostRecord *)selectByCacheKey:(NSString *)cacheKey { if (!cacheKey) { return nil; } __block HttpdnsHostRecord *record = nil; dispatch_sync(_dbQueue, ^{ record = [self selectByCacheKeyInternal:cacheKey]; }); return record; } - (BOOL)deleteByCacheKey:(NSString *)cacheKey { if (!cacheKey) { return NO; } __block BOOL result = NO; dispatch_sync(_dbQueue, ^{ NSString *sql = [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ = ?", kTableName, kColumnCacheKey]; sqlite3_stmt *stmt; if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &stmt, NULL) == SQLITE_OK) { sqlite3_bind_text(stmt, 1, [cacheKey UTF8String], -1, SQLITE_TRANSIENT); result = (sqlite3_step(stmt) == SQLITE_DONE); sqlite3_finalize(stmt); } }); return result; } - (NSInteger)deleteByHostNameArr:(NSArray *)hostNameArr { if (!hostNameArr || hostNameArr.count == 0) { return 0; } // 过滤掉空值 NSMutableArray *validHostNames = [NSMutableArray array]; for (NSString *hostname in hostNameArr) { if (hostname && hostname.length > 0) { [validHostNames addObject:hostname]; } } if (validHostNames.count == 0) { return 0; } __block NSInteger deletedCount = 0; dispatch_sync(_dbQueue, ^{ // 构建IN子句的占位符 NSMutableString *placeholders = [NSMutableString string]; for (NSUInteger i = 0; i < validHostNames.count; i++) { [placeholders appendString:@"?"]; if (i < validHostNames.count - 1) { [placeholders appendString:@","]; } } // 构建SQL语句 NSString *sql = [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ IN (%@)", kTableName, kColumnHostName, placeholders]; sqlite3_stmt *stmt; if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &stmt, NULL) == SQLITE_OK) { // 绑定所有参数 for (NSUInteger i = 0; i < validHostNames.count; i++) { sqlite3_bind_text(stmt, (int)(i + 1), [validHostNames[i] UTF8String], -1, SQLITE_TRANSIENT); } // 执行删除 if (sqlite3_step(stmt) == SQLITE_DONE) { deletedCount = sqlite3_changes(_db); } else { NSLog(@"Failed to delete records: %s", sqlite3_errmsg(_db)); } sqlite3_finalize(stmt); } else { NSLog(@"Failed to prepare delete statement: %s", sqlite3_errmsg(_db)); } }); return deletedCount; } - (BOOL)deleteAll { __block BOOL result = NO; dispatch_sync(_dbQueue, ^{ NSString *sql = [NSString stringWithFormat:@"DELETE FROM %@", kTableName]; char *errMsg; result = (sqlite3_exec(_db, [sql UTF8String], NULL, NULL, &errMsg) == SQLITE_OK); if (errMsg) { NSLog(@"Failed to delete all records: %s", errMsg); sqlite3_free(errMsg); } }); return result; } - (NSArray *)getAllRecords { __block NSMutableArray *records = [NSMutableArray array]; dispatch_sync(_dbQueue, ^{ NSString *sql = [NSString stringWithFormat:@"SELECT * FROM %@", kTableName]; sqlite3_stmt *stmt; if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &stmt, NULL) == SQLITE_OK) { while (sqlite3_step(stmt) == SQLITE_ROW) { HttpdnsHostRecord *record = [self recordFromStatement:stmt]; if (record) { [records addObject:record]; } } sqlite3_finalize(stmt); } else { NSLog(@"Failed to prepare getAllRecords statement: %s", sqlite3_errmsg(_db)); } }); return [records copy]; } - (NSInteger)cleanRecordAlreadExpiredAt:(NSTimeInterval)specifiedTime { __block NSInteger cleanedCount = 0; // 获取所有记录 NSArray *allRecords = [self getAllRecords]; dispatch_sync(_dbQueue, ^{ for (HttpdnsHostRecord *record in allRecords) { BOOL v4Expired = NO; BOOL v6Expired = NO; // 检查IPv4记录是否过期 if (record.v4LookupTime + record.v4ttl <= specifiedTime) { v4Expired = YES; } // 检查IPv6记录是否过期 if (record.v6LookupTime + record.v6ttl <= specifiedTime) { v6Expired = YES; } // 如果两种IP类型都过期,删除整条记录 if (v4Expired && v6Expired) { NSString *sql = [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ = ?", kTableName, kColumnCacheKey]; sqlite3_stmt *stmt; if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &stmt, NULL) == SQLITE_OK) { sqlite3_bind_text(stmt, 1, [record.cacheKey UTF8String], -1, SQLITE_TRANSIENT); if (sqlite3_step(stmt) == SQLITE_DONE) { cleanedCount++; } sqlite3_finalize(stmt); } } // 如果只有一种IP类型过期,更新记录 else if (v4Expired || v6Expired) { NSMutableArray *v4ips = [NSMutableArray arrayWithArray:record.v4ips]; NSMutableArray *v6ips = [NSMutableArray arrayWithArray:record.v6ips]; // 如果IPv4过期,清空IPv4记录 if (v4Expired) { [v4ips removeAllObjects]; } // 如果IPv6过期,清空IPv6记录 if (v6Expired) { [v6ips removeAllObjects]; } // 更新记录 NSString *sql = [NSString stringWithFormat: @"UPDATE %@ SET %@ = ?, %@ = ? WHERE %@ = ?", kTableName, kColumnV4Ips, kColumnV6Ips, kColumnCacheKey]; sqlite3_stmt *stmt; if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &stmt, NULL) == SQLITE_OK) { // 绑定v4ips if (v4ips.count > 0) { NSString *v4ipsStr = [v4ips componentsJoinedByString:@","]; sqlite3_bind_text(stmt, 1, [v4ipsStr UTF8String], -1, SQLITE_TRANSIENT); } else { sqlite3_bind_null(stmt, 1); } // 绑定v6ips if (v6ips.count > 0) { NSString *v6ipsStr = [v6ips componentsJoinedByString:@","]; sqlite3_bind_text(stmt, 2, [v6ipsStr UTF8String], -1, SQLITE_TRANSIENT); } else { sqlite3_bind_null(stmt, 2); } // 绑定cacheKey sqlite3_bind_text(stmt, 3, [record.cacheKey UTF8String], -1, SQLITE_TRANSIENT); if (sqlite3_step(stmt) == SQLITE_DONE) { cleanedCount++; } sqlite3_finalize(stmt); } } } }); return cleanedCount; } #pragma mark - Private Methods - (BOOL)openDB { if (sqlite3_open([_dbPath UTF8String], &_db) != SQLITE_OK) { NSLog(@"Failed to open database: %s", sqlite3_errmsg(_db)); return NO; } // 创建表 return [self createTableIfNeeded]; } - (BOOL)createTableIfNeeded { NSString *sql = [NSString stringWithFormat: @"CREATE TABLE IF NOT EXISTS %@ (" @"%@ INTEGER PRIMARY KEY AUTOINCREMENT, " @"%@ TEXT UNIQUE NOT NULL, " @"%@ TEXT NOT NULL, " @"%@ REAL, " @"%@ REAL, " @"%@ TEXT, " @"%@ TEXT, " @"%@ INTEGER, " @"%@ INTEGER, " @"%@ TEXT, " @"%@ INTEGER, " @"%@ INTEGER, " @"%@ TEXT" @")", kTableName, kColumnId, kColumnCacheKey, kColumnHostName, kColumnCreateAt, kColumnModifyAt, kColumnClientIp, kColumnV4Ips, kColumnV4Ttl, kColumnV4LookupTime, kColumnV6Ips, kColumnV6Ttl, kColumnV6LookupTime, kColumnExtra]; char *errMsg; if (sqlite3_exec(_db, [sql UTF8String], NULL, NULL, &errMsg) != SQLITE_OK) { NSLog(@"Failed to create table: %s", errMsg); sqlite3_free(errMsg); return NO; } return YES; } - (BOOL)saveRecord:(HttpdnsHostRecord *)record { // 使用INSERT OR REPLACE语法,如果记录存在则更新,不存在则插入 NSString *sql = [NSString stringWithFormat: @"INSERT OR REPLACE INTO %@ (" @"%@, %@, %@, %@, %@, %@, %@, %@, %@, %@, %@, %@) " @"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", kTableName, kColumnCacheKey, kColumnHostName, kColumnCreateAt, kColumnModifyAt, kColumnClientIp, kColumnV4Ips, kColumnV4Ttl, kColumnV4LookupTime, kColumnV6Ips, kColumnV6Ttl, kColumnV6LookupTime, kColumnExtra]; sqlite3_stmt *stmt; if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &stmt, NULL) != SQLITE_OK) { NSLog(@"Failed to prepare save statement: %s", sqlite3_errmsg(_db)); return NO; } // 绑定参数 int index = 1; // 绑定cacheKey (唯一键) sqlite3_bind_text(stmt, index++, [record.cacheKey UTF8String], -1, SQLITE_TRANSIENT); // 绑定hostName sqlite3_bind_text(stmt, index++, [record.hostName UTF8String], -1, SQLITE_TRANSIENT); // 绑定createAt if (record.createAt) { sqlite3_bind_double(stmt, index++, [record.createAt timeIntervalSince1970]); } else { sqlite3_bind_null(stmt, index++); } // 绑定modifyAt if (record.modifyAt) { sqlite3_bind_double(stmt, index++, [record.modifyAt timeIntervalSince1970]); } else { sqlite3_bind_null(stmt, index++); } // 绑定clientIp if (record.clientIp) { sqlite3_bind_text(stmt, index++, [record.clientIp UTF8String], -1, SQLITE_TRANSIENT); } else { sqlite3_bind_null(stmt, index++); } // 绑定v4ips if (record.v4ips.count > 0) { NSString *v4ipsStr = [record.v4ips componentsJoinedByString:@","]; sqlite3_bind_text(stmt, index++, [v4ipsStr UTF8String], -1, SQLITE_TRANSIENT); } else { sqlite3_bind_null(stmt, index++); } // 绑定v4ttl sqlite3_bind_int64(stmt, index++, record.v4ttl); // 绑定v4LookupTime sqlite3_bind_int64(stmt, index++, record.v4LookupTime); // 绑定v6ips if (record.v6ips.count > 0) { NSString *v6ipsStr = [record.v6ips componentsJoinedByString:@","]; sqlite3_bind_text(stmt, index++, [v6ipsStr UTF8String], -1, SQLITE_TRANSIENT); } else { sqlite3_bind_null(stmt, index++); } // 绑定v6ttl sqlite3_bind_int64(stmt, index++, record.v6ttl); // 绑定v6LookupTime sqlite3_bind_int64(stmt, index++, record.v6LookupTime); // 绑定extra if (record.extra) { sqlite3_bind_text(stmt, index++, [record.extra UTF8String], -1, SQLITE_TRANSIENT); } else { sqlite3_bind_null(stmt, index++); } BOOL result = (sqlite3_step(stmt) == SQLITE_DONE); sqlite3_finalize(stmt); return result; } - (HttpdnsHostRecord *)selectByCacheKeyInternal:(NSString *)cacheKey { NSString *sql = [NSString stringWithFormat:@"SELECT * FROM %@ WHERE %@ = ?", kTableName, kColumnCacheKey]; sqlite3_stmt *stmt; if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &stmt, NULL) != SQLITE_OK) { NSLog(@"Failed to prepare query statement: %s", sqlite3_errmsg(_db)); return nil; } sqlite3_bind_text(stmt, 1, [cacheKey UTF8String], -1, SQLITE_TRANSIENT); HttpdnsHostRecord *record = nil; if (sqlite3_step(stmt) == SQLITE_ROW) { record = [self recordFromStatement:stmt]; } sqlite3_finalize(stmt); return record; } - (HttpdnsHostRecord *)recordFromStatement:(sqlite3_stmt *)stmt { // 获取id NSUInteger recordId = (NSUInteger)sqlite3_column_int64(stmt, 0); // 获取cacheKey const char *cacheKeyChars = (const char *)sqlite3_column_text(stmt, 1); NSString *cacheKey = cacheKeyChars ? [NSString stringWithUTF8String:cacheKeyChars] : nil; // 获取hostName const char *hostNameChars = (const char *)sqlite3_column_text(stmt, 2); NSString *hostName = hostNameChars ? [NSString stringWithUTF8String:hostNameChars] : nil; // 获取createAt NSDate *createAt = nil; if (sqlite3_column_type(stmt, 3) != SQLITE_NULL) { double createAtTimestamp = sqlite3_column_double(stmt, 3); createAt = [NSDate dateWithTimeIntervalSince1970:createAtTimestamp]; } // 获取modifyAt NSDate *modifyAt = nil; if (sqlite3_column_type(stmt, 4) != SQLITE_NULL) { double modifyAtTimestamp = sqlite3_column_double(stmt, 4); modifyAt = [NSDate dateWithTimeIntervalSince1970:modifyAtTimestamp]; } // 获取clientIp const char *clientIpChars = (const char *)sqlite3_column_text(stmt, 5); NSString *clientIp = clientIpChars ? [NSString stringWithUTF8String:clientIpChars] : nil; // 获取v4ips NSArray *v4ips = nil; const char *v4ipsChars = (const char *)sqlite3_column_text(stmt, 6); if (v4ipsChars) { NSString *v4ipsStr = [NSString stringWithUTF8String:v4ipsChars]; v4ips = [v4ipsStr componentsSeparatedByString:@","]; } else { v4ips = @[]; } // 获取v4ttl int64_t v4ttl = sqlite3_column_int64(stmt, 7); // 获取v4LookupTime int64_t v4LookupTime = sqlite3_column_int64(stmt, 8); // 获取v6ips NSArray *v6ips = nil; const char *v6ipsChars = (const char *)sqlite3_column_text(stmt, 9); if (v6ipsChars) { NSString *v6ipsStr = [NSString stringWithUTF8String:v6ipsChars]; v6ips = [v6ipsStr componentsSeparatedByString:@","]; } else { v6ips = @[]; } // 获取v6ttl int64_t v6ttl = sqlite3_column_int64(stmt, 10); // 获取v6LookupTime int64_t v6LookupTime = sqlite3_column_int64(stmt, 11); // 获取extra NSString *extra = nil; const char *extraChars = (const char *)sqlite3_column_text(stmt, 12); if (extraChars) { extra = [NSString stringWithUTF8String:extraChars]; } // 创建记录对象 return [[HttpdnsHostRecord alloc] initWithId:recordId cacheKey:cacheKey hostName:hostName createAt:createAt modifyAt:modifyAt clientIp:clientIp v4ips:v4ips v4ttl:v4ttl v4LookupTime:v4LookupTime v6ips:v6ips v6ttl:v6ttl v6LookupTime:v6LookupTime extra:extra]; } @end