Thinkphp6学习笔记,持续记录

16,744次阅读
没有评论

共计 4096 个字符,预计需要花费 11 分钟才能阅读完成。

官方文档

由此开始:https://www.kancloud.cn/manual/thinkphp6_0/1037479

目录结构:https://www.kancloud.cn/manual/thinkphp6_0/1037483

伪静态设置:https://www.kancloud.cn/manual/thinkphp6_0/1037488

Thinkphp 网址导航:http://sites.thinkphp.cn/1556332

1. 安装 lnmp 之后做些什么?

open_basedir = /:/tmp/:/proc/,调整 PHP 目录访问权限;

display_errors = On,打开默认不显示的错误提示;

安装 Redis,安装 Swoole;

2. 安装 Thinkphp 之后要做些什么?

  1. 安装 think-view 模板库:“topthink/think-view”:”*”;
  2. Nginx 解析的根目录改为 Thinkphp 项目所在的 public 目录;
  3. Thinkphp 伪静态修改好,并重启 Nginx;
  4. 将 runtime、session 等需要写入的目录给予可写的权限;
  5. 启用 Thinkphp 的 session:app/middleware.php 写入;
    thinkmiddlewareSessionInit::class
    
  6. config/session.php 调整 session 的有效期;
  7. config/middleware.php 新增中间件类的别名
    'alias'    => ["Token"=>appmiddlewareToken::class,
          "BusinessToken"=>appmiddlewareBusinessToken::class
    ],
    
  8. configdatabase.php,修改数据库相关的配置信息;
  9. configfilesystem.php,修改自带的文件管理库相关配置;
  10. configapp.php,修改应用相关配置(是否显示错误信息等等);
  11. configcache.php,配置 redis 等缓存数据库;
  12. .env,修改调试的相关配置信息;
  13. appExceptionHandle.php,修改默认错误信息输出的默认模板;

细节总结

1. 基本

  1. 助手函数:https://www.kancloud.cn/manual/thinkphp6_0/1037653
  2. 门面 Facade:https://www.kancloud.cn/manual/thinkphp6_0/1037491
  3. URL 访问模式:域名 / 入口 / 控制器 / 操作 / 参数名 / 参数 …,入口、控制器不填的时候默认 index。单个参数无需指定参数名。(参数绑定:https://www.kancloud.cn/manual/thinkphp6_0/1037523
  4. Request 请求对象:https://www.kancloud.cn/manual/thinkphp6_0/1037516
  5. Response 响应对象:https://www.kancloud.cn/manual/thinkphp6_0/1037525
  6. 在 Tp6 里不能通过 header 函数设置响应头,只能通过 response 对象。

2. 控制器以及多应用

  1. 官方文档:https://www.kancloud.cn/manual/thinkphp6_0/1037510
  2. Tp6 支持任意的多级路由器,访问时 URl 格式为:host/one.two.three/methods
  3. 可以通过路由的方式精简 URL。路由不能针对整个控制器路由。
  4. 跨域访问:https://www.kancloud.cn/manual/thinkphp6_0/1037507

多应用模式

多应用模式部署后,记得删除 app 目录下的 controller 目录(系统根据该目录作为判断是否单应用的依据)。

在 mac 或者 linux 环境下面,注意需要设置 runtime 目录权限为 777。

3. 视图

  1. 视图的目录规范:https://www.kancloud.cn/manual/thinkphp6_0/1037611
  2. 视图模板赋值与输出:https://www.kancloud.cn/manual/thinkphp6_0/1037609
  3. 模板开发文档:https://www.kancloud.cn/manual/think-template
  4. // 模板变量赋值
    View::assign('name','ThinkPHP');
    // 或者批量赋值
    View::assign(['name'  => 'ThinkPHP',
         'email' => 'thinkphp@qq.com'
    ]);
    
    // 使用视图输出过滤
    return View::filter(function($content){return str_replace("rn",'',$content);
        })->fetch();
    
    
  5. 可以使用 View 静态方法或者 view()助手函数返回的对象;
  6. 模板路径:默认情况下,框架会自动定位你的模板文件路径,优先定位应用目录下的 view 目录 ,这种方式的视图目录下就是应用的控制器目录。(app/(多应用时存在的目录)/controller 同级目录的 view 目录 / 控制器目录 / 视图.html);第二种方式是视图文件和应用类库文件完全分离, 统一放置在根目录下的 view 目录。
  7. 表示系统会按照默认规则自动定位视图目录下的模板文件,其规则是:控制器名(小写 + 下划线)/ 操作名.html。如果有更改模板引擎的 view_depr,改为_后就变成控制器_视图.html;
    /* 指定模板输出 */
    return View::fetch('edit'); 
    /* 表示调用当前控制器下面的 edit 模板 */
    
    return View::fetch('member/read');
    /* 表示调用 Member 控制器下面的 read 模板。*/
    
    return View::fetch('admin@member/edit');
    /* 表示跨应用调用模板 */

4. 数据库

  1. 原生查询:https://www.kancloud.cn/manual/thinkphp6_0/1037570
    $user=Db::table('$tablename');  /* 含前缀 */
    $user=Db::name('$name');/* 不含前缀 */
    
  2. 在 Db 类的方法中,name(),会自动加上表前缀,table(),则不会。
  3. 链式查询:https://www.kancloud.cn/manual/thinkphp6_0/1037538,如 field 指定部分字段等等。
  4. select 方法查询结果是一个数据集对象,如果需要转换为数组可以使用(toArray)
  5. find 方法:查询符合条件的第一条数据。select 方法:查询符合条件的所有数据。find 只是取一条记录;打印出来的区别只是有个 limit 1 的限制;
  6. 字符串条件查询:whereRaw(‘type=1 AND status=1’),以及 whereOr、whereIn 等等
  7. find 查询未找到数据是返回 null,findOrEmpty 未找到数据时返回空数组。insertGetId,插入数据时返回 Id
  8. update 方法返回影响数据的条数,没修改任何数据返回 0;
  9. fieldRaw 方法中可直接使用 Mysql 函数,fieldRaw(‘id,SUM(score)’);
  10. 如果要更新的数据需要使用 SQL 函数或者其它字段,exp(‘name’,’UPPER(name)’);
  11. getOptions(),$model_list = User::order();$options = $model_list->getOptions(); 获取本次查询的条件;
  12. fetchSql(),fetchSql 用于直接返回 SQL 而不是执行查询,适用于任何的 CURD 操作方法。
  13. buildSql(),返回用于子查询的 sql 语句
  14. ‘break_reconnect’ => true,用于开启断线重连。
  15. 配置中的 params 参数可设置 PDO 连接参数

5. 引入自定义类库、函数

  1. 如果你需要在核心之外扩展和使用第三方类库,并且该类库不是通过 Composer 安装使用,那么可以直接放入应用根目录下面的 extend 目录下面,该目录是官方建议的第三方扩展类库目录。
  2. 官方文档说明:https://www.kancloud.cn/manual/thinkphp5/177200
  3. 强烈建议使用 Composer 安装和更新扩展类库,ThinkPHP5.0+ 的扩展类库都采用 Composer 方式进行安装。

6. 扩展和自定义配置目录:

  1. 5.0.1 开始增加了扩展配置目录的概念,在应用配置目录或者模块配置目录下面增加 extra 子目录,下面的配置文件都会自动加载,无需任何配置。
  2. Tp6.x 的 config 目录下的所有配置文件系统都会自动读取,不需要手动加载。如果存在子目录,你可以通过 Config 类的 load 方法手动加载
  3. 官方说明文档:https://www.kancloud.cn/manual/thinkphp5/215848
  4. 自定义函数直接写在 application 或 app 目录下的 common.php 即可。

7. 数据模型

  1. 模型会自动对应数据表,模型类的命名规则是除去表前缀的数据表名称,采用驼峰法命名,并且首字母大写。
  2. 模型可使用数据库 Db 的所有方法。
  3. 模型类定义在 app/model 目录内,官方文档:https://www.kancloud.cn/manual/thinkphp6_0/1037483。
  4. 模型在控制器中的引入方式有三种,在控制器的开始部分 use 进来(推荐使用)、使用 Loader 类加载模型、使用助手函数 model(不推荐使用,以防助手函数被覆盖掉)。
  5. select 方法查询结果是一个数据集对象,如果需要转换为数组可以使用(toArrray()方法)。
  6. V6.0.3+ 版本开始,原生查询仅支持 Db 类操作,不支持在模型中调用原生查询方法(包括 query 和 execute 方法)。

8. 异常处理

  1. 官方文档:https://www.kancloud.cn/manual/thinkphp6_0/1037615
getStatusCode() == 404) {return json(["code" => 0, "errMsg" => $e->getMessage()])->code(404);
     } else {return json(["code" => 0, "errMsg" => $e->getMessage()]);
     }
    } else {return json(["code" => 0, "errMsg" => $e->getMessage()]);
    }
}

9. 中间件

  1. 中间件主要用于拦截或过滤应用的 HTTP 请求,并进行必要的业务处理。
  2. 全局中间件 ->应用中间件 ->路由中间件 ->控制器中间件;
  3. 官方文档:https://www.kancloud.cn/manual/thinkphp6_0/1037493
  4. 直接使用中间件时需要指定完整的包名 + 类名,通过 middleware.php 配置中间件的别名后可直接指定别名。
  5. 直接传递参数到控制器;
    public function handle($request, Closure $next)
    {if ('think' == $request->name) {$request->name = 'ThinkPHP';
         }
    
         return $next($request);
    }
    

路由中间件

可以细化到对方法进行中间件控制

10. 事件

  1. 事件相比较中间件的优势是事件比中间件更加精准定位(或者说粒度更细),并且更适合一些业务场景的扩展。例如,我们通常会遇到用户注册或者登录后需要做一系列操作,通过事件系统可以做到不侵入原有代码完成登录的操作扩展,降低系统的耦合性的同时,也降低了 BUG 的可能性。
  2. 官方文档:https://www.kancloud.cn/manual/thinkphp6_0/1037492

11. 数据库事务

什么情况下应该使用事务,当数据库操作结束后,还有其它的非数据库业务流程,失败时数据库操作不应该存在时使用。

Thinkphp6 模型使用事务同 DB 类一致,实例化后直接调用 startTrans、commit、rollback;

回滚只能在 Commit 之前,Commit 之后将无法回滚。

12. 数据库加锁

排它锁:Db::name(‘user’)->where(‘id’,1)->lock(true)->find();

共享锁:Db::name(‘user’)->where(‘id’,1)->lock(‘lock in share mode’)->find();

13.Redis 的使用

官方文档:https://www.kancloud.cn/manual/thinkphp6_0/1037634

官方提供的方法都比较简单易用,当然也可以自己对 Redis 类进行封装;

路由相关

1. 基础知识

官方文档:https://www.kancloud.cn/manual/thinkphp6_0/1037494

路由解析的过程一般包含:

  • 路由定义:完成路由规则的定义和参数设置;
  • 路由检测:检查当前的 URL 请求是否有匹配的路由;
  • 路由解析:解析当前路由实际对应的操作(方法或闭包);
  • 路由调度:执行路由解析的结果调度;
  • 掌握路由主要是要掌握路由定义及参数设置,其它环节是由系统自动完成的。

路由定义

route 目录下的任何路由定义文件都是有效的,分开多个路由定义文件并没有实际的意义,纯粹出于管理方便而已。默认的路由定义文件是 route.php,但你完全可以更改文件名,或者添加多个路由定义文件。

1.1 单应用路由

├─route                 路由定义目录
│  ├─route.php          路由定义
│  ├─api.php            路由定义
│  └─...                更多路由定义

1.2 多应用路由

多应用模式,路由定义文件需要放入应用目录下:

├─app           应用目录
│  ├─app_name           应用目录
│  │  ├─common.php      函数文件
│  │  ├─controller      控制器目录
│  │  ├─model           模型目录
│  │  ├─view            视图目录
│  │  ├─config          配置目录
│  │  ├─route           路由目录
│  │  │  ├─route.php    路由定义
│  │  │  ├─api.php      路由定义
│  │  │  └─...          更多路由定义

2. 注册路由

要使用 Route 类注册路由必须首先在路由定义文件开头添加引用(定义路由之后,原来的访问地址会自动失效)

use thinkfacadeRoute;
Route::rule('路由表达式', '路由地址', '请求类型');
// 注册路由到 News 控制器的 read 操作
Route::rule('new/:id','News/read');
// 单独指定请求类型鹅快捷方法
Route:: 快捷方法名('路由表达式', '路由地址');

3. 路由类型

  • 路由到控制器 / 操作,路由到 blog 控制器,Route::get(‘blog/:id’,’Blog/read’);
  • 路由到类的方法,完整类名 @方法名,Route::get(‘blog/:id’,’appindexserviceBlog@read’);
  • 重定向路由,Route::redirect(‘blog/:id’, ‘http://blog.thinkphp.cn/read/:id’, 302);
  • 路由到模板,路由到模板文件,Route::view(‘hello/:name’, ‘index/hello’,[携带的变量]);
  • 路由到闭包,Route::get(‘hello’, function () {return ‘hello,world!’;});

4. 路由参数

官方文档:https://www.kancloud.cn/manual/thinkphp6_0/1037499

Route::get('new/:id', 'News/read')
    ->ext('html')
    ->https();

路由参数可以混合使用,只要有任何一条参数检查不通过,当前路由就不会生效,继续检测后面的路由规则。

5. 路由中间件

单个路由注册中间件:

Route::rule('hello/:name','hello')
	->middleware(appmiddlewareAuth::class);

路由分组注册中间件:

Route::group('hello', function(){Route::rule('hello/:name','hello');
})->middleware(appmiddlewareAuth::class);

使用数组方式,定义多个中间件:

Route::rule('hello/:name','hello')
	->middleware([appmiddlewareAuth::class,appmiddlewareCheck::class]);

6. 注解路由

ThinkPHP 支持使用注解方式定义路由(也称为注解路由),如果需要使用注解路由需要安装额外的扩展:

composer require topthink/think-annotation

使用注解定义路由:

class Index
{
    /**
     * @param  string $name 数据名称
     * @return mixed
     * @Route("hello/:name")
     */
	public function hello($name)
    {return 'hello,'.$name;}
}

7. 路由绑定

把当前的 URL 绑定到控制器 / 操作,最多支持绑定到操作级别。

7.1 绑定到控制器 / 操作

// 绑定当前的 URL 到 Blog 控制器
Route::bind('blog');
// 绑定当前的 URL 到 Blog 控制器的 read 操作
Route::bind('blog/read');

8. 多级目录路由

Request 对象

Request 对象可通过 thinkfacadeRequest;t 或者 request()助手函数两种方式获取;

1.Request::has('id','get');

判断参数是否传递,等同于 isset(request()->get("id"));

  1. param 获取当前请求的变量;
  2. get 获取 $_GET 变量;
  3. post 获取 $_POST 变量;
  4. put 获取 PUT 变量;
  5. delete 获取 DELETE 变量;
  6. session 获取 SESSION 变量;
  7. cookie 获取 $_COOKIE 变量;
  8. request 获取 $_REQUEST 变量;
  9. server 获取 $_SERVER 变量;
  10. env 获取 $_ENV 变量;
  11. route 获取 路由(包括 PATHINFO)变量
  12. middleware 获取 中间件赋值 / 传递的变量;
  13. file 获取 $_FILES 变量;
  14. all V6.0.8+ 获取包括 $_FILES 变量在内的请求变量,相当于 param+file;

2. 默认值

Request::get('name','default'); // 返回值为 default

3. 变量过滤

框架默认没有设置任何全局过滤规则,你可以在 appRequest 对象中设置 filter 全局过滤属性;

全局变量过滤方法:Request::filter(['strip_tags','htmlspecialchars']),

获取变量的时候过滤:Request::param('username','','strip_tags,strtolower'); // 获取 param 变量 并依次调用 strip_tags、strtolower 函数过滤

4. 获取 JSON 数据

对于 body 中提交的 json 对象,你无需使用 php://input 去获取,可以直接当做表单提交的数据使用,因为系统已经自动处理过了;

5. 获取部分参数、排除参数 

/* 设置默认值 */
Request::only(['id'=>0,'name'=>'']);

/*  只获取当前请求的 id 和 name 变量 */
Request::only(['id','name']);

/*  只获取 GET 请求的 id 和 name 变量 */
Request::only(['id','name'], 'get');

/*  排除 id 和 name 变量 */
Request::except(['id','name']); 

6. 修饰符

Request:: 变量类型('变量名 / 修饰符');

  1. s 强制转换为字符串类型
  2. d 强制转换为整型类型
  3. b 强制转换为布尔类型
  4. a 强制转换为数组类型
  5. f 强制转换为浮点类型

7. 助手函数

为了简化使用,还可以使用系统提供的 input 助手函数完成上述大部分功能。https://www.kancloud.cn/manual/thinkphp6_0/1037519#_89 

  • input('?get.id');// 是否存在
  • input('param.name'); // 获取单个参数
  • input('param.'); // 获取全部参数
  • input('');// 获取全部参数 input('get.id');// 获取单个参数
  • input('get.name','','htmlspecialchars'); // 获取 get 变量 并用 htmlspecialchars 函数过滤
  • input('post.name','','orgFilter::safeHtml'); // 获取 post 变量 并用 orgFilter 类的 safeHtml 方法过滤
  • input('post.ids/a');// 变量修饰符

事件

事件系统相比行为系统强大的地方在于事件本身可以是一个类,并且可以更好的支持事件订阅者。

事件相比较中间件的优势是事件比中间件更加精准定位(或者说粒度更细),并且更适合一些业务场景的扩展。例如,我们通常会遇到用户注册或者登录后需要做一系列操作,通过事件系统可以做到不侵入原有代码完成登录的操作扩展,降低系统的耦合性的同时,也降低了 BUG 的可能性。

1. 观察者模式

观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在观察者模式中,主体是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。观察者模式不仅被广泛应用于软件界面元素之间的交互,在业务对象之间的交互、权限管理等方面也有广泛的应用。

观察者模式(Observer)完美的将观察者和被观察的对象分离开。举个例子,用户界面可以作为一个观察者,业务数据是被观察者,用户界面观察业务数据的变化,发现数据变化后,就显示在界面上。

面向对象设计的一个原则是:系统中的每个类将重点放在某一个功能上,而不是其他方面。一个对象只做一件事情,并且将他做好。观察者模式在模块之间划定了清晰的界限,提高了应用程序的可维护性和重用性。

2. 事件机制

事件机制涉及三个类:事件类(event)、监听类(listener)、订阅类(subscribe)

  • 通过监听的方式,每个事件都需要定义一个监听类来处理监听逻辑,并且在 event.php 的 listen 数组中配置
  • 通过订阅的方式,如果采用自动绑定,则需要在订阅类中为每个事件定义一个监听方法(方法名不能自己定义),不需要定义 subscribe 方法;如果采用手动绑定,则定义 subscribe 方法,并为每一个事件绑定负责处理业务逻辑的具体方法(方法名可以自己定义)
  • 通过监听的方式中,事件类基本上可有可无,因为创建的监听类本来就是专门处理对应的事件的业务逻辑的。并且监听类可以用事件类代替。
  • 通过订阅的方式中,如果采用自动绑定,事件类也没有必要,因为在订阅类中为每个事件定义的监听方法本来就是专门处理对应的事件的业务逻辑的。如果采用手动绑定,也可以把监听方法写在订阅类中,那么事件类也就没必要了。但是个人认为手动绑定还是使用事件类比较好。订阅类只负责绑定,事件类负责处理具体业务逻辑,这样结构上比较清晰
  • 其实通过监听的方式中,监听类(例子中的 UserListener)可以替换成其他命名空间下的其他类,只要该类有一个 handle 方法就行,比如事件类。替换之后记得要在 event.php 配置文件中做对应修改。同理,通过订阅的方式中,也可以将订阅类(例子中的 UserSubscribe)直接替换为其他类,但是要记得自动绑定需要在该类下有符合命名规则的方法,手动绑定需要在该类下有一个 subscribe 方法

参考:https://blog.csdn.net/firstime_tzjz/article/details/126869156

功能总览

  1. 日志相关功能:https://www.kancloud.cn/manual/thinkphp6_0/1037616
  2. 错误和调试:https://www.kancloud.cn/manual/thinkphp6_0/1037615https://www.kancloud.cn/manual/thinkphp6_0/1037618
  3. 数据验证:https://www.kancloud.cn/manual/thinkphp6_0/1037623
  4. 系统安全:https://www.kancloud.cn/manual/thinkphp5/268461
  5. 实用小功能:https://www.kancloud.cn/manual/thinkphp6_0/1037633
  6. 扩展功能的相关说明:https://www.kancloud.cn/manual/thinkphp5/118129
  7. Thinkphp 部署相关的细节:https://www.kancloud.cn/manual/thinkphp5/129745

使用心得

1. 快捷获取当前时间

/*
 * 按照格式化字符获取当前时间
 * */
function now($format="Y-m-d H:i:s"){return date($format,time());
}

3. 分页输出

QdAccount::order('belong', 'desc')
           ->paginate(['list_rows' => $json['size'],
                 'page' => $json['page'],
           ])->toArray();

// 最终输出
 {
    "total": 2,
    "per_page": 30,
    "current_page": 1,
    "last_page": 1,
    "data": [
      {
        "id": 1,
        "mobile": "18273411374",
        "password": "123",
        "register_time": "2022-07-14 13:31:04"
      },
      {
        "id": 2,
        "mobile": "18273411374",
        "password": "123",
        "register_time": "2022-07-14 13:32:04"
      }
    ]
  }

问题总结

1. 日常报错

  1. Tp6.0 报错 Driver [Think] not supported. 是因为没有安装 tp 视图库,安装即可。
    composer require topthink/think-view
    
  2. 文件上传:https://www.kancloud.cn/manual/thinkphp6_0/1037639,文件系统类:https://github.com/ctfang/think-flysystem
  3. 在 Linux 系统上,Thinkphp 是区分大小写的。模型名、控制器等必须大小写一致。
  4. Session 保存的目录不可写时,将导致 session 失效。
  5. 通过助手函数直接让用户响应下载文件:https://kancloud.cn/manual/thinkphp6_0/1037529
  6. 控制器进行响应时,可以携带一个状态码。
  7. Thinkphp 通过伪静态重定向,会导致正常访问不存在的资源时也会经过 PHP 处理。

2. Tp6.1

更新 6.1 移除了 filesystem 的库,导致文件上传报错了。安装的时候又报错了。

require ext-fileinfo * -> it is missing from your system. Install or enable PHP's fileinfo extension.

出现此错误的原因是 php.ini 中的 fileinfo 扩展没有开启,安装 fileinfo 拓展,开启 extension=fileinfo

3.tp6.1 问题记录

更新 6.1 移除了 filesystem 的库,导致文件上传报错了。好不容易安装了,6.1 版本用不了。

最后升级 thinkphp 到 6.1.x-dev 版本才解决。

4. 字段缓存

ORM:https://www.kancloud.cn/manual/think-orm/1258072

// 开启字段缓存
'fields_cache'      => true,
// 字段缓存路径
'schema_cache_path' => 'path/to/cache',

开启后,会自动生成使用过的数据表字段缓存,如果你更改了数据表的字段及类型,需要清空字段缓存文件。字段缓存采用文件方式保存,路径由 schema_cache_path 配置参数设置。

5. group 和 paginate 一起使用时,分页异常

tp 中使用 paginate 时会使用 Db 里面的 count 函数来统计数据总行数,count 函数则会使用 count(当前查询的所有字段) limit 1 进行查询,当使用了 group by 时 count 统计的是分组后的每行的数量,然后 limit 1 返回第一行的统计值 所以会出现总行数不对的情况。

// 原语句
$list=Db::name(‘sms_log’)->field(‘mobile’)->group(‘mobile’)->paginate(10);// 此时总页数和分页的 html 异常

// 修改后
$buildSql=Db::name(‘sms_log’)->field(‘mobile’)->group(‘mobile’)->buildSql();
$list=Db::table($buildSql)->alias(‘bs’)->paginate(10);// 总页数和分页的 html 正确.

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于2024-09-10发表,共计4096字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)