sqxclib 是在 C 语言和 SQL(或 JSON ...等)之间转换数据的库。它提供 ORM 的功能和 C++ 包装器 (C++ wrapper)
项目地址: GitHub, Gitee
用户可以使用 C99 指定初始化(designated initializer) 或 C++ 聚合初始化(aggregate initialization) 定义常量 SQL 表、列、迁移, 这可以减少制作架构时的运行时间,请参阅 doc/schema-builder-constant.cn.md。 当然也可以使用 C 函数 或 C++ 方法 动态执行这些操作。
所有定义的 SQL 表和列 都可以用于解析 JSON 对象和字段。也可以从 SQL 列 解析 JSON 对象和数组。
BLOB 支持,支持的类型列表位于 doc/SqTable.cn.md 中。
自定义类型映射。
可以独立使用的查询生成器。见 doc/SqQuery.cn.md。
可以在低端硬件上工作。
单一头文件 〈 sqxclib.h 〉 (注意: 不包含特殊宏和支持库)
命令行工具可以生成迁移并进行迁移。见 doc/SqApp.cn.md。
支持 SQLite, MySQL / MariaDB, PostgreSQL。
提供项目模板。见目录 project-template。
定义映射到数据库表 "users" 的 C 结构化数据类型。
// 如果您使用 C 语言,请使用 'typedef' 为结构类型赋予新名称。
typedef struct User User;
struct User {
int id; // 主键
char *name;
char *email;
int city_id; // 外键
time_t created_at;
time_t updated_at;
#ifdef __cplusplus // C++ 数据类型
std::string strCpp;
std::vector<int> intsCpp;
#endif
};
使用 C++ 方法在 schema_v1 中定义表和列 (动态)
/* 为 C++ STL 定义全局类型 */
Sq::TypeStl<std::vector<int>> SqTypeIntVector(SQ_TYPE_INT); // C++ std::vector
/* 创建架构并指定版本号为 1 */
schema_v1 = new Sq::Schema(1, "Ver 1");
// 创建表 "users",然后将列添加到表中。
table = schema_v1->create<User>("users");
// 主键 PRIMARY KEY
table->integer("id", &User::id)->primary();
// VARCHAR
table->string("name", &User::name);
// VARCHAR(60)
table->string("email", &User::email, 60);
// DEFAULT CURRENT_TIMESTAMP
table->timestamp("created_at", &User::created_at)->useCurrent();
// DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
table->timestamp("updated_at", &User::updated_at)->useCurrent()->useCurrentOnUpdate();
// C++ 类型 - std::string 和 std::vector
table->stdstring("strCpp", &User::strCpp);
table->custom("intsCpp", &User::intsCpp, &SqTypeIntVector);
// 外键 FOREIGN KEY
table->integer("city_id", &User::city_id)->reference("cities", "id");
// 约束外键 CONSTRAINT FOREIGN KEY
table->foreign("users_city_id_foreign", "city_id")
->reference("cities", "id")->onDelete("NO ACTION")->onUpdate("NO ACTION");
// 创建索引 CREATE INDEX
table->index("users_id_index", "id");
/* 如果您将当前时间存储在列和成员中并且它们使用默认名称 - 'created_at' 和 'updated_at',
您可以使用下面的行替换上述 2 个 timestamp() 方法。
*/
// table->timestamps<User>();
使用 C++ 方法更改 schema_v2 中的表和列 (动态)
/* 创建架构并指定版本号为 2 */
schema_v2 = new Sq::Schema(2, "Ver 2");
// 更改表 "users"
table = schema_v2->alter("users");
// 向表中添加列
table->integer("test_add", &User::test_add);
// 更改表中的列
table->integer("city_id", &User::city_id)->change();
// 删除约束外键 DROP CONSTRAINT FOREIGN KEY
table->dropForeign("users_city_id_foreign");
// 删除列
table->dropColumn("name");
// 重命名列
table->renameColumn("email", "email2");
使用 C 函数在 schema_v1 中定义表和列 (动态)
/* 创建架构并指定版本号为 1 */
schema_v1 = sq_schema_new_ver(1, "Ver 1");
// 创建表 "users"
table = sq_schema_create(schema_v1, "users", User);
// 主键 PRIMARY KEY
column = sq_table_add_integer(table, "id", offsetof(User, id));
sq_column_primary(column);
// VARCHAR
column = sq_table_add_string(table, "name", offsetof(User, name), -1);
// VARCHAR(60)
column = sq_table_add_string(table, "email", offsetof(User, email), 60);
// DEFAULT CURRENT_TIMESTAMP
column = sq_table_add_timestamp(table, "created_at", offset(User, created_at));
sq_column_use_current(column);
// DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
column = sq_table_add_timestamp(table, "updated_at", offset(User, updated_at));
sq_column_use_current(column);
sq_column_use_current_on_update(column);
// 外键 FOREIGN KEY. 注意:此处使用 NULL 终止的参数列表
column = sq_table_add_integer(table, "city_id", offsetof(User, city_id));
sq_column_reference(column, "cities", "id", NULL);
// 约束外键 CONSTRAINT FOREIGN KEY. 注意:此处使用 NULL 终止的参数列表
column = sq_table_add_foreign(table, "users_city_id_foreign", "city_id", NULL);
sq_column_reference(column, "cities", "id", NULL);
sq_column_on_delete(column, "NO ACTION");
sq_column_on_update(column, "NO ACTION");
// 创建索引 CREATE INDEX. 注意:此处使用 NULL 终止的参数列表
column = sq_table_add_index(table, "users_id_index", "id", NULL);
/* 如果您将当前时间存储在列和成员中并且它们使用默认名称 - 'created_at' 和 'updated_at',
您可以使用下面的行替换上述 2 个 sq_table_add_timestamp() 函数。
*/
// sq_table_add_timestamps_struct(table, User);
使用 C 函数更改 schema_v2 中的表和列 (动态)
/* 创建架构并指定版本号为 2 */
schema_v2 = sq_schema_new_ver(2, "Ver 2");
// 更改表 "users"
table = sq_schema_alter(schema_v2, "users", NULL);
// 将列添加到表中
column = sq_table_add_integer(table, "test_add", offsetof(User, test_add));
// 更改表中的列
column = sq_table_add_integer(table, "city_id", offsetof(User, city_id));
sq_column_change(column);
// 删除约束外键 DROP CONSTRAINT FOREIGN KEY
sq_table_drop_foreign(table, "users_city_id_foreign");
// 删除列
sq_table_drop_column(table, "name");
// 重命名列
sq_table_rename_column(table, "email", "email2");
还有更多...
Sqdb 是数据库产品(SQLite、MySQL 等)的基础结构。您可以在 doc/Sqdb.cn.md 中获得更多描述和示例。
使用 C 函数打开 SQLite 数据库
// 数据库配置
SqdbConfigSqlite config = {
.folder = "/path",
.extension = "db"
};
// 接口
Sqdb *db;
db = sqdb_sqlite_new(&config);
// db = sqdb_sqlite_new(NULL); // 如果 config 为 NULL,则使用默认设置。
storage = sq_storage_new(db);
sq_storage_open(storage, "sqxc_local"); // 这将打开文件 "sqxc_local.db"
使用 C 函数打开 MySQL 数据库
// 数据库配置
SqdbConfigMysql config = {
.host = "localhost",
.port = 3306,
.user = "name",
.password = "xxx"
};
// 接口
Sqdb *db;
db = sqdb_mysql_new(&config);
// db = sqdb_mysql_new(NULL); // 如果 config 为 NULL,则使用默认设置。
storage = sq_storage_new(db);
sq_storage_open(storage, "sqxc_local");
使用 C++ 方法打开 SQLite 数据库
// 数据库配置
Sq::DbConfigSqlite config = {
"/path", // .folder = "/path",
"db", // .extension = "db",
};
// 接口
Sq::DbMethod *db;
db = new Sq::DbSqlite(config);
// db = new Sq::DbSqlite(NULL); // 如果 config 为 NULL,则使用默认设置。
storage = new Sq::Storage(db);
storage->open("sqxc_local"); // 这将打开文件 "sqxc_local.db"
您可以在 doc/database-migrations.cn.md 中获得有关迁移和架构的更多描述。
使用 C++ 方法迁移架构并同步到数据库
// 迁移 'schema_v1' 和 'schema_v2'
storage->migrate(schema_v1);
storage->migrate(schema_v2);
// 将架构同步到数据库并更新 'storage' 中的架构
// 这主要由 SQLite 使用
storage->migrate(NULL);
// 释放未使用的 'schema_v1' 和 'schema_v2'
delete schema_v1;
delete schema_v2;
使用 C 函数迁移架构并同步到数据库
// 迁移 'schema_v1' 和 'schema_v2'
sq_storage_migrate(storage, schema_v1);
sq_storage_migrate(storage, schema_v2);
// 将架构同步到数据库并更新 'storage' 中的架构
// 这主要由 SQLite 使用
sq_storage_migrate(storage, NULL);
// 释放未使用的 'schema_v1' 和 'schema_v2'
sq_schema_free(schema_v1);
sq_schema_free(schema_v2);
如果要使用单独的迁移文件来执行此操作,则可以将所有迁移文件放在 workspace/database/migrations 中。
sqxclib 提供了 SqApp 来使用这些文件。请参阅 doc/SqApp.cn.md 以获取更多信息。
此库使用 SqStorage 在数据库中创建、读取、更新和删除行。
要获取更多信息和示例,您可以查看 doc/SqStorage.cn.md
获取多行时用户可以指定返回数据的容器类型。如果您没有指定容器类型,getAll() 和 query() 将使用默认容器类型 - SqPtrArray。
使用 C 函数
容器类型指定为 NULL(使用默认容器类型)。
User *user;
SqPtrArray *array;
// 获取多行
array = sq_storage_get_all(storage, "users", NULL, NULL, "WHERE id > 8 AND id < 20");
// 获取所有行
array = sq_storage_get_all(storage, "users", NULL, NULL, NULL);
// 获取一行 (其中 id 等于 2)
user = sq_storage_get(storage, "users", NULL, 2);
使用 C++ 方法
User *user;
Sq::PtrArray *array;
// 获取多行
array = storage->getAll("users", "WHERE id > 8 AND id < 20");
// 使用 C++ 类 'where' 系列获取多行(在下面的 "查询生成器" 中说明)
array = storage->getAll("users", Sq::where("id", ">", 8).whereRaw("id < %d", 20));
// 获取所有行
array = storage->getAll("users");
// 获取一行 (其中 id 等于 2)
user = storage->get("users", 2);
使用 C++ 模板函数
容器类型指定为 std::vector。
User *user;
std::vector<User> *vector;
// 获取多行
vector = storage->getAll<std::vector<User>>("WHERE id > 8 AND id < 20");
// 使用 C++ 类 'where' 系列获取多行(在下面的 "查询生成器" 中说明)
vector = storage->getAll<std::vector<User>>(Sq::where("id", ">", 8).whereRaw("id < %d", 20));
// 获取所有行
vector = storage->getAll<std::vector<User>>();
// 获取一行 (其中 id 等于 2)
user = storage->get<User>(2);
使用 C 函数
User user = {10, "Bob", "bob@server"};
// 插入一行
sq_storage_insert(storage, "users", NULL, &user);
// 更新一行
sq_storage_update(storage, "users", NULL, &user);
// 更新特定列 - 多行中的 "name" 和 "email"。
sq_storage_update_all(storage, "users", NULL, &user,
"WHERE id > 11 AND id < 28",
"name", "email",
NULL);
// 更新特定字段 - 多行中的 User::name 和 User::email。
sq_storage_update_field(storage, "users", NULL, &user,
"WHERE id > 11 AND id < 28",
offsetof(User, name),
offsetof(User, email),
-1);
使用 C++ 方法
User user = {10, "Bob", "bob@server"};
// 插入一行
storage->insert("users", &user);
// 更新一行
storage->update("users", &user);
// 更新特定列 - 多行中的 "name" 和 "email"。
storage->updateAll("users", &user,
"WHERE id > 11 AND id < 28",
"name", "email");
// 更新特定字段 - 多行中的 User::name 和 User::email。
storage->updateField("users", &user,
"WHERE id > 11 AND id < 28",
&User::name, &User::email);
使用 C++ 模板函数
User user = {10, "Bob", "bob@server"};
// 插入一行
storage->insert<User>(&user);
// 或
storage->insert(&user);
// 更新一行
storage->update<User>(&user);
// 或
storage->update(&user);
// 更新特定列 - 多行中的 "name" 和 "email"。
// 调用 updateAll<User>(...)
storage->updateAll(&user,
"WHERE id > 11 AND id < 28",
"name", "email");
// 更新特定字段 - 多行中的 User::name 和 User::email。
// 调用 updateField<User>(...)
storage->updateField(&user,
"WHERE id > 11 AND id < 28",
&User::name, &User::email);
使用 C 函数
// 删除一行 (其中 id 等于 5)
sq_storage_remove(storage, "users", NULL, 5);
// 删除多行
sq_storage_remove_all(storage, "users", "WHERE id < 5");
使用 C++ 方法
// 删除一行 (其中 id 等于 5)
storage->remove("users", 5);
// 删除多行
storage->removeAll("users", "WHERE id < 5");
使用 C++ 模板函数
// 删除一行 (其中 id 等于 5)
storage->remove<User>(5);
// 删除多行
storage->removeAll<User>("WHERE id < 5");
sq_storage_query_raw() 使用原始字符串进行查询。程序必须指定数据类型和容器类型。
使用 C 函数
int *p2integer;
int max_id;
// 如果只查询 MAX(id),会得到一个整数。
// 因此指定表类型为 SQ_TYPE_INT,容器类型为 NULL。
p2integer = sq_storage_query_raw(storage, "SELECT MAX(id) FROM table", SQ_TYPE_INT, NULL);
// 返回整数指针
max_id = *p2integer;
// 不需要时释放整数指针。
free(p2integer);
// 如果只查询一行,则不需要容器。
// 因此指定容器类型为 NULL。
table = sq_storage_query_raw(storage, "SELECT * FROM table WHERE id=1", tableType, NULL);
使用 C++ 方法
int *p2integer;
int max_id;
// 如果只查询 MAX(id),会得到一个整数。
// 因此指定表类型为 SQ_TYPE_INT,容器类型为 NULL。
p2integer = storage->query("SELECT MAX(id) FROM table", SQ_TYPE_INT, NULL);
// 返回整数指针
max_id = *p2integer;
// 不需要时释放整数指针。
free(p2integer);
// 如果只查询一行,则不需要容器。
// 因此指定容器类型为 NULL。
table = storage->query("SELECT * FROM table WHERE id=1", tableType, NULL);
SqQuery 可以使用 C 函数或 C++ 方法生成 SQL 语句。 要获取更多信息和示例,您可以查看 doc/SqQuery.cn.md
SQL 语句
SELECT id, age
FROM companies
JOIN ( SELECT * FROM city WHERE id < 100 ) AS c ON c.id = companies.city_id
WHERE age > 5
使用 C++ 方法生成查询
query->select("id", "age")
->from("companies")
->join([query] {
query->from("city")
->where("id", "<", 100);
})
->as("c")
->onRaw("c.id = companies.city_id")
->whereRaw("age > %d", 5);
使用 C 函数生成查询
sq_query_select(query, "id", "age");
sq_query_from(query, "companies");
sq_query_join_sub(query); // 子查询的开始
// sq_query_join(query, NULL); // 子查询的开始
sq_query_from(query, "city");
sq_query_where(query, "id", "<", "%d", 100);
sq_query_end_sub(query); // 子查询的结尾
sq_query_as(query, "c");
sq_query_on_raw(query, "c.id = companies.city_id");
sq_query_where_raw(query, "age > %d", 5);
SqStorage 提供 sq_storage_query() 和 C++ 方法 query() 来处理查询。
// C 函数
array = sq_storage_query(storage, query, NULL, NULL);
// C++ 方法
array = storage->query(query);
SqQuery 提供 sq_query_c() 或 C++ 方法 c() 来为 SqStorage 生成 SQL 语句。
使用 C 函数
// SQL 语句排除 "SELECT * FROM ..."
sq_query_clear(query);
sq_query_where(query, "id", ">", "%d", 10);
sq_query_or_where_raw(query, "city_id < %d", 22);
array = sq_storage_get_all(storage, "users", NULL, NULL,
sq_query_c(query));
使用 C++ 方法
// SQL 语句排除 "SELECT * FROM ..."
query->clear()
->where("id", ">", 10)
->orWhereRaw("city_id < %d", 22);
array = storage->getAll("users", query->c());
方便的 C++ 类 'where' 系列
使用 Sq::Where(或 Sq::where)的 operator()
Sq::Where where;
array = storage->getAll("users",
where("id", ">", 10).orWhereRaw("city_id < %d", 22));
使用 Sq::where 的构造函数和运算符
// 使用参数包构造函数
array = storage->getAll("users",
Sq::where("id", ">", 10).orWhereRaw("city_id < %d", 22));
// 使用默认构造函数和 operator()
array = storage->getAll("users",
Sq::where()("id", ">", 10).orWhereRaw("city_id < %d", 22));
下面是目前提供的方便的 C++ 类:
Sq::Where, Sq::WhereNot,
Sq::WhereRaw, Sq::WhereNotRaw,
Sq::WhereExists, Sq::WhereNotExists,
Sq::WhereBetween, Sq::WhereNotBetween,
Sq::WhereIn, Sq::WhereNotIn,
Sq::WhereNull, Sq::WhereNotNull,
'Where' 类系列使用 'typedef' 给它们新名称:小写的 'where' 类系列。
Sq::where, Sq::whereNot,
Sq::whereRaw, Sq::whereNotRaw,
Sq::whereExists, Sq::whereNotExists,
Sq::whereBetween, Sq::whereNotBetween,
Sq::whereIn, Sq::whereNotIn,
Sq::whereNull, Sq::whereNotNull,
方便的 C++ 类 'select' 和 'from'
使用 C++ Sq::select 或 Sq::from 来运行数据库查询。
// 将 Sq::select 与 query 方法一起使用
array = storage->query(Sq::select("email").from("users").whereRaw("city_id > 5"));
// 将 Sq::from 与 query 方法一起使用
array = storage->query(Sq::from("users").whereRaw("city_id > 5"));
SqTypeJoint 是处理多表连接查询的默认类型。它可以为查询结果创建指针数组。
例如: 从连接多表的查询中获取结果。
使用 C 函数
sq_query_from(query, "cities");
sq_query_join(query, "users", "cities.id", "=", "%s", "users.city_id");
SqPtrArray *array = sq_storage_query(storage, query, NULL, NULL);
for (int i = 0; i < array->length; i++) {
void **element = (void**)array->data[i];
city = (City*)element[0]; // sq_query_from(query, "cities");
user = (User*)element[1]; // sq_query_join(query, "users", ...);
// 因为 SqPtrArray 默认不释放元素,所以在释放数组之前先释放元素。
// free(element);
}
使用 C++ 方法
query->from("cities")->join("users", "cities.id", "=", "users.city_id");
Sq::PtrArray *array = (Sq::PtrArray*) storage->query(query);
for (int i = 0; i < array->length; i++) {
void **element = (void**)array->data[i];
city = (City*)element[0]; // from("cities")
user = (User*)element[1]; // join("users")
// 因为 Sq::PtrArray 默认不释放元素,所以在释放数组之前先释放元素。
// free(element);
}
使用 C++ STL
用户可以将指向指针的指针(double pointer)指定为 STL 容器的元素。
std::vector<void**> *vector;
query->from("cities")->join("users", "cities.id", "=", "users.city_id");
vector = storage->query< std::vector<void**> >(query);
for (unsigned int index = 0; index < vector->size(); index++) {
void **element = vector->at(index);
city = (City*)element[0]; // from("cities")
user = (User*)element[1]; // join("users")
}
如果你不想使用指针作为容器的元素:
参考 SqTypeJoint 以获取更多信息。
以下是 C++ STL 示例之一:
std::vector< Sq::Joint<2> > *vector;
query->from("cities")->join("users", "cities.id", "=", "users.city_id");
vector = storage->query< std::vector< Sq::Joint<2> > >(query);
for (unsigned int index = 0; index < vector->size(); index++) {
Sq::Joint<2> &joint = vector->at(index);
city = (City*)joint[0]; // from("cities")
user = (User*)joint[1]; // join("users")
}
SqTypeRow 派生自 SqTypeJoint。它创建 SqRow 的实例并解析未知(或已知)的结果。
SQ_TYPE_ROW 是 SqTypeRow 的内置静态常量类型。SqTypeRow 和 SQ_TYPE_ROW 都在 sqxcsupport 库中 (sqxcsupport.h)。
使用 C 函数
SqRow *row;
SqPtrArray *array;
// 指定表类型为 SQ_TYPE_ROW
// 指定返回数据的容器类型为 SQ_TYPE_PTR_ARRAY
array = sq_storage_query(storage, query, SQ_TYPE_ROW, SQ_TYPE_PTR_ARRAY);
array = sq_storage_get_all(storage, "users", SQ_TYPE_ROW, SQ_TYPE_PTR_ARRAY, NULL);
// 指定表类型为 SQ_TYPE_ROW
row = sq_storage_get(storage, "users", SQ_TYPE_ROW, 11);
使用 C++ 方法
Sq::Row *row;
Sq::PtrArray *array;
// 指定表类型为 SQ_TYPE_ROW
// 指定返回数据的容器类型为 SQ_TYPE_PTR_ARRAY
array = (Sq::PtrArray*) storage->query(query, SQ_TYPE_ROW, SQ_TYPE_PTR_ARRAY);
array = (Sq::PtrArray*) storage->getAll("users", SQ_TYPE_ROW, SQ_TYPE_PTR_ARRAY, NULL);
// 指定表类型为 SQ_TYPE_ROW
row = (Sq::Row*) storage->get("users", SQ_TYPE_ROW, 11);
使用 C++ STL
Sq::Row *row;
std::vector<Sq::Row*> *rowVector;
// 指定表类型为 SQ_TYPE_ROW
// 指定返回数据的容器类型为 std::vector<Sq::Row*>
rowVector = storage->query< std::vector<Sq::Row*> >(query, SQ_TYPE_ROW);
rowVector = storage->getAll< std::vector<Sq::Row*> >("users", SQ_TYPE_ROW, NULL);
// 获取第一行
row = rowVector->at(0);
SqRow 包含 2 个数组。一个是列数组,另一个是数据数组。
// 第一列的名称
char *columnName = row->cols[0].name;
// 第一列的数据类型 (在此示例中,columnType 等于 SQ_TYPE_STR)
SqType *columnType = row->cols[0].type;
// 第一列的值 (如果 columnType 等于 SQ_TYPE_STR)
char *columnValue = row->data[0].str;
使用 C 函数
User *user;
sq_storage_begin_trans(storage);
sq_storage_insert(storage, "users", NULL, user);
if (abort)
sq_storage_rollback_trans(storage);
else
sq_storage_commit_trans(storage);
使用 C++ 方法
User *user;
storage->beginTrans();
storage->insert(user);
if (abort)
storage->rollbackTrans();
else
storage->commitTrans();
更改构建配置。
sqxclib 在搜索和排序 SQL 列名和 JSON 字段名时默认区分大小写。用户可以在 sqxc/SqConfig.h 中更改。
// SqConfig.h 中的常用设置
/* sqxclib 在搜索和排序 SQL 列名和 JSON 字段名时默认区分大小写。
某些旧的 SQL 产品可能需要禁用此功能。
受影响的源代码 : SqEntry, SqRelation-migration
*/
#define SQ_CONFIG_ENTRY_NAME_CASE_SENSITIVE 1
/* 如果用户没有指定 SQL 字符串长度,程序将使用默认值。
SQL_STRING_LENGTH_DEFAULT
*/
#define SQ_CONFIG_SQL_STRING_LENGTH_DEFAULT 191
Sqxc 是数据解析和写入的接口。
用户可以链接多个 Sqxc 元素来转换不同类型的数据。
您可以在 doc/Sqxc.cn.md 中获得更多描述和示例。
它定义了如何初始化、终结和转换 C 数据类型。
您可以在 doc/SqType.cn.md 中获得更多描述和示例。
SqSchema 定义数据库架构。它存储表和表的更改记录。
您可以在 doc/SqSchema.cn.md 中获得更多描述和示例。
SqApp 使用配置文件(SqApp-config.h)来初始化数据库并为用户的应用程序进行迁移。
它提供命令行程序来生成迁移并进行迁移。
请参阅文档 doc/SqApp.cn.md。
SqConsole 提供命令行界面(主要用于 SqAppTool)。
请参阅文档 doc/SqConsole.cn.md。
sqxc 中文发音「思库可思」,还可以翻译成白话文。
备注:中文发音是在 2022年5月14日 凌晨4点左右决定的。
sqxclib 在 木兰宽松许可证 第2版 下获得许可。
觀察者網觀察觀察者,認同者眾推廣認同者。
观察者网观察观察者,认同者众推广认同者。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。