iOS开发中SQLite死锁问题(OC版)

iOS开发中SQLite死锁问题(OC版)

翻自己的博客发现自己以前写过如何使用SQLite,然后当时好像没提死锁的事情。因为SQLite只支持单线程的,所以如果碰到多个功能需要访问SQLite的,很有可能引起死锁问题。当然了,现在可能都用Swift不用OC了,但是为了方案的完整性,我还是要记录一下这件事情。

那么比较常见的就是@synchronized关键字,这个Java的同学应该很熟悉,过多的解释也没什么好说的,前人都已经栽树成林了,我没必要再多啰嗦了。

以下是两年前的代码了,也是为了补之前那篇博客《iOS SQLite3 “增删改查”(Objective-C)》的坑

代码或者博客写的不好,还请多多指教,感谢!

//
//  SQLite.h
//
//  Created by Zhan on 16/9/8.
//  Copyright © 2016年 Michael Zhan. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <sqlite3.h>

typedef void (^SelectBlock)(NSMutableArray * mutableArray);
typedef void (^DBFailureBlock)(void);
typedef void (^Success)(void);
typedef void (^Failed)(void);

@interface ContactDB : NSObject

+ (ContactDB *)sharedDataBaseHandle;
-(sqlite3 *)openDB;
-(void)createTable;
-(void)dropTable:(Success)successBlock failed:(Failed)failedBlock;

-(void)insert_name:(NSString *)name phone:(NSString *)phone headimg:(NSString *)path deptId:(NSString *)deptId deptName:(NSString *)deptName uppercase:(NSString *)uppercase userId:(NSString *)userId mail:(NSString *)mail;

-(void)loadContact:(SelectBlock)successBlock failure:(DBFailureBlock)failureBlock;

-(void)select_name:(NSString *)name success:(SelectBlock)successBlock failure:(DBFailureBlock)failureBlock;
-(void)select_phone:(NSString *)phone success:(SelectBlock)successBlock failure:(DBFailureBlock)failureBlock;
-(void)select_uppercase:(NSString *)uppercase success:(SelectBlock)successBlock failure:(DBFailureBlock)failureBlock;

-(void)select_userId:(NSString *)userId success:(SelectBlock)successBlock failure:(DBFailureBlock)failureBlock;

-(void)select_deptName:(NSString *)deptName success:(SelectBlock)successBlock failure:(DBFailureBlock)failureBlock;

-(void)closeDB;

+ (void)destroyDealloc;

@end
//
//  SQLite.m
//
//  Created by Zhan on 16/9/8.
//  Copyright © 2016年 Michael Zhan. All rights reserved.
//

#import "ContactDB.h"

@implementation ContactDB

static ContactDB *manager = nil;
static sqlite3 * db = nil;  //设置句柄 通过句柄对数据库进行操作
static bool isOperation;

+(ContactDB *)sharedDataBaseHandle{
    if (manager == nil) {
        manager = [[ContactDB alloc] init];
        isOperation = false;
    }
    return manager;
}

-(sqlite3 *)openDB{
    if (db != nil) {
        return db;
    }
    NSArray *test = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString * fileName = [[test lastObject]stringByAppendingPathComponent:@"Contact"];
    NSLog(@"%@",fileName);  //打印路径
    sqlite3_open(fileName.UTF8String, &db);
    [self dropTable:^{
        [self createTable];
    } failed:^{
        
    }];
    return db;
}

-(void)createTable{
    NSString * sql = [[@"create table if not exists "stringByAppendingString:@"Contact"]stringByAppendingString:@" (id integer primary key autoincrement not null, name text not null, phone text not null, headimg text null, deptId text not null, deptName text not null, uppercase text null, userId text not null, mail text null);"];
    //    NSLog(@"sql : %@",sql);
    if(db == nil){
        return;
    }
    sqlite3_exec(db, sql.UTF8String, NULL, NULL, NULL);
//    NSLog(@"create contact error : %s",err);
}

-(void)loadContact:(SelectBlock)successBlock failure:(DBFailureBlock)failureBlock{
    sqlite3_stmt *statement = nil;
    NSString *sql = @"select * from Contact";
    //    NSLog(@"sql == %@",sql);
//    BOOL hasRow = false;
    if(db == nil && isOperation == true){
        return;
    }
    NSMutableArray * mutableArray = [[NSMutableArray alloc]init];
    isOperation = true;
    @synchronized(self){
        if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, nil) == SQLITE_OK) {
            while (sqlite3_step(statement) == SQLITE_ROW) {
    //            hasRow = true;
                NSMutableDictionary *mutableDictionary = [[NSMutableDictionary alloc]init];
                
                char *name = (char *)sqlite3_column_text(statement, 1);
                NSString *nameStr = [[NSString alloc] initWithUTF8String:name];
                [mutableDictionary setObject:nameStr forKey:@"name"];
                
                char *phone = (char *)sqlite3_column_text(statement, 2);
                NSString *phoneStr = [[NSString alloc]initWithUTF8String:phone];
                [mutableDictionary setObject:phoneStr forKey:@"phone"];
                
                char *headimg = (char *)sqlite3_column_text(statement, 3);
                NSString *headimgStr = [[NSString alloc]initWithUTF8String:headimg];
                [mutableDictionary setObject:headimgStr forKey:@"headimg"];
                
                char *deptId = (char *)sqlite3_column_text(statement, 4);
                NSString *deptIdStr = [[NSString alloc]initWithUTF8String:deptId];
                [mutableDictionary setObject:deptIdStr forKey:@"deptId"];
                
                char *deptName = (char *)sqlite3_column_text(statement, 5);
                NSString *deptNameStr = [[NSString alloc]initWithUTF8String:deptName];
                [mutableDictionary setObject:deptNameStr forKey:@"deptName"];
                
                char *uppercase = (char *)sqlite3_column_text(statement, 6);
                NSString *uppercasetStr = [[NSString alloc]initWithUTF8String:uppercase];
                [mutableDictionary setObject:uppercasetStr forKey:@"uppercase"];
                
                char *userId = (char *)sqlite3_column_text(statement, 7);
                NSString *userIdStr = [[NSString alloc]initWithUTF8String:userId];
                [mutableDictionary setObject:userIdStr forKey:@"userId"];
                
                char *mail = (char *)sqlite3_column_text(statement, 8);
                NSString *mailStr = [[NSString alloc]initWithUTF8String:mail];
                [mutableDictionary setObject:mailStr forKey:@"mail"];
                
                [mutableArray addObject:mutableDictionary];
            }
            isOperation = false;
            successBlock(mutableArray);
        }else{
            failureBlock();
        }
        sqlite3_finalize(statement);
    }
}

- (void)insert_name:(NSString *)name phone:(NSString *)phone headimg:(NSString *)path deptId:(NSString *)deptId deptName:(NSString *)deptName uppercase:(NSString *)uppercase userId:(NSString *)userId mail:(NSString *)mail{
        BOOL hasInfo = false;
        sqlite3_stmt *statement = nil;
        NSString *select = [[@"select * from Contact where userId = '"stringByAppendingString:userId]stringByAppendingString:@"'"];
        @synchronized (self) {
            if(db == nil && isOperation == true){
                return;
            }
            isOperation = true;
            if (sqlite3_prepare_v2(db, [select UTF8String], -1, &statement, nil) == SQLITE_OK) {
                while (sqlite3_step(statement) == SQLITE_ROW) {
                    hasInfo = true;
                    break;
                }
            }
            uppercase = [uppercase stringByReplacingOccurrencesOfString:@" " withString:@""];
            NSString * sql;
            if(hasInfo){
                sql = [NSString stringWithFormat:@"update Contact set name = '%@', phone = '%@', headimg = '%@', deptId = '%@', deptName = '%@', uppercase = '%@', mail = '%@' where userId = '%@'",name, phone, path, deptId, deptName, uppercase, mail, userId];
            }else{
                sql = [NSString stringWithFormat:@"insert into Contact (name, phone, headimg, deptId, deptName, uppercase, userId, mail) VALUES('%@', '%@', '%@', '%@', '%@', '%@', '%@', '%@')", name, phone, path, deptId, deptName, uppercase, userId, mail];
            }
            sqlite3_exec(db, sql.UTF8String, NULL, NULL, NULL);
            isOperation = false;
            sqlite3_finalize(statement);
        }
  
-(void)select_name:(NSString *)name success:(SelectBlock)successBlock failure:(DBFailureBlock)failureBlock{
    sqlite3_stmt *statement = nil;
    NSString *sql = [[@"select * from Contact where name LIKE '%"stringByAppendingString:name]stringByAppendingString:@"%'"];
    NSMutableArray * mutableArray = [[NSMutableArray alloc]init];
    if(db == nil && isOperation == true){
        return;
    }
    isOperation = true;
    @synchronized (self) {
        if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, nil) == SQLITE_OK) {
            while (sqlite3_step(statement) == SQLITE_ROW) {
                NSMutableDictionary *mutableDictionary = [[NSMutableDictionary alloc]init];
                
                char *name = (char *)sqlite3_column_text(statement, 1);
                NSString *nameStr = [[NSString alloc] initWithUTF8String:name];
                [mutableDictionary setObject:nameStr forKey:@"name"];
                
                char *phone = (char *)sqlite3_column_text(statement, 2);
                NSString *phoneStr = [[NSString alloc]initWithUTF8String:phone];
                [mutableDictionary setObject:phoneStr forKey:@"phone"];
                
                char *headimg = (char *)sqlite3_column_text(statement, 3);
                NSString *headimgStr = [[NSString alloc]initWithUTF8String:headimg];
                [mutableDictionary setObject:headimgStr forKey:@"headimg"];
                
                char *deptId = (char *)sqlite3_column_text(statement, 4);
                NSString *deptIdStr = [[NSString alloc]initWithUTF8String:deptId];
                [mutableDictionary setObject:deptIdStr forKey:@"deptId"];
                
                char *deptName = (char *)sqlite3_column_text(statement, 5);
                NSString *deptNameStr = [[NSString alloc]initWithUTF8String:deptName];
                [mutableDictionary setObject:deptNameStr forKey:@"deptName"];
                
                char *uppercase = (char *)sqlite3_column_text(statement, 6);
                NSString *uppercasetStr = [[NSString alloc]initWithUTF8String:uppercase];
                [mutableDictionary setObject:uppercasetStr forKey:@"uppercase"];
                
                char *userId = (char *)sqlite3_column_text(statement, 7);
                NSString *userIdStr = [[NSString alloc]initWithUTF8String:userId];
                [mutableDictionary setObject:userIdStr forKey:@"userId"];
                
                char *mail = (char *)sqlite3_column_text(statement, 8);
                NSString *mailStr = [[NSString alloc]initWithUTF8String:mail];
                [mutableDictionary setObject:mailStr forKey:@"mail"];
                
                [mutableArray addObject:mutableDictionary];
            }
            isOperation = false;
            successBlock(mutableArray);
        }else{
            failureBlock();
        }
        sqlite3_finalize(statement);
    }
}

-(void)select_phone:(NSString *)phone success:(SelectBlock)successBlock failure:(DBFailureBlock)failureBlock{
    sqlite3_stmt *statement = nil;
    NSString *sql = [[@"select * from Contact where phone LIKE '%"stringByAppendingString:phone]stringByAppendingString:@"%'"];
    //    NSLog(@"sql == %@",sql);
    NSMutableArray * mutableArray = [[NSMutableArray alloc]init];
    if(db == nil && isOperation == true){
        return;
    }
    isOperation = true;
    @synchronized (self) {
        if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, nil) == SQLITE_OK) {
            while (sqlite3_step(statement) == SQLITE_ROW) {
                NSMutableDictionary *mutableDictionary = [[NSMutableDictionary alloc]init];
                
                char *name = (char *)sqlite3_column_text(statement, 1);
                NSString *nameStr = [[NSString alloc] initWithUTF8String:name];
                [mutableDictionary setObject:nameStr forKey:@"name"];
                
                char *phone = (char *)sqlite3_column_text(statement, 2);
                NSString *phoneStr = [[NSString alloc]initWithUTF8String:phone];
                [mutableDictionary setObject:phoneStr forKey:@"phone"];
                
                char *headimg = (char *)sqlite3_column_text(statement, 3);
                NSString *headimgStr = [[NSString alloc]initWithUTF8String:headimg];
                [mutableDictionary setObject:headimgStr forKey:@"headimg"];
                
                char *deptId = (char *)sqlite3_column_text(statement, 4);
                NSString *deptIdStr = [[NSString alloc]initWithUTF8String:deptId];
                [mutableDictionary setObject:deptIdStr forKey:@"deptId"];
                
                char *deptName = (char *)sqlite3_column_text(statement, 5);
                NSString *deptNameStr = [[NSString alloc]initWithUTF8String:deptName];
                [mutableDictionary setObject:deptNameStr forKey:@"deptName"];
                
                char *uppercase = (char *)sqlite3_column_text(statement, 6);
                NSString *uppercasetStr = [[NSString alloc]initWithUTF8String:uppercase];
                [mutableDictionary setObject:uppercasetStr forKey:@"uppercase"];
                
                char *userId = (char *)sqlite3_column_text(statement, 7);
                NSString *userIdStr = [[NSString alloc]initWithUTF8String:userId];
                [mutableDictionary setObject:userIdStr forKey:@"userId"];
                
                char *mail = (char *)sqlite3_column_text(statement, 8);
                NSString *mailStr = [[NSString alloc]initWithUTF8String:mail];
                [mutableDictionary setObject:mailStr forKey:@"mail"];
                
                [mutableArray addObject:mutableDictionary];
            }
            isOperation = false;
            successBlock(mutableArray);
        }else{
            failureBlock();
        }
        sqlite3_finalize(statement);
    }
}

-(void)select_uppercase:(NSString *)uppercase success:(SelectBlock)successBlock failure:(DBFailureBlock)failureBlock{
    uppercase = [uppercase uppercaseString];
    uppercase = [uppercase stringByReplacingOccurrencesOfString:@" " withString:@""];
    sqlite3_stmt *statement = nil;
    NSString *sql = [[@"select * from Contact where uppercase LIKE '%"stringByAppendingString:uppercase]stringByAppendingString:@"%'"];
    //    NSLog(@"sql == %@",sql);
    NSMutableArray * mutableArray = [[NSMutableArray alloc]init];
    if(db == nil && isOperation == true){
        return;
    }
    isOperation = true;
    @synchronized (self) {
        if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, nil) == SQLITE_OK) {
            while (sqlite3_step(statement) == SQLITE_ROW) {
                NSMutableDictionary *mutableDictionary = [[NSMutableDictionary alloc]init];
                
                char *name = (char *)sqlite3_column_text(statement, 1);
                NSString *nameStr = [[NSString alloc] initWithUTF8String:name];
                [mutableDictionary setObject:nameStr forKey:@"name"];
                
                char *phone = (char *)sqlite3_column_text(statement, 2);
                NSString *phoneStr = [[NSString alloc]initWithUTF8String:phone];
                [mutableDictionary setObject:phoneStr forKey:@"phone"];
                
                char *headimg = (char *)sqlite3_column_text(statement, 3);
                NSString *headimgStr = [[NSString alloc]initWithUTF8String:headimg];
                [mutableDictionary setObject:headimgStr forKey:@"headimg"];
                
                char *deptId = (char *)sqlite3_column_text(statement, 4);
                NSString *deptIdStr = [[NSString alloc]initWithUTF8String:deptId];
                [mutableDictionary setObject:deptIdStr forKey:@"deptId"];
                
                char *deptName = (char *)sqlite3_column_text(statement, 5);
                NSString *deptNameStr = [[NSString alloc]initWithUTF8String:deptName];
                [mutableDictionary setObject:deptNameStr forKey:@"deptName"];
                
                char *userId = (char *)sqlite3_column_text(statement, 7);
                NSString *userIdStr = [[NSString alloc]initWithUTF8String:userId];
                [mutableDictionary setObject:userIdStr forKey:@"userId"];
                
                char *uppercase = (char *)sqlite3_column_text(statement, 6);
                NSString *uppercasetStr = [[NSString alloc]initWithUTF8String:uppercase];
                [mutableDictionary setObject:uppercasetStr forKey:@"uppercase"];
                
                [mutableArray addObject:mutableDictionary];
            }
            isOperation = false;
            successBlock(mutableArray);
        }else{
            failureBlock();
        }
        sqlite3_finalize(statement);
    }
}

-(void)select_userId:(NSString *)userId success:(SelectBlock)successBlock failure:(DBFailureBlock)failureBlock{
    sqlite3_stmt *statement = nil;
    NSString *sql = [[@"select * from Contact where userId = '"stringByAppendingString:userId]stringByAppendingString:@"'"];
    if(db == nil && isOperation == true){
        return;
    }
    NSMutableArray * mutableArray = [[NSMutableArray alloc]init];
    @synchronized (self) {
        if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, nil) == SQLITE_OK) {
            while (sqlite3_step(statement) == SQLITE_ROW) {
                NSMutableDictionary *mutableDictionary = [[NSMutableDictionary alloc]init];
                
                char *name = (char *)sqlite3_column_text(statement, 1);
                NSString *nameStr = [[NSString alloc] initWithUTF8String:name];
                [mutableDictionary setObject:nameStr forKey:@"name"];
                
                char *phone = (char *)sqlite3_column_text(statement, 2);
                NSString *phoneStr = [[NSString alloc]initWithUTF8String:phone];
                [mutableDictionary setObject:phoneStr forKey:@"phone"];
                
                char *headimg = (char *)sqlite3_column_text(statement, 3);
                NSString *headimgStr = [[NSString alloc]initWithUTF8String:headimg];
                [mutableDictionary setObject:headimgStr forKey:@"headimg"];
                
                char *deptId = (char *)sqlite3_column_text(statement, 4);
                NSString *deptIdStr = [[NSString alloc]initWithUTF8String:deptId];
                [mutableDictionary setObject:deptIdStr forKey:@"deptId"];
                
                char *deptName = (char *)sqlite3_column_text(statement, 5);
                NSString *deptNameStr = [[NSString alloc]initWithUTF8String:deptName];
                [mutableDictionary setObject:deptNameStr forKey:@"deptName"];
                
                char *uppercase = (char *)sqlite3_column_text(statement, 6);
                NSString *uppercasetStr = [[NSString alloc]initWithUTF8String:uppercase];
                [mutableDictionary setObject:uppercasetStr forKey:@"uppercase"];
                
                char *userId = (char *)sqlite3_column_text(statement, 7);
                NSString *userIdStr = [[NSString alloc]initWithUTF8String:userId];
                [mutableDictionary setObject:userIdStr forKey:@"userId"];
                
                char *mail = (char *)sqlite3_column_text(statement, 8);
                NSString *mailStr = [[NSString alloc]initWithUTF8String:mail];
                [mutableDictionary setObject:mailStr forKey:@"mail"];
                
                [mutableArray addObject:mutableDictionary];
            }
            isOperation = false;
            successBlock(mutableArray);
        }else{
            failureBlock();
        }
        sqlite3_finalize(statement);
    }
}

-(void)select_deptName:(NSString *)deptName success:(SelectBlock)successBlock failure:(DBFailureBlock)failureBlock{
    sqlite3_stmt *statement = nil;
    NSString *sql = [[@"select * from Contact where deptName = '"stringByAppendingString:deptName]stringByAppendingString:@"'"];
//    NSLog(@"sql == %@",sql);
    if(db == nil && isOperation == true){
        return;
    }
    NSMutableArray * mutableArray = [[NSMutableArray alloc]init];
    @synchronized (self) {
        if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, nil) == SQLITE_OK) {
            while (sqlite3_step(statement) == SQLITE_ROW) {
                NSMutableDictionary *mutableDictionary = [[NSMutableDictionary alloc]init];
                
                char *name = (char *)sqlite3_column_text(statement, 1);
                NSString *nameStr = [[NSString alloc] initWithUTF8String:name];
                [mutableDictionary setObject:nameStr forKey:@"name"];
                
                char *phone = (char *)sqlite3_column_text(statement, 2);
                NSString *phoneStr = [[NSString alloc]initWithUTF8String:phone];
                [mutableDictionary setObject:phoneStr forKey:@"phone"];
                
                char *headimg = (char *)sqlite3_column_text(statement, 3);
                NSString *headimgStr = [[NSString alloc]initWithUTF8String:headimg];
                [mutableDictionary setObject:headimgStr forKey:@"headimg"];
                
                char *deptId = (char *)sqlite3_column_text(statement, 4);
                NSString *deptIdStr = [[NSString alloc]initWithUTF8String:deptId];
                [mutableDictionary setObject:deptIdStr forKey:@"deptId"];
                
                char *deptName = (char *)sqlite3_column_text(statement, 5);
                NSString *deptNameStr = [[NSString alloc]initWithUTF8String:deptName];
                [mutableDictionary setObject:deptNameStr forKey:@"deptName"];
                
                char *uppercase = (char *)sqlite3_column_text(statement, 6);
                NSString *uppercasetStr = [[NSString alloc]initWithUTF8String:uppercase];
                [mutableDictionary setObject:uppercasetStr forKey:@"uppercase"];
                
                char *userId = (char *)sqlite3_column_text(statement, 7);
                NSString *userIdStr = [[NSString alloc]initWithUTF8String:userId];
                [mutableDictionary setObject:userIdStr forKey:@"userId"];
                
                char *mail = (char *)sqlite3_column_text(statement, 8);
                NSString *mailStr = [[NSString alloc]initWithUTF8String:mail];
                [mutableDictionary setObject:mailStr forKey:@"mail"];
                
                [mutableArray addObject:mutableDictionary];
            }
            isOperation = false;
            successBlock(mutableArray);
        }else{
            failureBlock();
        }
            sqlite3_finalize(statement);
    }
}

-(void)dropTable:(Success)successBlock failed:(Failed)failedBlock{
    NSString * sql = [@"drop table if exists "stringByAppendingString:@"Contact"];
    NSLog(@"sql : %@",sql);
    @synchronized (self) {
        @try {
           sqlite3_exec(db, sql.UTF8String, NULL, NULL, NULL);
           successBlock();
        } @catch (NSException *exception) {
           failedBlock();
        } @finally {
            
        }
    }
//    sqlite3_exec(db, sql.UTF8String, NULL, NULL, NULL);
}

-(void)closeDB{
    if (sqlite3_close(db) == SQLITE_OK){
        db = nil;
    }
}

+ (void)destroyDealloc{
    if (nil == manager){
        return;
    }
    manager = nil;
}

@end
本次更新对于支持库来说,接口改动不大,模块的话是尽量和支持库接口保持一致,所以对于以前的模块接口可能改动就大了。 要解决多线程问题,主要是对sqlite3的锁机制进行了解,和事务有密切的关系。本次更新的本在开始事务时,增加了一个 事务锁状态 的参数。 拿简单的话来描述就是 开始事务 时如果 设置了 事务锁状态_ 立即 参数,那么这个时候其他线程的连接就不能写操作了,但是可以读操作,但是在提交事务时也要等待所有读操作完成了才能提交。 设置了 事务锁状态_ 独占 参数,那么这个时候其他线程的连接读写都不可以了。 在等待的时候就处于繁忙状态,我们可以设置 数据库.繁忙超时 ()来设置等待时间。 还有种情况还是会 死锁 ,就是记录集没有关闭,而且这次更新的支持库和模块都是必须 手动关闭记录集 的,所以一定注意。 当然多进程和多线程是一个道理,具体操作看例子。 esqlite3 V1.1 相对于1.0的更新 1、增加了全局命令: S3互斥体进入 S3互斥体退出 S3聚合上下文 S3取数据库自上下文 2、增加了zySqlite数据库 命令 繁忙超时 繁忙处理 取文件名 是否只读 取互斥体 是否自动提交 进度处理 取下一记录集 取总影响行 3、增加了 zySqlite记录集 命令 是否繁忙 是否只读 取数据库句柄 取行数 4、数据库.开始事务() 增加了 事务锁状态 参数,此参数在多线程非常重要。 5、记录集必须手动关闭,任何内部方法都不再自动关闭。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值