【原】FMDB源碼閱讀(二)

来源:http://www.cnblogs.com/polobymulberry/archive/2016/02/24/5209255.html
-Advertisement-
Play Games

【原】FMDB源碼閱讀(二) 本文轉載請註明出處 —— polobymulberry-博客園 1. 前言 上一篇只是簡單地過了一下FMDB一個簡單例子的基本流程,並沒有涉及到FMDB的所有方方面面,比如FMDB的executeUpdate:系列方法、資料庫的加解密等等。這次寫的就是對FMDataba...


【原】FMDB源碼閱讀(二)

本文轉載請註明出處 —— polobymulberry-博客園

1. 前言


上一篇只是簡單地過了一下FMDB一個簡單例子的基本流程,並沒有涉及到FMDB的所有方方面面,比如FMDB的executeUpdate:系列方法、資料庫的加解密等等。這次寫的就是對FMDatabase和FMResultSet這兩個文件的補全內容。每次寫這種補全的內容最頭疼,內容會很分散,感覺沒啥條理。

2. executeUpdate:系列函數


註意除了“SELECT”語句外,其他的SQL語句都需要使用executeUpdate:系列函數,這些SQL語句包括`CREATE`, `UPDATE`, `INSERT`, `ALTER`, `COMMIT`, `BEGIN`, `DETACH`, `DELETE`, `DROP`, `END`, `EXPLAIN`, `VACUUM`, 和`REPLACE`等等。

executeUpdate:函數使用例子如下:

BOOL success = [db executeUpdate:@"INSERT INTO authors (identifier, name, date, comment) VALUES (?, ?, ?, ?)", @(identifier), name, date, comment ?: [NSNull null]];
if (!success) {
    NSLog(@"error = %@", [db lastErrorMessage]);
}

基本上所有executeUpdate:系列函數都是對- [FMDatabase executeUpdate:error:withArgumentsInArray:orDictionary:orVAList:]函數的封裝。註意- [FMDatabase executeUpdate:error:withArgumentsInArray:orDictionary:orVAList:]函數的具體實現,基本和- [FMDatabase executeQuery:withArgumentsInArray:orDictionary:orVAList:]大部分實現是差不多的,關鍵在於executeQuery是查詢語句,所以它需要FMResultSet來保存查詢的結果。而executeUpdate是非查詢語句,不需要保存查詢結果,但需要調用sqlite3_step(pStmt)來執行該SQL語句。這裡就不贅述了,詳見源碼。

3. executeStatements:系列函數


使用executeStatements:函數可以將多個SQL執行語句寫在一個字元串中,並執行。具體使用舉例如下:

NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
                 "create table bulktest2 (id integer primary key autoincrement, y text);"
                 "create table bulktest3 (id integer primary key autoincrement, z text);"
                 "insert into bulktest1 (x) values ('XXX');"
                 "insert into bulktest2 (y) values ('YYY');"
                 "insert into bulktest3 (z) values ('ZZZ');";

success = [db executeStatements:sql];

sql = @"select count(*) as count from bulktest1;"
       "select count(*) as count from bulktest2;"
       "select count(*) as count from bulktest3;";

success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
    NSInteger count = [dictionary[@"count"] integerValue];
    XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary);
    return 0;
}];

基本上executeStatements:系列函數最終封裝的都是- [FMDatabase executeStatements:withResultBlock:]函數,而此函數又是對sqlite3_exec函數的封裝。

sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void *data, char **errmsg)

該常式提供了一個執行 SQL 命令的快捷方式,SQL 命令由 sql 參數提供,可以由多個 SQL 命令組成。

在這裡,第一個參數 sqlite3 是打開的資料庫對象,sqlite_callback 是一個回調,data 作為其第一個參數,errmsg 將被返回用來獲取程式生成的任何錯誤。

sqlite3_exec() 程式解析並執行由 sql 參數所給的每個命令,直到字元串結束或者遇到錯誤為止。

executeStatements:源碼如下(很簡單,就不贅述了):

- (BOOL)executeStatements:(NSString *)sql withResultBlock:(FMDBExecuteStatementsCallbackBlock)block {
    
    int rc;
    char *errmsg = nil;
    
    rc = sqlite3_exec([self sqliteHandle], [sql UTF8String], block ? FMDBExecuteBulkSQLCallback : nil, (__bridge void *)(block), &errmsg);
    
    if (errmsg && [self logsErrors]) {
        NSLog(@"Error inserting batch: %s", errmsg);
        sqlite3_free(errmsg);
    }
    
    return (rc == SQLITE_OK);
}

4. executeQueryWithFormat:和executeUpdateWithFormat:函數


考慮到如果用戶直接調用printf那種形式的字元串(比如“ INSERT INTO myTable (%@) VALUES (%d)”, “age”,25),那麼就需要自己將對應字元串處理成相應的SQL語句。恰好executeQuery和executeUpdate系列函數提供了相應的介面:

- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
- (BOOL)executeUpdateWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);

其實這兩個函數和其他executeQuery和executeUpdate系列方法,多的就是一個將format和…轉化為可用的SQL語句步驟。其它部分其實本質還是調用- [FMDatabase executeUpdate:error:withArgumentsInArray:orDictionary:orVAList:]和- [FMDatabase executeQuery:withArgumentsInArray:orDictionary:orVAList:]。下麵僅列出format和…的轉化代碼:

va_list args;
// 將args指向format中第一個參數
va_start(args, format);

NSMutableString *sql      = [NSMutableString stringWithCapacity:[format length]];
NSMutableArray *arguments = [NSMutableArray array];

// 使用extractSQL函數將format和args轉化為sql和arguments供後面函數使用
[self extractSQL:format argumentsList:args intoString:sql arguments:arguments];
// 關閉args,與va_start成對出現
va_end(args);

至於extractSQL:這個函數其實就是將(“INSERT INTO myTable (%@) VALUES (%d)”, “age”,25)中的%s和%d這種符號變成”?”,然後將”age”和25加入到arguments中。具體實現如下:

- (void)extractSQL:(NSString *)sql argumentsList:(va_list)args intoString:(NSMutableString *)cleanedSQL arguments:(NSMutableArray *)arguments {
    
    NSUInteger length = [sql length];
    unichar last = '\0';
    for (NSUInteger i = 0; i < length; ++i) {
        id arg = nil;
        /**
            使用last和current兩個變數(有些還需要next變數,比如%llu)判斷當前掃描到的字元串是不是%@、
            %c、%s、%d等等。舉個例子,如果碰到%s,那麼說明我替換的參數其實是一個字元串,所以使用arg = 
            [NSString stringWithUTF8String:]獲取到相應的arg作為參數值,至於%s/%c/%llu這些表示什麼,
            那就屬於C語言的範疇,此處就不討論了。
         */ 
        // 註意type va_arg(va_list arg_ptr,type)函數是根據傳入的type參數決定返回值類型的
        // 另外它的作用是獲取下一個參數的地址
         unichar current = [sql characterAtIndex:i];
        unichar add = current;
        if (last == '%') {
            switch (current) {
                case '@':
                    arg = va_arg(args, id);
                    break;
                case 'c':
                    // warning: second argument to 'va_arg' is of promotable type 'char'; this va_arg has undefined behavior because arguments will be promoted to 'int'
                    arg = [NSString stringWithFormat:@"%c", va_arg(args, int)];
                    break;
                case 's':
                    arg = [NSString stringWithUTF8String:va_arg(args, char*)];
                    break;
                case 'd':
                case 'D':
                case 'i':
                    arg = [NSNumber numberWithInt:va_arg(args, int)];
                    break;
                case 'u':
                case 'U':
                    arg = [NSNumber numberWithUnsignedInt:va_arg(args, unsigned int)];
                    break;
                // %hi表示short int,%hu表示short unsigned int
                case 'h':
                    i++;
                    if (i < length && [sql characterAtIndex:i] == 'i') {
                        //  warning: second argument to 'va_arg' is of promotable type 'short'; this va_arg has undefined behavior because arguments will be promoted to 'int'
                        arg = [NSNumber numberWithShort:(short)(va_arg(args, int))];
                    }
                    else if (i < length && [sql characterAtIndex:i] == 'u') {
                        // warning: second argument to 'va_arg' is of promotable type 'unsigned short'; this va_arg has undefined behavior because arguments will be promoted to 'int'
                        arg = [NSNumber numberWithUnsignedShort:(unsigned short)(va_arg(args, uint))];
                    }
                    else {
                        i--;
                    }
                    break;
                // %qi表示long long,%qu表示unsigned long long
                case 'q':
                    i++;
                    if (i < length && [sql characterAtIndex:i] == 'i') {
                        arg = [NSNumber numberWithLongLong:va_arg(args, long long)];
                    }
                    else if (i < length && [sql characterAtIndex:i] == 'u') {
                        arg = [NSNumber numberWithUnsignedLongLong:va_arg(args, unsigned long long)];
                    }
                    else {
                        i--;
                    }
                    break;
                case 'f':
                    arg = [NSNumber numberWithDouble:va_arg(args, double)];
                    break;
               // %g原本是根據數據選擇合適的方式輸出(浮點數還是科學計數法),不過此處是用float類型輸出
                case 'g':
                    // warning: second argument to 'va_arg' is of promotable type 'float'; this va_arg has undefined behavior because arguments will be promoted to 'double'
                    arg = [NSNumber numberWithFloat:(float)(va_arg(args, double))];
                    break;
                case 'l':
                    i++;
                    if (i < length) {
                        unichar next = [sql characterAtIndex:i];
                        if (next == 'l') {
                            i++;
                            if (i < length && [sql characterAtIndex:i] == 'd') {
                                //%lld
                                arg = [NSNumber numberWithLongLong:va_arg(args, long long)];
                            }
                            else if (i < length && [sql characterAtIndex:i] == 'u') {
                                //%llu
                                arg = [NSNumber numberWithUnsignedLongLong:va_arg(args, unsigned long long)];
                            }
                            else {
                                i--;
                            }
                        }
                        else if (next == 'd') {
                            //%ld
                            arg = [NSNumber numberWithLong:va_arg(args, long)];
                        }
                        else if (next == 'u') {
                            //%lu
                            arg = [NSNumber numberWithUnsignedLong:va_arg(args, unsigned long)];
                        }
                        else {
                            i--;
                        }
                    }
                    else {
                        i--;
                    }
                    break;
                default:
                    // something else that we can't interpret. just pass it on through like normal
                    break;
            }
        }
        else if (current == '%') {
            // 遇到%,直接跳過。
            add = '\0';
        }
        // 如果arg不為空,表示確定arg是參數,那麼就使用?替換它,並將其對應參數值arg添加到arguments
        if (arg != nil) {
            [cleanedSQL appendString:@"?"];
            [arguments addObject:arg];
        }
        // 如果參數格式是%@,但此時arg是空,那麼就替換為NULL
        else if (add == (unichar)'@' && last == (unichar) '%') {
            [cleanedSQL appendFormat:@"NULL"];
        }
        // 如果不是參數,就用原先字元串替換
        else if (add != '\0') {
            [cleanedSQL appendFormat:@"%C", add];
        }
        last = current;
    }
}

5. - (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt


上一篇僅僅對該函數進行簡單說明,該函數是用來在pStmt中綁定參數值到指定(根據idx)參數上。具體封裝的是sqlite3_bind*系列函數。

如果要使用sqlite3_bind*系列函數,需要指定三個參數,一個是正在使用的sqlite_stmt對象,一個是參數索引idx,還有一個就是需要綁定的參數值,此函數解決的關鍵就是根據obj判斷出其類型,然後調用相關的sqlite3_bind*函數,比如obj是int型,那麼就調用sqlite3_bind_int函數。又或者obj是NSData類型,那麼就調用sqlite_bind_blob函數。具體後面詳細解釋。

- (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt {
    // 如果obj為指針為空,那麼就使用sqlite3_bind_null給該參數綁定SQL null。
    if ((!obj) || ((NSNull *)obj == [NSNull null])) {
        sqlite3_bind_null(pStmt, idx);
    }
    
    // FIXME - someday check the return codes on these binds.
    else if ([obj isKindOfClass:[NSData class]]) {
        const void *bytes = [obj bytes];
        if (!bytes) {
           // 如果obj是一個空的NSData對象
            // 不要直接將NULL指針作為參數值,否則sqlite會綁定一個NULL指針給參數,而不是一個blob對象(Binary Large Object)
            bytes = "";
        }
    // SQLITE_STATIC表示傳過來參數值的指針是不變的,所以完事後不需要銷毀它,與其相對的是
SQLITE_TRANSIENT
        sqlite3_bind_blob(pStmt, idx, bytes, (int)[obj length], SQLITE_STATIC);
    }
    // 如果obj是一個NSDate對象
    else if ([obj isKindOfClass:[NSDate class]]) {
       // 如果你自定義了Date格式,那麼就將該NSDate轉化為你定義的格式,並綁定到參數上
        // 如果沒有自定義Date格式,那麼預設使用timeIntervalSince1970來計算參數值進行綁定
        if (self.hasDateFormatter)
            sqlite3_bind_text(pStmt, idx, [[self stringFromDate:obj] UTF8String], -1, SQLITE_STATIC);
        else
            sqlite3_bind_double(pStmt, idx, [obj timeIntervalSince1970]);
    }
    // 如果是NSNumber對象,註意此處判斷obj類型的方法
    // @encode,@編譯器指令之一,返回一個給定類型編碼為一種內部表示的字元串(例如,@encode(int) → i),類似於 ANSI C 的 typeof 操作。蘋果的 Objective-C 運行時庫內部利用類型編碼幫助加快消息分發。
    else if ([obj isKindOfClass:[NSNumber class]]) {
        
        if (strcmp([obj objCType], @encode(char)) == 0) {
            sqlite3_bind_int(pStmt, idx, [obj charValue]);
        }
        else if (strcmp([obj objCType], @encode(unsigned char)) == 0) {
            sqlite3_bind_int(pStmt, idx, [obj unsignedCharValue]);
        }
        else if (strcmp([obj objCType], @encode(short)) == 0) {
            sqlite3_bind_int(pStmt, idx, [obj shortValue]);
        }
        else if (strcmp([obj objCType], @encode(unsigned short)) == 0) {
            sqlite3_bind_int(pStmt, idx, [obj unsignedShortValue]);
        }
        else if (strcmp([obj objCType], @encode(int)) == 0) {
            sqlite3_bind_int(pStmt, idx, [obj intValue]);
        }
        else if (strcmp([obj objCType], @encode(unsigned int)) == 0) {
            sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedIntValue]);
        }
        else if (strcmp([obj objCType], @encode(long)) == 0) {
            sqlite3_bind_int64(pStmt, idx, [obj longValue]);
        }
        else if (strcmp([obj objCType], @encode(unsigned long)) == 0) {
            sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongValue]);
        }
        else if (strcmp([obj objCType], @encode(long long)) == 0) {
            sqlite3_bind_int64(pStmt, idx, [obj longLongValue]);
        }
        else if (strcmp([obj objCType], @encode(unsigned long long)) == 0) {
            sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongLongValue]);
        }
        else if (strcmp([obj objCType], @encode(float)) == 0) {
            sqlite3_bind_double(pStmt, idx, [obj floatValue]);
        }
        else if (strcmp([obj objCType], @encode(double)) == 0) {
            sqlite3_bind_double(pStmt, idx, [obj doubleValue]);
        }
        else if (strcmp([obj objCType], @encode(BOOL)) == 0) { // bool使用sqlite3_bind_int來綁定的
            sqlite3_bind_int(pStmt, idx, ([obj boolValue] ? 1 : 0));
        }
        else {
            sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
        }
    }
    else {
        sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
    }
}

6. openWithFlags:系列函數


除了前面提到過的open函數外,FMDB還為我們提供了openWithFlags:系列函數,其本質是封裝了sqlite3_open_v2。

int sqlite3_open_v2(  
  const char *filename,   /* 資料庫名稱 (UTF-8) */
  sqlite3 **ppDb,         /* 輸出: SQLite資料庫對象 */
  int flags,              /* 標識符 */
  const char *zVfs        /* 想要使用的VFS名稱 */
);

對於sqlite3_open和sqlite3_open16函數,如果可能將以可讀可寫的方式打開資料庫,否則以只讀的方式打開資料庫。如果要打開的資料庫文件不存在,就新建一個。對於sqlite3_open_v2函數,情況就要複雜一些了,因為這個v2版本的函數強大就強大在它可以對打開(連接)資料庫的方式進行控制,具體是通過它的參數flags來完成。sqlite3_open_v2函數只支持UTF-8編碼的SQlite3資料庫文件。

如flags設置為SQLITE_OPEN_READONLY,則SQlite3資料庫文件以只讀的方式打開,如果該資料庫文件不存在,則sqlite3_open_v2函數執行失敗,返回一個error。如果flags設置為SQLITE_OPEN_READWRITE,則SQlite3資料庫文件以可讀可寫的方式打開,如果該資料庫文件本身被操作系統設置為防寫狀態,則以只讀的方式打開。如果該資料庫文件不存在,則sqlite3_open_v2函數執行失敗,返回一個error。如果flags設置為SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,則SQlite3資料庫文件以可讀可寫的方式打開,如果該資料庫文件不存在則新建一個。這也是sqlite3_open和sqlite3_open16函數的預設行為。除此之外,flags還可以設置為其他標誌,具體可以查看SQlite官方文檔。

參數zVfs允許客戶應用程式命名一個虛擬文件系統(Virtual File System)模塊,用來與資料庫連接。VFS作為SQlite library和底層存儲系統(如某個文件系統)之間的一個抽象層,通常客戶應用程式可以簡單的給該參數傳遞一個NULL指針,以使用預設的VFS模塊。

對於UTF-8編碼的SQlite3資料庫文件,推薦使用sqlite3_open_v2函數進行連接,它可以對資料庫文件的打開和處理操作進行更多的控制。

7. FMResultSet其他的獲取結果方式


之前只提到過FMResultSet的resultSetWithStatement:、close、next函數。其實FMResultSet除了使用next獲取查詢結果外,還有很多其他的介面可以查詢到結果。

一系列的*ForColumn:和*ForColumnIndex:(*表示對應的數據類型)函數都是用來獲取查詢結果的。這裡值得註意的是*ForColumn:函數本質是調用相應的*ForColumnIndex:函數。比如:

- (int)intForColumn:(NSString*)columnName {
    return [self intForColumnIndex:[self columnIndexForName:columnName]];
}

上述函數實現內部做了一個轉化,就是利用columIndexForName:函數查詢到這個columnName對應的索引值。而這個columnIndexForName:本質是根據_columnNameToIndexMap屬性獲取到列名稱(columnName)的對應列號(columnIdx)。_columnNameToIndexMap是一個NSMutableDictionary對象。其中key表示的是指定結果集中對應列的名稱,value表示的是指定結果集中對應的列號(columnIdx)。所以我們這裡主要看下columnNameToIndexMap的實現:

- (NSMutableDictionary *)columnNameToIndexMap {
    if (!_columnNameToIndexMap) {
        // 找出由statement指定的結果集中列的數目
        int columnCount = sqlite3_column_count([_statement statement]);
        _columnNameToIndexMap = [[NSMutableDictionary alloc] initWithCapacity:(NSUInteger)columnCount];
        int columnIdx = 0;
        // 將列號和該列對應名稱綁定在一起,組成_columnNameToIndexMap 
        for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
            [_columnNameToIndexMap setObject:[NSNumber numberWithInt:columnIdx]
                                      forKey:[[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)] lowercaseString]];
        }
    }
    return _columnNameToIndexMap;
}

這時我們再回頭看看*ForColumnIndex:函數的實現。它的本質就是調用sqlite3_column_*(*表示對應的數據類型),也就是從statement中獲取到對應列號的數據,比如

- (int)intForColumnIndex:(int)columnIdx {
    return sqlite3_column_int([_statement statement], columnIdx);
}

8. FMDB的加解密


FMDB中使用- [FMDatabase setKey:]和- [FMDatabase setKeyWithData:]輸入資料庫密碼以求驗證用戶身份,使用- [FMDatabase rekey:]和- [FMDatabase rekeyWithData:]來給資料庫設置密碼或者清除密碼。這兩類函數分別對sqlite3_key和sqlite3_rekey函數進行了封裝。

int sqlite3_key( sqlite3 *db, const void *pKey, int nKey)

db 是指定資料庫,pKey 是密鑰,nKey 是密鑰長度。例:sqlite3_key( db, "abc", 3);

sqlite3_key是輸入密鑰,如果資料庫已加密必須先執行此函數並輸入正確密鑰才能進行操作,如果資料庫沒有加密,執行此函數後進行資料庫操作反而會出現“此資料庫已加密或不是一個資料庫文件”的錯誤。

int sqlite3_rekey( sqlite3 *db, const void *pKey, int nKey)

參數同sqlite3_key。
sqlite3_rekey是變更密鑰或給沒有加密的資料庫添加密鑰或清空密鑰,變更密鑰或清空密鑰前必須先正確執行 sqlite3_key。在正確執行 sqlite3_rekey 之後在 sqlite3_close 關閉資料庫之前可以正常操作資料庫,不需要再執行 sqlite3_key。
清空密鑰為 sqlite3_rekey( db, NULL, 0)。

// 下麵的代碼比較簡單,就不過多解釋了。核心就是sqlite3_key和sqlite3_rekey這兩個函數
// 使用rekey:和setKey:之前先要將對應NSString對象轉化為NSData數據
- (BOOL)rekey:(NSString*)key {
    NSData *keyData = [NSData dataWithBytes:(void *)[key UTF8String] length:(NSUInteger)strlen([key UTF8String])];
    
    return [self rekeyWithData:keyData];
}

- (BOOL)rekeyWithData:(NSData *)keyData {
#ifdef SQLITE_HAS_CODEC
    if (!keyData) {
        return NO;
    }
    
    int rc = sqlite3_rekey(_db, [keyData bytes], (int)[keyData length]);
    
    if (rc != SQLITE_OK) {
        NSLog(@"error on rekey: %d", rc);
        NSLog(@"%@", [self lastErrorMessage]);
    }
    
    return (rc == SQLITE_OK);
#else
#pragma unused(keyData)
    return NO;
#endif
}

- (BOOL)setKey:(NSString*)key {
    NSData *keyData = [NSData dataWithBytes:[key UTF8String] length:(NSUInteger)strlen([key UTF8String])];
    
    return [self setKeyWithData:keyData];
}

- (BOOL)setKeyWithData:(NSData *)keyData {
#ifdef SQLITE_HAS_CODEC
    if (!keyData) {
        return NO;
    }
    
    int rc = sqlite3_key(_db, [keyData bytes], (int)[keyData length]);
    
    return (rc == SQLITE_OK);
#else
#pragma unused(keyData)
    return NO;
#endif
}

9. 參考文章



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • jQuery獲取Select選擇的Text和Value: 語法解釋: 1. $("#select_id").change(function(){//code...}); //為Select添加事件,當選擇其中一項時觸發 2. var checkText=$("#select_id").find("o
  • //get base URL var _urlstr = window.location.href; if (_urlstr.indexOf("?") > -1) { _urlstr = _urlstr.substring(0, _urlstr.lastIndexOf("?")); } _urlst
  • 有幾種方法:①CSS處理方法(僅IE) #backGroundImg { background-image: url("X.png"); background-repeat: no-repeat; filter:progid:dximagetransform.microsoft.alphaimage
  • 在數據添加到DOM時候,我們可以需要對內容進行HtmlEncode或JavaScriptEncode,以預防XSS攻擊。 JavaScriptEncode 使用“\”對特殊字元進行轉義,除數字字母之外,小於127的字元編碼使用16進位“\xHH”的方式進行編碼,大於用unicode(非常嚴格模式)。
  • magic Toolbox是一款處理圖像的javascript插件,這款插件號稱用戶僅需五分鐘便可讓網站擁有酷炫的效果。我個人認為在用戶體驗方面做得相當不錯,最讓我滿意的是支持PC端及跨移動設備的用戶體驗。magicToolBox主要有六大功能,分別為:Magic Zoom、Magic Zoom P
  • firefox不支持background-position-x background-position-y,使用background-position:5px 5px;
  • 九宮格的樣子以及游戲想大家接觸過,想當年也是玩了好多九宮格游戲,其中最經典的就是1-9這九個數字填進格子,橫豎向、對角相加數值相等這個益智游戲了。今天,在一個qq群里有人問九宮格做法,我就突然想起乾前端這麼長時間還真的沒做過九宮格。今天就把我做好的實例發上來,共看客們批評教育。
  • 寫了一段時間的心情,今天開始寫第一篇算是跟技術有點沾邊的文章,將今天在公司對一個老項目改版中涉及的代碼粘貼出來,也算開個張。 由於以前主要是做後端開發,對前端代碼瞭解不多,加上有一段時間沒有實際動手寫代碼,思路雖然還在,但真正寫起來才發現不是那麼容易,所以記錄下來,有很好的紀念意義。 主要場景是解決
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...