
离线 TA的专栏
- 打卡等级:热心大叔
- 打卡总天数:204
- 打卡月天数:0
- 打卡总奖励:3221
- 最近打卡:2023-08-27 04:32:00
|
sqlx 是 Go 语言中一个流行的第三方包,它提供了对 Go 标准库 的扩展,旨在简化和改进 Go 语言中使用 SQL 的体验,并提供了更加强大的数据库交互功能。 保留了 接口不变,是 的超集,这使得将现有项目中使用的 替换为 变得相当轻松。
本文重点讲解 在 基础上扩展的功能,对于 已经支持的功能则不会详细讲解。如果你对 不熟悉,可以查看我的另一篇文章《在 Go 中如何使用 database/sql 来操作数据库》。
安装
安装方式同 Go 语言中其他第三方包一样: - $ go get github.com/jmoiron/sqlx
复制代码 sqlx 类型设计
的设计与 差别不大,编码风格较为统一,参考 标准库, 提供了如下几种与之对应的数据类型:
- :类似于,表示数据库对象,可以用来操作数据库。
- :类似于,事务对象。
- :类似于,预处理 SQL 语句。
- :对的封装,支持具名参数。
- :类似于,的返回结果。
- :类似于,的返回结果。
以上类型与 提供的对应类型在功能上区别不大,但 为这些类型提供了更友好的方法。
准备
为了演示 用法,我准备了如下 MySQL 数据库表: - DROP TABLE IF EXISTS `user`;
- CREATE TABLE `user` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `name` varchar(50) DEFAULT NULL COMMENT '用户名',
- `email` varchar(255) NOT NULL DEFAULT '' COMMENT '邮箱',
- `age` tinyint(4) NOT NULL DEFAULT '0' COMMENT '年龄',
- `birthday` datetime DEFAULT NULL COMMENT '生日',
- `salary` varchar(128) DEFAULT NULL COMMENT '薪水',
- `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
- `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- PRIMARY KEY (`id`),
- UNIQUE KEY `u_email` (`email`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
复制代码你可以使用 MySQL 命令行或图形化工具创建这张表。
连接数据库
使用 连接数据库: - package main
- import (
- "database/sql"
- "log"
- _ "github.com/go-sql-driver/mysql"
- "github.com/jmoiron/sqlx"
- )
- func main() {
- var (
- db *sqlx.DB
- err error
- dsn = "user:password@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=true&loc=Local"
- )
- // 1. 使用 sqlx.Open 连接数据库
- db, err = sqlx.Open("mysql", dsn)
- if err != nil {
- log.Fatal(err)
- }
- // 2. 使用 sqlx.Open 变体方法 sqlx.MustOpen 连接数据库,如果出现错误直接 panic
- db = sqlx.MustOpen("mysql", dsn)
- // 3. 如果已经有了 *sql.DB 对象,则可以使用 sqlx.NewDb 连接数据库,得到 *sqlx.DB 对象
- sqlDB, err := sql.Open("mysql", dsn)
- if err != nil {
- log.Fatal(err)
- }
- db = sqlx.NewDb(sqlDB, "mysql")
- // 4. 使用 sqlx.Connect 连接数据库,等价于 sqlx.Open + db.Ping
- db, err = sqlx.Connect("mysql", dsn)
- if err != nil {
- log.Fatal(err)
- }
- // 5. 使用 sqlx.Connect 变体方法 sqlx.MustConnect 连接数据库,如果出现错误直接 panic
- db = sqlx.MustConnect("mysql", dsn)
- }
复制代码在 中我们可以通过以上 5 种方式连接数据库。 对标 方法,返回 类型。 与 一样会返回 实例,但如果遇到错误则会 。 支持从一个 包的 对象创建一个新的 类型,并且需要指定驱动名称。
使用前 3 种方式连接数据库并不会立即与数据库建立连接,连接将会在合适的时候延迟建立。为了确保能够正常连接数据库,往往需要调用 方法进行验证: - ctx := context.Background()
- if err := db.PingContext(ctx); err != nil {
- log.Fatal(err)
- }
复制代码提供的 方法就是用来简化这一操作的,它等价于 + 两个方法,其定义如下: - func Connect(driverName, dataSourceName string) (*DB, error) {
- db, err := Open(driverName, dataSourceName)
- if err != nil {
- return nil, err
- }
- err = db.Ping()
- if err != nil {
- db.Close()
- return nil, err
- }
- return db, nil
- }
复制代码方法在 方法的基础上,提供了遇到错误立即 的功能。看到 方法的定义你就明白了: - func MustConnect(driverName, dataSourceName string) *DB {
- db, err := Connect(driverName, dataSourceName)
- if err != nil {
- panic(err)
- }
- return db
- }
复制代码以后当你遇见 类似方法名时就应该想到,其功能往往等价于 方法,不过在其内部实现中,遇到 不再返回,而是直接进行 ,这也是 Go 语言很多库中的惯用方法。
声明模型
我们定义一个 结构体来映射数据库中的 表: - type User struct {
- ID int
- Name sql.NullString `json:"username"`
- Email string
- Age int
- Birthday time.Time
- Salary Salary
- CreatedAt time.Time `db:"created_at"`
- UpdatedAt time.Time `db:"updated_at"`
- }
- type Salary struct {
- Month int `json:"month"`
- Year int `json:"year"`
- }
- // Scan implements sql.Scanner, use custom types in *sql.Rows.Scan
- func (s *Salary) Scan(src any) error {
- if src == nil {
- return nil
- }
- var buf []byte
- switch v := src.(type) {
- case []byte:
- buf = v
- case string:
- buf = []byte(v)
- default:
- return fmt.Errorf("invalid type: %T", src)
- }
- err := json.Unmarshal(buf, s)
- return err
- }
- // Value implements driver.Valuer, use custom types in Query/QueryRow/Exec
- func (s Salary) Value() (driver.Value, error) {
- v, err := json.Marshal(s)
- return string(v), err
- }
复制代码结构体在这里可以被称为「模型」。
执行 SQL 命令
包提供了 方法来执行一条 SQL 命令, 对其进行了扩展,提供了 方法来执行一条 SQL 命令: - func MustCreateUser(db *sqlx.DB) (int64, error) {
- birthday := time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local)
- user := User{
- Name: sql.NullString{String: "jianghushinian", Valid: true},
- Email: "jianghushinian007@outlook.com",
- Age: 10,
- Birthday: birthday,
- Salary: Salary{
- Month: 100000,
- Year: 10000000,
- },
- }
- res := db.MustExec(
- `INSERT INTO user(name, email, age, birthday, salary) VALUES(?, ?, ?, ?, ?)`,
- user.Name, user.Email, user.Age, user.Birthday, user.Salary,
- )
- return res.LastInsertId()
- }
复制代码这里使用 方法插入了一条 记录。 方法定义如下: - func (db *DB) MustExec(query string, args ...interface{}) sql.Result {
- return MustExec(db, query, args...)
- }
- func MustExec(e Execer, query string, args ...interface{}) sql.Result {
- res, err := e.Exec(query, args...)
- if err != nil {
- panic(err)
- }
- return res
- }
复制代码与前文介绍的 方法一样, 方法也会在遇到错误时直接 ,其内部调用的是 方法。
执行 SQL 查询
包提供了 和 两个查询方法,其签名如下: - func (db *DB) Query(query string, args ...any) (*Rows, error)
- func (db *DB) QueryRow(query string, args ...any) *Row
复制代码在这两个方法的基础上,扩展出如下两个方法: - func (db *DB) Queryx(query string, args ...interface{}) (*Rows, error)
- func (db *DB) QueryRowx(query string, args ...interface{}) *Row
复制代码这两个方法返回的类型正是前文 sqlx 类型设计 中提到的 、 类型。
下面来讲解下这两个方法如何使用。
Queryx
使用 方法查询记录如下: - func QueryxUsers(db *sqlx.DB) ([]User, error) {
- var us []User
- rows, err := db.Queryx("SELECT * FROM user")
- if err != nil {
- return nil, err
- }
- defer rows.Close()
- for rows.Next() {
- var u User
- // sqlx 提供了便捷方法可以将查询结果直接扫描到结构体
- err = rows.StructScan(&u)
- if err != nil {
- return nil, err
- }
- us = append(us, u)
- }
- return us, nil
- }
复制代码方法签名虽然与 方法基本相同,但它返回类型 得到了扩展,其提供的 方法能够方便的将查询结果直接扫描到 结构体,这极大的增加了便携性,我们再也不用像使用 提供的 方法那样挨个写出 的属性了。
QueryRowx
使用 方法查询记录如下: - func QueryRowxUser(db *sqlx.DB, id int) (User, error) {
- var u User
- err := db.QueryRowx("SELECT * FROM user WHERE id = ?", id).StructScan(&u)
- return u, err
- }
复制代码同样提供了 方法将查询结果扫描到结构体。
另外,这里使用了链式调用的方式,在调用 之后直接调用了 ,接收的 是 的返回结果。这是因为 的返回结果 中记录了错误信息 ,如果查询阶段遇到错误会被记录到 中。在调用 方法阶段,其内部首先判断 ,如果存在 直接返回错误,没有错误则将查询结果扫描到 参数接收到的结构体指针,代码实现如下: - type Row struct {
- err error
- unsafe bool
- rows *sql.Rows
- Mapper *reflectx.Mapper
- }
- func (r *Row) StructScan(dest interface{}) error {
- return r.scanAny(dest, true)
- }
- func (r *Row) scanAny(dest interface{}, structOnly bool) error {
- if r.err != nil {
- return r.err
- }
- ...
- }
复制代码不仅扩展了 和 两个查询方法,它还新增了两个查询方法: - func (db *DB) Get(dest interface{}, query string, args ...interface{}) error
- func (db *DB) Select(dest interface{}, query string, args ...interface{}) error
复制代码方法包装了 方法,用以简化查询单条记录。 方法包装了 方法,用以简化查询多条记录。
接下来讲解这两个方法如何使用。
Get
使用 方法查询记录如下: - func GetUser(db *sqlx.DB, id int) (User, error) {
- var u User
- // 查询记录扫描数据到 struct
- err := db.Get(&u, "SELECT * FROM user WHERE id = ?", id)
- return u, err
- }
复制代码可以发现 方法用起来非常简单,我们不再需要调用 方法将查询结果扫描到结构体中,只需要将结构体指针当作 方法的第一个参数传递进去即可。
其代码实现如下: - func (db *DB) Get(dest interface{}, query string, args ...interface{}) error {
- return Get(db, dest, query, args...)
- }
- func Get(q Queryer, dest interface{}, query string, args ...interface{}) error {
- r := q.QueryRowx(query, args...)
- return r.scanAny(dest, false)
- }
复制代码根据源码可以看出, 内部调用了 方法。
Select
使用 方法查询记录如下: - func SelectUsers(db *sqlx.DB) ([]User, error) {
- var us []User
- // 查询记录扫描数据到 slice
- err := db.Select(&us, "SELECT * FROM user")
- return us, err
- }
复制代码可以发现 方法用起来同样非常简单,它可以直接将查询结果扫描到 切片中。
其代码实现如下: - func (db *DB) Select(dest interface{}, query string, args ...interface{}) error {
- return Select(db, dest, query, args...)
- }
- func Select(q Queryer, dest interface{}, query string, args ...interface{}) error {
- rows, err := q.Queryx(query, args...)
- if err != nil {
- return err
- }
- // if something happens here, we want to make sure the rows are Closed
- defer rows.Close()
- return scanAll(rows, dest, false)
- }
复制代码根据源码可以看出, 内部调用了 方法。
sqlx.In
在 中如果想要执行 SQL IN 查询,由于 IN 查询参数长度不固定,我们不得不使用 来动态拼接 SQL 语句,以保证 SQL 中参数占位符的个数是正确的。 提供了 方法来支持 SQL IN 查询,这极大的简化了代码,也使得代码更易维护和安全。
使用示例如下: - func SqlxIn(db *sqlx.DB, ids []int64) ([]User, error) {
- query, args, err := sqlx.In("SELECT * FROM user WHERE id IN (?)", ids)
- if err != nil {
- return nil, err
- }
- query = db.Rebind(query)
- rows, err := db.Query(query, args...)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
- var us []User
- for rows.Next() {
- var user User
- err = rows.Scan(&user.ID, &user.Name, &user.Email, &user.Age,
- &user.Birthday, &user.Salary, &user.CreatedAt, &user.UpdatedAt)
- if err != nil {
- return nil, err
- }
- us = append(us, user)
- }
- return us, nil
- }
复制代码调用 并传递 SQL 语句以及切片类型的参数,它将返回新的查询 SQL 以及参数 ,这个 将会根据 来动态调整。
比如我们传递 为 ,则得到 为 - SELECT * FROM user WHERE id IN (?, ?, ?)
复制代码。
注意,我们接下来又调用 重新绑定了 变量的参数占位符。如果你使用 MySQL 数据库,这不是必须的,因为我们使用的 MySQL 驱动程序参数占位符就是 。而如果你使用 PostgreSQL 数据库,由于 PostgreSQL 驱动程序参数占位符是 ,这时就必须要调用 方法来转换参数占位符了。
它会将 - SELECT * FROM user WHERE id IN (?, ?, ?)
复制代码中的参数占位符转换为 PostgreSQL 驱动程序能够识别的参数占位符 - SELECT * FROM user WHERE id IN ($1, $2, $3)
复制代码。
之后的代码就跟使用 查询记录没什么两样了。
使用具名参数
提供了两个方法 、 ,它们能够支持具名参数 ,这样就不必再使用 这种占位符的形式了。
这两个方法签名如下: - func (db *DB) NamedExec(query string, arg interface{}) (sql.Result, error)
- func (db *DB) NamedQuery(query string, arg interface{}) (*Rows, error)
复制代码其使用示例如下: - func NamedExec(db *sqlx.DB) error {
- m := map[string]interface{}{
- "email": "jianghushinian007@outlook.com",
- "age": 18,
- }
- result, err := db.NamedExec(`UPDATE user SET age = :age WHERE email = :email`, m)
- if err != nil {
- return err
- }
- fmt.Println(result.RowsAffected())
- return nil
- }
- func NamedQuery(db *sqlx.DB) ([]User, error) {
- u := User{
- Email: "jianghushinian007@outlook.com",
- Age: 18,
- }
- rows, err := db.NamedQuery("SELECT * FROM user WHERE email = :email OR age = :age", u)
- if err != nil {
- return nil, err
- }
- defer rows.Close()
- var users []User
- for rows.Next() {
- var user User
- err := rows.StructScan(&user)
- if err != nil {
- return nil, err
- }
- users = append(users, user)
- }
- return users, nil
- }
复制代码我们可以使用 的方式来命名参数,它能够匹配 或 对应字段的参数值,这样的 SQL 语句可读性更强。
事务
在事务的支持上, 扩展出了 版本的事务,使用示例如下: - func MustTransaction(db *sqlx.DB) error {
- tx := db.MustBegin()
- tx.MustExec("UPDATE user SET age = 25 WHERE id = ?", 1)
- return tx.Commit()
- }
复制代码不过这种用法不多,你知道就行。以下是事务的推荐用法: - func Transaction(db *sqlx.DB, id int64, name string) error {
- tx, err := db.Begin()
- if err != nil {
- return err
- }
- defer tx.Rollback()
- res, err := tx.Exec("UPDATE user SET name = ? WHERE id = ?", name, id)
- if err != nil {
- return err
- }
- rowsAffected, err := res.RowsAffected()
- if err != nil {
- return err
- }
- fmt.Printf("rowsAffected: %d\n", rowsAffected)
- return tx.Commit()
- }
复制代码我们使用 语句来处理事务的回滚操作,这样就不必在每次处理错误时重复的编写调用 的代码。
如果代码正常执行到最后,通过 来提交事务,此时即使再调用 也不会对结果产生影响。
预处理语句
针对 扩展出了 方法,返回 类型。 类型支持 、 、 、 这些 特有的方法。
其使用示例如下: - func PreparexGetUser(db *sqlx.DB) (User, error) {
- stmt, err := db.Preparex(`SELECT * FROM user WHERE id = ?`)
- if err != nil {
- return User{}, err
- }
- var u User
- err = stmt.Get(&u, 1)
- return u, err
- }
复制代码方法定义如下: - func Preparex(p Preparer, query string) (*Stmt, error) {
- s, err := p.Prepare(query)
- if err != nil {
- return nil, err
- }
- return &Stmt{Stmt: s, unsafe: isUnsafe(p), Mapper: mapperFor(p)}, err
- }
复制代码实际上 内部还是调用的 方法,只不过将其返回结果构造成 类型并返回。
不安全的扫描
在使用 等方法查询记录时,如果 SQL 语句查询出来的字段与要绑定的模型属性不匹配,则会报错。
示例如下: - func GetUser(db *sqlx.DB) (User, error) {
- var user struct {
- ID int
- Name string
- Email string
- // 没有 Age 属性
- }
- err := db.Get(&user, "SELECT id, name, email, age FROM user WHERE id = ?", 1)
- if err != nil {
- return User{}, err
- }
- return User{
- ID: user.ID,
- Name: sql.NullString{String: user.Name},
- Email: user.Email,
- }, nil
- }
复制代码以上示例代码中,SQL 语句中查询了 、 、 、 4 个字段,而 结构体则只有 、 、 3 个属性,由于无法一一对应,执行以上代码,我们将得到如下报错信息: - missing destination name age in *struct { ID int; Name string; Email string }
复制代码这种表现是合理的,符合 Go 语言的编程风格,尽早暴露错误有助于减少代码存在 BUG 的隐患。
不过,有些时候,我们就是为了方便想要让上面的示例代码能够运行,可以这样做: - func UnsafeGetUser(db *sqlx.DB) (User, error) {
- var user struct {
- ID int
- Name string
- Email string
- // 没有 Age 属性
- }
- udb := db.Unsafe()
- err := udb.Get(&user, "SELECT id, name, email, age FROM user WHERE id = ?", 1)
- if err != nil {
- return User{}, err
- }
- return User{
- ID: user.ID,
- Name: sql.NullString{String: user.Name},
- Email: user.Email,
- }, nil
- }
复制代码这里我们不再直接使用 来查询记录,而是先通过 获取 属性为 的 对象,然后再调用它的 方法。 定义如下: - type DB struct {
- *sql.DB
- driverName string
- unsafe bool
- Mapper *reflectx.Mapper
- }
复制代码当 属性为 时, 对象会忽略不匹配的字段,使代码能够正常运行,并将能够匹配的字段正确绑定到 结构体对象上。
通过这个属性的名称我们就知道,这是不安全的做法,不被推荐。
与未使用的变量一样,被忽略的列是对网络和数据库资源的浪费,并且这很容易导致出现模型与数据库表不匹配而不被感知的情况。
Scan 变体
前文示例中,我们见过了 的变体 的用法,它能够方便的将查询结果扫描到 中。 还提供了 、 两个方法,能够将查询结果分别扫描到 和 中。
使用示例如下: - func MapScan(db *sqlx.DB) ([]map[string]interface{}, error) {
- rows, err := db.Queryx("SELECT * FROM user")
- if err != nil {
- return nil, err
- }
- defer rows.Close()
- var res []map[string]interface{}
- for rows.Next() {
- r := make(map[string]interface{})
- err := rows.MapScan(r)
- if err != nil {
- return nil, err
- }
- res = append(res, r)
- }
- return res, err
- }
- func SliceScan(db *sqlx.DB) ([][]interface{}, error) {
- rows, err := db.Queryx("SELECT * FROM user")
- if err != nil {
- return nil, err
- }
- defer rows.Close()
- var res [][]interface{}
- for rows.Next() {
- // cols is an []interface{} of all the column results
- cols, err := rows.SliceScan()
- if err != nil {
- return nil, err
- }
- res = append(res, cols)
- }
- return res, err
- }
复制代码其中, 用法与 用法类似,都是将接收查询结果集的目标模型指针变量当作参数传递进来。 用法略有不同,它不接收参数,而是将结果保存在 中并返回。
可以按需使用以上两个方法。
控制字段名称映射
讲到这里,想必不少同学心里可能存在一个疑惑, 在将查询记录的字段映射到对应结构体属性时,是如何找到对应关系的呢?
答案就是 结构体标签。
回顾前文讲 声明模型 时, 结构体中定义的 、 两个字段,定义如下: - CreatedAt time.Time `db:"created_at"`
- UpdatedAt time.Time `db:"updated_at"`
复制代码这里显式的标明了结构体标签 , 正是使用 标签来映射查询字段和模型属性。
默认情况下,结构体字段会被映射成全小写形式,如 字段会被映射为 ,而 字段会被映射为 。
因为在 数据库表中,创建时间和更新时间两个字段分别为 、 ,与 默认字段映射规则不匹配,所以我才显式的为 和 两个字段指明了 标签,这样 的 就能正常工作了。
当然,数据库字段不一定都是小写,如果你的数据库字段为全大写, 提供了 方法来控制查询字段和模型属性的映射关系。
其使用示例如下: - func MapperFuncUseToUpper(db *sqlx.DB) (User, error) {
- copyDB := sqlx.NewDb(db.DB, db.DriverName())
- copyDB.MapperFunc(strings.ToUpper)
- var user User
- err := copyDB.Get(&user, "SELECT id as ID, name as NAME, email as EMAIL FROM user WHERE id = ?", 1)
- if err != nil {
- return User{}, err
- }
- return user, nil
- }
复制代码这里为了不改变原有的 对象,我们复制了一个 ,调用 并将 传递进来。
注意这里的查询语句中,查询字段全部通过 重新命名成了大写形式,而 模型字段 默认都为小写形式。 - copyDB.MapperFunc(strings.ToUpper)
复制代码的作用,就是在调用 方法将查询结果扫描到结构体时,把 模型的小写字段,通过 方法转成大写,这样查询字段和模型属性就全为大写了,也就能够一一匹配上了。
还有一种情况,如果你的模型已存在 标签,并且不想重复的再抄一遍到 标签,我们可以直接使用 标签来映射查询字段和模型属性。 - func MapperFuncUseJsonTag(db *sqlx.DB) (User, error) {
- copyDB := sqlx.NewDb(db.DB, db.DriverName())
- // Create a new mapper which will use the struct field tag "json" instead of "db"
- copyDB.Mapper = reflectx.NewMapperFunc("json", strings.ToLower)
- var user User
- // json tag
- err := copyDB.Get(&user, "SELECT id, name as username, email FROM user WHERE id = ?", 1)
- if err != nil {
- return User{}, err
- }
- return user, nil
- }
复制代码这里需要直接修改 属性,赋值为 - reflectx.NewMapperFunc("json", strings.ToLower)
复制代码将模型映射的标签由 改为 ,并通过 方法转换为小写。 按照如下方式导入: - import "github.com/jmoiron/sqlx/reflectx"
复制代码现在,查询语句中 属性通过使用 被重命名为 ,而 刚好与 模型中 字段的 标签相对应: - Name sql.NullString `json:"username"`
复制代码所以,以上示例代码能够正确映射查询字段和模型属性。
总结
建立在 包之上,用于简化和增强与关系型数据库的交互操作。
对常见数据库操作方法, 提供了 版本,如 用来连接数据库, 用来执行 SQL 语句,当遇到 时将会直接 。 还扩展了查询方法 、 、 、 ,并且这些查询方法支持直接将查询结果扫描到结构体。 为 SQL IN 操作提供了便捷方法 。
为了使 SQL 更易阅读, 提供了 、 两个方法支持具名参数。
调用 方法能够获取 属性为 的 对象,在将查询结果扫描到结构体使可以用来忽略不匹配的记录字段。
除了能够将查询结果扫描到 , 还支持将查询结果扫描到 和 。 使用 结构体标签来映射查询字段和模型属性,如果不显式指定 标签,默认映射的模型属性为小写形式,可以通过 函数来修改默认行为。
本文完整代码示例我放在了 GitHub 上,欢迎点击查看。
以上就是Go语言使用sqlx操作数据库的示例详解的详细内容,更多关于Go sqlx操作数据库的资料请关注晓枫资讯其它相关文章!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:  进行删除处理。
4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
|