Discuz!Q 首个版本 发布,你想说点什么!本文内容较多,门槛较高,请耐心观看!

安装方法

服务器环境为: PHP 7.2+ 和 MySQL 5.6+, 并且需要安装 Composer。Composer加速,可配置腾讯云镜像

内测下载 Discuz! Q

首先注册腾讯云帐号实名认证,然后在API密钥管理处新建一个密钥,运行列命令可下载 Discuz Q

composer config -g http-basic.cloud.comsenz-service.com ${QCLOUD_SECRET_ID} ${QCLOUD_SECRET_KEY}
composer create-project --prefer-dist comsenz/discuz . --prefer-dist
正式发布后下载 Discuz! Q
composer create-project --prefer-dist comsenz/discuz . --prefer-dist
cd resources/frame
npm install
npm run build
背景故事

Discuz! Q项目由于是从 0 到 1,介于我们的目标,如果从第一行代码开始编写,是极为庞大的工程。想想Discuz!X,代码量依赖 10 多年的时间的积累,才完善出各种工具类、自己的框架及插件机制等。
在此背景下,我们必须借助开源的力量,才得以快速构建出Discuz! Q。以下是整个Discuz! Q中所用到的技术栈,在此特别感谢他们

安装服务器要求

Discuz! Q 对系统有一些要求,请确保你的服务器满足以下要求:

  • PHP >= 7.2.0
  • BCMath PHP 拓展
  • Ctype PHP 拓展
  • JSON PHP 拓展
  • Mbstring PHP 拓展
  • OpenSSL PHP 拓展
  • PDO PHP 拓展
  • Tokenizer PHP 拓展
  • XML PHP 拓展
安装 Discuz! Q

Discuz! Q 使用 Composer 来管理项目依赖。因此,在使用 Discuz! Q 之前,请确保你的机器已经安装了 Composer。

通过站点启动安装

首先,部署好服务器环境:
然后打开 http://{您的域名}/install 进入安装界面,按照页面提示进行安装。

配置公共路径

安装完 Discuz! Q 之后,你应该配置你的 web 服务的文档目录指向 public 路径。该路径下的 index.php 文件作为所有接口控制器, index.html 作为所有前端 HTTP 请求控制器。

配置文件

Discuz! Q 的所有配置文件存放在 config 目录下。每个选项都有文档标注,便于通过文件查看并熟悉对你有用的选项。

目录权限

在安装 Discuz! Q 后,你可能需要配置一些权限。 storage 目录在你的 web 服务下应该是可写的权限,否则 Discuz! Q 将无法运行。

应用密钥

安装好 Discuz! Q 之后的下一步是设置你的应用密钥为随机字符串。如果你通过 Composer 或者 Discuz! Q 安装器安装的,这个密钥已经通过 php disco key:generate 命令为你设置好了。
通常,这个字符串应该是 32 个字符长度。这个密钥将会设置在 config.php 文件中。如果应用密钥还没有设置,你的用户会话和其他的加密数据将会不安全

其他配置

Discuz! Q 几乎不需要除上面所说的其他什么配置了。打开你的站点开始你建站之旅然而,你可能会想要再次查看 config/config.php 文件和它的注释说明。它包含一些你希望根据你应用来更改的选项,诸如: timezone 和 locale 。

Web 服务器配置优雅链接Apache

Discuz! Q 中包含了一个 public/.htaccess 文件,通常用于在资源路径中隐藏 index.php 的前端控制器。在用 Apache 为 Discuz! Q 提供服务之前,确保启用了 mod_rewrite 模块,这样 .htaccess 文件才能被服务器解析。

如果 Discuz! Q 附带的 .htaccess 文件不起作用,尝试下面的方法替代:

Options +FollowSymLinks -Indexes
RewriteEngine On

RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
Nginx

如果你使用 Nginx ,在你的站点配置中加入以下配置,建义开启 https 和 http2 ,所有的请求将会引导至 index.html 前端控制器和后端 index.php API控制器:

listen 443 ssl http2;
server_name www.xxx.com;
root /home/wwww;

ssl_certificate /xxx/server.crt;
ssl_certificate_key /xxx/server.key;

ssl_session_cache shared:SSL:1m;
ssl_session_timeout  5m;

ssl_prefer_server_ciphers   on;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256::!MD5;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
add_header Strict-Transport-Security "max-age=31536000";

add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";

index index.html;

location /install {
        try_files $uri $uri/ /index.php?$query_string;
}

location /api {
    try_files $uri $uri/ /index.php?$query_string;
}

location / {
    try_files $uri $uri/ /index.html;
}

location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt  { access_log off; log_not_found off; }

error_page 404 /index.php;
配置信息
<?php

return [
    'debug' => true,
    'locale' => 'zh-CN',
    'fallback_locale' => 'zh-CN',
    'timezone' => 'Asia/Shanghai',
    'key' => 'base64:JtNRiS14Mopb+HNi3ztxi6259im9DTDBJXOzLDbcquw=',
    'cipher' => 'AES-256-CBC',
    'site_url' => 'DummySiteUrl',
    'database' =>
        [
            'driver' => 'mysql',
            'host' => 'DummyDbHost',
            'port' => 'DummyDbPort',
            'database' => 'DummyDbDatabase',
            'username' => 'DummyDbUsername',
            'password' => 'DummyDbPassword',
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => 'DummyDbPrefix',
            'prefix_indexes' => true,
            'strict' => true,
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => '',
            ]) : [],
        ],
    'redis' => [
        'client' => 'phpredis',

        'options' => [
            'cluster' => 'redis',
            'prefix' => Str::slug('discuz', '_').'_database_'
        ],

        'default' => [
            'url' => '',
            'host' => '127.0.0.1',
            'password' => '123',
            'port' => 6379,
            'database' => 0
        ],

        'cache' => [
            'url' => '',
            'host' => '127.0.0.1',
            'password' => '123',
            'port' => 6379,
            'database' => 1
        ],
    ],
    //缓存系统配置
    'cache' => [
        'default' => 'file', //如果配置的 redis 可用, 会自动切换为redis

        'stores' => [
            'file' => [
                'driver' => 'file',
                'path' => storage_path('cache/data'),
            ],
            'redis' => [
                'driver' => 'redis',
                'connection' => 'cache',
            ],
        ],

        'prefix' => 'discuz_cache',

    ],
    //文件系统配置
    'filesystems' => [
        'default' => 'local',
        'cloud' => '',
        'disks' => [
            'local' => [
                'driver' => 'local',
                'root' => storage_path('app'),
            ],
            'public' => [
                'driver' => 'local',
                'root' => storage_path('app/public'),
                'url' => 'public',
                'visibility' => 'public',
            ],
            'avatar' => [
                'driver' => 'local',
                'root' => storage_path('app/public/avatars'),
                'url' => 'avatar',
                'visibility' => 'public',
            ],
            'attachment' => [
                'driver' => 'local',
                'root'   => storage_path('app/public/attachment'),
                'url'    => 'attachment'
            ],
            'cos' => [
                'driver' => 'cos',
                'region' => 'ap-beijing', //设置一个默认的存储桶地域
                'schema' => 'https', //协议头部,默认为http
                'bucket' => 'test-1251011534',
                'read_from_cdn' => false, //是否从cdn读取,如果为true , 设置cdn地址
                'credentials'=> [
                    'secretId'  => 'COS_SECRETID',  //"云 API 密钥 SecretId";
                    'secretKey' => 'COS_SECRETKEY', //"云 API 密钥 SecretKey";
                    'token' => 'token' //"临时密钥 token";
                ]
            ]
        ]
    ],
    'queue' => [
        'default' => 'redis',
        'connections' => [
            'redis' => [
                'driver' => 'redis',
                'connection' => 'default',
                'queue' => 'REDIS_QUEUE',
                'retry_after' => 90,
                'block_for' => null,
            ]
        ]
    ],
    'excel' => [
        'root' => storage_path('public/exports')
    ],
    //加载ServiceProvider
    'providers' => [
        AppFormatterFormatterServiceProvider::class,
        AppProvidersEventServiceProvider::class,
        AppProvidersSettingsServiceProvider::class,
        AppProvidersCategoryServiceProvider::class,
        AppProvidersUserServiceProvider::class,
        AppProvidersThreadServiceProvider::class,
        AppProvidersPostServiceProvider::class,
        AppProvidersOrderServiceProvider::class,
        AppProvidersUserWalletLogsServiceProvider::class,
        AppProvidersUserWalletCashServiceProvider::class,
        AppProvidersUserWalletServiceProvider::class,
    ],
    'sms' => [
        // HTTP 请求的超时时间(秒)
        'timeout' => 5.0,

        // 默认发送配置
        'default' => [
            // 网关调用策略,默认:顺序调用
            'strategy' => OvertrueEasySmsStrategiesOrderStrategy::class,

            // 默认可用的发送网关
            'gateways' => [
                'qcloud'
            ],
        ],
        // 可用的网关配置
        'gateways' => [
            'errorlog' => [
                'file' => storage_path('log/easy-sms.log')
            ],
            'qcloud' => [
                'sdk_app_id' => 'xxx', // SDK APP ID
                'app_key' => 'xxxx', // APP KEY
                'sign_name' => 'xxx', // 短信签名,如果使用默认签名,该字段可缺省(对应官方文档中的sign)
            ],
        ],
    ]
];
API 说明

jsonapi.orgDiscuz! Q 使用的是 jsonapi.org 定义的格式,使用 tobscure/json-api 包的实现。

路由分别对应
  • 以psr-2基础命名规则外,下面为实践中相关命名规则
  • 以user为例, 路由和路由名称为小写复数按功能以.分开
  • 控制器按功能,列表为复数,其它单条担任可为单数驼峰命名
  • 序列化模型名为单数驼峰命名
  • 数据库模型以单数驼峰命名
  • 表名以复数命名

下面为具体增删改查路由例子:

$route->get('/users', 'users.index', ListUsersController::class);
$route->get('/users/{id}', 'users.resource', ResourceUserController::class);
$route->post('/users', 'users.create', CreateUserController::class);
$route->patch('/users/{id}', 'users.update', UpdateUserController::class);
$route->delete('/users/{id}', 'users.delete', DeleteUserController::class);
列表数据

继承 DiscuzApiControllerAbstractListController 需要指定 $serializer 要用于序列化模型的 data 方法,并实现返回模型集合的方法。该 data 方法接收 Request 对象和 tobscure/json-api Document。

class ListUsersController extends AbstractListController
{
    public $serializer = UserSerializer::class;

    public function data(ServerRequestInterface $request, Document $document)
    {
        return User::all();
    }
}
单条数据

同列表数据一样继承 DiscuzApiControllerAbstractResourceController 并实现相关属性和方法,接收参数一样

class ResourceUserController extends AbstractResourceController
{
    public $serializer = UserSerializer::class;

    public function data(ServerRequestInterface $request, Document $document)
    {
        $body = $request->getQueryParams();
        $id = $body->get('id');

        return User::findOrFail($id);
    }
}
创建数据

继承 DiscuzApiControllerAbstractCreateController。这与单条数据相同,但响应状态代码将自动设置为 201 Created。您可以通过以下方式访问传入的 JSON:API POST传过来的数据可通过 $request->getParsedBody() 来取到 json 数组,并通过 Laravel Arr 类来获取数组相关信息

class CreateUserController extends AbstractCreateController
{
    public $serializer = UserSerializer::class;

    public function data(ServerRequestInterface $request, Document $document)
    {
        $attributes = Arr::get($request->getParsedBody(), 'data.attributes');

        return User::create([
            'name' => Arr::get($attributes, 'name')
        ]);
    }
}
更新数据

继承 DiscuzApiControllerAbstractResourceController 同单条数据一样,现相关属性和方法,接收参数一样,返回相关数据,获取同创建数据一样

class UpdateUserController extends AbstractCreateController
{
    public $serializer = UserSerializer::class;

    public function data(ServerRequestInterface $request, Document $document)
    {
        $attributes = Arr::get($request->getParsedBody(), 'data.attributes', []);

        $body = $request->getQueryParams();
        $attributes = $body->get('id');

        $user = User::findOrFail($id);

        $user->name = $attributes['name'];

        $user->save();

        return $user;
    }
}
删除数据

继承 DiscuzApiControllerAbstractDeleteController 实现delete 方法,该方法接收 Request,默认返回空 204 No Content 响应。

class DeleteUserController extends AbstractDeleteController

    public function delete(ServerRequestInterface $request)
    {
        $id = Arr::get($request->getQueryParams(), 'id');

        User::findOrFail($id)->delete();
    }
}
序列化模型说明

继承 DiscuzApiSerializerAbstractSerializer 实现 getDefaultAttributes 方法,该方法接收数据库模型,写好 type 属性

class UserSerializer extends AbstractSerializer
{
    protected $type = 'user';

    public function getDefaultAttributes($model)
    {
        return [
            'id' => $model->id,
            'username' => $model->username,
            'email' => $model->email
        ];
    }
}
自定义返回类型

实现接口 PsrHttpServerRequestHandlerInterface

<?php

namespace DiscuzApiController;

use PsrHttpServerRequestHandlerInterface;

class AbstractSerializeController implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {

        return new HtmlResponse();
    }
}

更多有趣内容,请安装后回头讨论!

点赞(5) 打赏

评论列表 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部