Files
waf-platform/HttpDNSSDK/sdk/ios/NewHttpDNS/Persistent/HttpdnsDB.m

609 lines
21 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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 <sqlite3.h>
// 表名
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<NSString *> *)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<HttpdnsHostRecord *> *)getAllRecords {
__block NSMutableArray<HttpdnsHostRecord *> *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<HttpdnsHostRecord *> *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<NSString *> *v4ips = [NSMutableArray arrayWithArray:record.v4ips];
NSMutableArray<NSString *> *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<NSString *> *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<NSString *> *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