├── tests
├── _data
│ └── .gitkeep
├── _output
│ └── .gitignore
├── api.suite.yml
├── _support
│ ├── Helper
│ │ └── Api.php
│ └── ApiTester.php
├── _bootstrap.php
└── api
│ ├── LoginUserCest.php
│ └── CreateUserCest.php
├── runtime
└── .gitignore
├── web
├── assets
│ └── .gitignore
├── .htaccess
└── index.php
├── .bowerrc
├── core
├── messages
│ └── zh-CN
│ │ ├── exception.php
│ │ └── app.php
├── exceptions
│ ├── ErrorCodes.php
│ ├── InternalException.php
│ └── InvalidArgumentException.php
├── types
│ ├── UserStatus.php
│ └── BaseType.php
├── EventBootstrap.php
├── traits
│ └── ServiceTrait.php
├── requests
│ ├── LoginRequest.php
│ └── JoinRequest.php
├── behaviors
│ └── LoggerBehavior.php
├── services
│ └── UserService.php
└── models
│ └── User.php
├── config
├── params.php
├── test.php
├── console.php
├── web.php
└── common.php
├── docker-compose.yml
├── .github
├── FUNDING.yml
└── workflows
│ ├── lint.yml
│ └── test.yml
├── .editorconfig
├── grumphp.yml
├── modules
└── v1
│ ├── Module.php
│ └── controllers
│ ├── UserController.php
│ └── ActiveController.php
├── .env.example
├── yii.bat
├── .gitignore
├── _ide_helper.php
├── yii
├── mail
└── layouts
│ └── html.php
├── .scrutinizer.yml
├── codeception.yml
├── commands
├── HelloController.php
└── GenerateController.php
├── controllers
└── SiteController.php
├── migrations
└── m200717_082932_create_user_table.php
├── LICENSE.md
├── composer.json
├── requirements.php
└── README.md
/tests/_data/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/runtime/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/tests/_output/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/web/assets/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory" : "vendor/bower-asset"
3 | }
4 |
--------------------------------------------------------------------------------
/web/.htaccess:
--------------------------------------------------------------------------------
1 | RewriteEngine on
2 | RewriteCond %{REQUEST_FILENAME} !-d
3 | RewriteCond %{REQUEST_FILENAME} !-f
4 | RewriteRule . index.php [L]
5 |
--------------------------------------------------------------------------------
/core/messages/zh-CN/exception.php:
--------------------------------------------------------------------------------
1 | '参数异常',
7 | ];
8 |
--------------------------------------------------------------------------------
/core/exceptions/ErrorCodes.php:
--------------------------------------------------------------------------------
1 | env('APP_URL'),
5 | 'adminEmail' => env('ADMIN_EMAIL'),
6 | 'senderEmail' => env('SENDER_EMAIL'),
7 | 'senderName' => env('SENDER_NAME', env('APP_NAME')),
8 | ];
9 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | php:
4 | image: yiisoftware/yii2-php:7.1-apache
5 | volumes:
6 | - ~/.composer-docker/cache:/root/.composer/cache:delegated
7 | - ./:/app:delegated
8 | ports:
9 | - '8000:80'
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | open_collective: yiier
4 | custom: ['https://blog-1251237404.cos.ap-guangzhou.myqcloud.com/20190424153510.png', 'https://blog-1251237404.cos.ap-guangzhou.myqcloud.com/20190424153431.png']
5 |
--------------------------------------------------------------------------------
/tests/_support/Helper/Api.php:
--------------------------------------------------------------------------------
1 | 'basic-tests',
12 | ], $web);
13 |
14 | return ArrayHelper::merge($common, $config);
15 |
--------------------------------------------------------------------------------
/core/messages/zh-CN/app.php:
--------------------------------------------------------------------------------
1 | '成功',
5 | 'Incorrect username or password.' => '账号或者密码错误。',
6 | '{attribute} can only be numbers and letters.' => '{attribute}只能为数字和字母。',
7 | 'Username' => '用户名',
8 | 'Email' => '邮箱',
9 | 'Password' => '密码',
10 | 'The JWT secret must be configured first.' => '必须先配置 JWT_SECRET',
11 | ];
12 |
--------------------------------------------------------------------------------
/web/index.php:
--------------------------------------------------------------------------------
1 | run();
12 |
--------------------------------------------------------------------------------
/tests/_bootstrap.php:
--------------------------------------------------------------------------------
1 | 'active',
17 | self::UNACTIVATED => 'unactivated',
18 | ];
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/modules/v1/Module.php:
--------------------------------------------------------------------------------
1 |
7 | rem @link http://www.yiiframework.com/
8 | rem @copyright Copyright (c) 2008 Yii Software LLC
9 | rem @license http://www.yiiframework.com/license/
10 | rem -------------------------------------------------------------
11 |
12 | @setlocal
13 |
14 | set YII_PATH=%~dp0
15 |
16 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
17 |
18 | "%PHP_COMMAND%" "%YII_PATH%yii" %*
19 |
20 | @endlocal
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # phpstorm project files
2 | .idea
3 |
4 | # netbeans project files
5 | nbproject
6 |
7 | # zend studio for eclipse project files
8 | .buildpath
9 | .project
10 | .settings
11 |
12 | # windows thumbnail cache
13 | Thumbs.db
14 |
15 | # composer vendor dir
16 | /vendor
17 |
18 | # composer itself is not needed
19 | composer.phar
20 |
21 | # Mac DS_Store Files
22 | .DS_Store
23 |
24 | # phpunit itself is not needed
25 | phpunit.phar
26 | # local phpunit config
27 | /phpunit.xml
28 |
29 | tests/_output/*
30 | tests/_support/_generated
31 |
32 | #vagrant folder
33 | /.vagrant
34 |
35 | # env
36 | .env
37 | .env.testing
38 |
--------------------------------------------------------------------------------
/_ide_helper.php:
--------------------------------------------------------------------------------
1 | run();
22 | exit($exitCode);
23 |
--------------------------------------------------------------------------------
/tests/_support/ApiTester.php:
--------------------------------------------------------------------------------
1 | beforeSend($event);
17 | });
18 |
19 | Event::on(Controller::class, Controller::EVENT_BEFORE_ACTION, function ($event) {
20 | \Yii::createObject(LoggerBehavior::class)->beforeAction();
21 | });
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/core/exceptions/InternalException.php:
--------------------------------------------------------------------------------
1 |
5 | * createTime : 2019/5/12 4:58 PM
6 | * description:
7 | */
8 |
9 | namespace app\core\traits;
10 |
11 | use app\core\services\UserService;
12 | use Yii;
13 | use yii\base\InvalidConfigException;
14 |
15 | /**
16 | * Trait ServiceTrait
17 | * @property-read UserService $userService
18 | */
19 | trait ServiceTrait
20 | {
21 | /**
22 | * @return UserService|object
23 | */
24 | public function getUserService()
25 | {
26 | try {
27 | return Yii::createObject(UserService::class);
28 | } catch (InvalidConfigException $e) {
29 | return new UserService();
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/core/exceptions/InvalidArgumentException.php:
--------------------------------------------------------------------------------
1 |
9 | beginPage() ?>
10 |
11 |
12 |
13 |
14 | = Html::encode($this->title) ?>
15 | head() ?>
16 |
17 |
18 | beginBody() ?>
19 | = $content ?>
20 | endBody() ?>
21 |
22 |
23 | endPage() ?>
24 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | build:
2 | nodes:
3 | analysis:
4 | tests:
5 | override:
6 | - php-scrutinizer-run
7 | checks:
8 | php:
9 | code_rating: true
10 | remove_extra_empty_lines: true
11 | remove_php_closing_tag: true
12 | remove_trailing_whitespace: true
13 | fix_use_statements:
14 | remove_unused: true
15 | preserve_multiple: false
16 | preserve_blanklines: true
17 | order_alphabetically: true
18 | fix_php_opening_tag: true
19 | fix_linefeed: true
20 | fix_line_ending: true
21 | fix_identation_4spaces: true
22 | fix_doc_comments: true
23 | tools:
24 | external_code_coverage:
25 | timeout: 600
26 | runs: 3
27 | php_analyzer: true
28 |
--------------------------------------------------------------------------------
/codeception.yml:
--------------------------------------------------------------------------------
1 | actor: Tester
2 | bootstrap: _bootstrap.php
3 | paths:
4 | tests: tests
5 | log: tests/_output
6 | data: tests/_data
7 | helpers: tests/_support
8 | settings:
9 | memory_limit: 1024M
10 | colors: true
11 | modules:
12 | config:
13 | Yii2:
14 | configFile: 'config/test.php'
15 |
16 | coverage:
17 | enabled: true
18 |
19 | # To enable code coverage:
20 | #coverage:
21 | # #c3_url: http://localhost:8080/index-test.php/
22 | # enabled: true
23 | # #remote: true
24 | # #remote_config: '../codeception.yml'
25 | # whitelist:
26 | # include:
27 | # - models/*
28 | # - controllers/*
29 | # - commands/*
30 | # - mail/*
31 | # blacklist:
32 | # include:
33 | # - assets/*
34 | # - config/*
35 | # - runtime/*
36 | # - vendor/*
37 | # - views/*
38 | # - web/*
39 | # - tests/*
40 |
--------------------------------------------------------------------------------
/commands/HelloController.php:
--------------------------------------------------------------------------------
1 |
20 | * @since 2.0
21 | */
22 | class HelloController extends Controller
23 | {
24 | /**
25 | * This command echoes what you have entered as the message.
26 | * @param string $message the message to be echoed.
27 | * @return int Exit code
28 | */
29 | public function actionIndex($message = 'hello world')
30 | {
31 | echo $message . "\n";
32 |
33 | \Yii::error(['request_id' => \Yii::$app->requestId->id, 'test_request_id']);
34 | return ExitCode::OK;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/commands/GenerateController.php:
--------------------------------------------------------------------------------
1 | stdout("{$item} key [{$key}] set successfully.\n", Console::FG_GREEN);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/config/console.php:
--------------------------------------------------------------------------------
1 | 'basic-console',
8 | 'basePath' => dirname(__DIR__),
9 | 'controllerNamespace' => 'app\commands',
10 | 'aliases' => [
11 | '@bower' => '@vendor/bower-asset',
12 | '@npm' => '@vendor/npm-asset',
13 | '@tests' => '@app/tests',
14 | ],
15 | 'components' => [
16 | 'user' => [
17 | 'class' => 'yii\web\User',
18 | 'identityClass' => 'app\models\User',
19 | 'enableSession' => false,
20 | 'enableAutoLogin' => false,
21 | ],
22 | 'urlManager' => [
23 | 'baseUrl' => env('APP_URL'),
24 | 'hostInfo' => env('APP_URL')
25 | ],
26 | ],
27 | 'params' => $params,
28 | ];
29 |
30 | if (YII_ENV_DEV) {
31 | // configuration adjustments for 'dev' environment
32 | $config['bootstrap'][] = 'gii';
33 | $config['modules']['gii'] = [
34 | 'class' => 'yii\gii\Module',
35 | ];
36 | }
37 |
38 | return \yii\helpers\ArrayHelper::merge($common, $config);
39 |
--------------------------------------------------------------------------------
/controllers/SiteController.php:
--------------------------------------------------------------------------------
1 | errorHandler->exception;
34 | if ($exception !== null) {
35 | Yii::error([
36 | 'request_id' => Yii::$app->requestId->id,
37 | 'exception' => $exception->getMessage(),
38 | 'line' => $exception->getLine(),
39 | 'file' => $exception->getFile(),
40 | ], 'response_data_error');
41 | return ['code' => $exception->getCode(), 'message' => $exception->getMessage()];
42 | }
43 | return [];
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/migrations/m200717_082932_create_user_table.php:
--------------------------------------------------------------------------------
1 | createTable('{{%user}}', [
16 | 'id' => $this->primaryKey(),
17 | 'username' => $this->string(60)->notNull()->unique(),
18 | 'avatar' => $this->string()->defaultValue(''),
19 | 'email' => $this->string(120)->notNull()->unique(),
20 | 'auth_key' => $this->string()->notNull(),
21 | 'password_hash' => $this->string()->notNull(),
22 | 'password_reset_token' => $this->string()->defaultValue(''),
23 | 'status' => $this->tinyInteger()->defaultValue(0),
24 | 'created_at' => $this->timestamp()->defaultValue(null),
25 | 'updated_at' => $this->timestamp()->defaultValue(null),
26 | ]);
27 | }
28 |
29 | /**
30 | * {@inheritdoc}
31 | */
32 | public function safeDown()
33 | {
34 | $this->dropTable('{{%user}}');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/core/types/BaseType.php:
--------------------------------------------------------------------------------
1 | 'trim'],
20 | [['username', 'password'], 'required'],
21 |
22 | ['password', 'validatePassword'],
23 | ];
24 | }
25 |
26 | public function validatePassword($attribute, $params)
27 | {
28 | if (!$this->hasErrors()) {
29 | $user = UserService::getUserByUsernameOrEmail($this->username);
30 | if (!$user || !$user->validatePassword($this->password)) {
31 | $this->addError($attribute, t('app', 'Incorrect username or password.'));
32 | }
33 | Yii::$app->user->setIdentity($user);
34 | }
35 | }
36 |
37 | /**
38 | * @inheritdoc
39 | */
40 | public function attributeLabels()
41 | {
42 | return [
43 | 'username' => t('app', 'Username'),
44 | 'password' => t('app', 'Password'),
45 | 'email' => t('app', 'Email'),
46 | ];
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/core/requests/JoinRequest.php:
--------------------------------------------------------------------------------
1 | '/^[a-z]\w*$/i',
26 | 'message' => t('app', '{attribute} can only be numbers and letters.')
27 | ],
28 | ['username', 'unique', 'targetClass' => User::class],
29 | ['username', 'string', 'min' => 4, 'max' => 60],
30 |
31 | ['email', 'string', 'min' => 2, 'max' => 120],
32 | ['email', 'unique', 'targetClass' => User::class],
33 | ['email', 'email'],
34 |
35 | ['password', 'required'],
36 | ['password', 'string', 'min' => 6],
37 | ];
38 | }
39 |
40 | /**
41 | * @inheritdoc
42 | */
43 | public function attributeLabels()
44 | {
45 | return [
46 | 'username' => t('app', 'Username'),
47 | 'password' => t('app', 'Password'),
48 | 'email' => t('app', 'Email'),
49 | ];
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright © 2008 by forecho (https://forecho.com)
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | * Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | * Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in
12 | the documentation and/or other materials provided with the
13 | distribution.
14 | * Neither the name of Yii Software LLC nor the names of its
15 | contributors may be used to endorse or promote products derived
16 | from this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/core/behaviors/LoggerBehavior.php:
--------------------------------------------------------------------------------
1 | 'beforeSend',
18 | Controller::EVENT_BEFORE_ACTION => 'beforeAction'
19 | ];
20 | }
21 |
22 | /**
23 | * @param $event
24 | * @throws \Exception
25 | */
26 | public function beforeSend($event)
27 | {
28 | $response = $event->sender;
29 | if ($response->format != 'html') {
30 | $request = \Yii::$app->request;
31 | $requestId = Yii::$app->requestId->id;
32 | $code = ArrayHelper::getValue($response->data, 'code');
33 | $message = [
34 | 'request_id' => $requestId,
35 | 'type' => $code === 0 ? 'response_data_success' : 'response_data_error',
36 | 'header' => Json::encode($request->headers),
37 | 'params' => $request->bodyParams,
38 | 'url' => $request->absoluteUrl,
39 | 'response' => Json::encode($response->data)
40 | ];
41 | $response->data = ['request_id' => $requestId] + $response->data;
42 | $code === 0 ? Yii::info($message, 'request') : Yii::error($message, 'request');
43 | }
44 | }
45 |
46 | public function beforeAction()
47 | {
48 | return Yii::$app->requestId->id;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/api/LoginUserCest.php:
--------------------------------------------------------------------------------
1 | haveHttpHeader('content-type', 'application/json');
27 | $I->sendPOST('/login', [
28 | 'username' => 'demo',
29 | 'password' => 'pass123456',
30 | ]);
31 | $I->seeResponseCodeIs(HttpCode::OK); // 200
32 | $I->seeResponseIsJson();
33 | $I->seeResponseContainsJson(['code' => ErrorCodes::INVALID_ARGUMENT_ERROR]);
34 | }
35 |
36 |
37 | /**
38 | * @param ApiTester $I
39 | * @param CreateUserCest $createUserCest
40 | */
41 | public function userLogin(ApiTester $I, CreateUserCest $createUserCest)
42 | {
43 | $createUserCest->createUser($I);
44 | $I->haveHttpHeader('content-type', 'application/json');
45 | $I->sendPOST('/login', [
46 | 'username' => 'demo',
47 | 'password' => 'pass123',
48 | ]);
49 | $I->seeResponseCodeIs(HttpCode::OK); // 200
50 | $I->seeResponseIsJson();
51 | $I->seeResponseContainsJson(['code' => 0]);
52 | $I->seeResponseJsonMatchesXpath('//data/token');
53 | $I->seeResponseJsonMatchesXpath('//data/user/username');
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/modules/v1/controllers/UserController.php:
--------------------------------------------------------------------------------
1 | request->bodyParams;
39 | $data = $this->validate(new JoinRequest(), $params);
40 |
41 | /** @var JoinRequest $data */
42 | return $this->userService->createUser($data);
43 | }
44 |
45 | /**
46 | * @return string[]
47 | * @throws InvalidArgumentException|\Throwable
48 | */
49 | public function actionLogin()
50 | {
51 | $params = Yii::$app->request->bodyParams;
52 | $this->validate(new LoginRequest(), $params);
53 | $token = $this->userService->getToken();
54 | $user = Yii::$app->user->identity;
55 |
56 | return [
57 | 'user' => $user,
58 | 'token' => (string)$token,
59 | ];
60 | }
61 |
62 | public function actionRefreshToken()
63 | {
64 | $user = Yii::$app->user->identity;
65 | $token = $this->userService->getToken();
66 | return [
67 | 'user' => $user,
68 | 'token' => (string)$token,
69 | ];
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/core/services/UserService.php:
--------------------------------------------------------------------------------
1 | username = $request->username;
27 | $user->email = $request->email;
28 | $user->setPassword($request->password);
29 | $user->generateAuthKey();
30 | if (!$user->save()) {
31 | throw new \yii\db\Exception(Setup::errorMessage($user->firstErrors));
32 | }
33 | } catch (Exception $e) {
34 | Yii::error(
35 | ['request_id' => Yii::$app->requestId->id, $user->attributes, $user->errors, (string)$e],
36 | __FUNCTION__
37 | );
38 | throw new InternalException($e->getMessage());
39 | }
40 | return $user;
41 | }
42 |
43 |
44 | /**
45 | * @return string
46 | * @throws \Throwable
47 | */
48 | public function getToken(): string
49 | {
50 | /** @var Jwt $jwt */
51 | $jwt = Yii::$app->jwt;
52 | if (!$jwt->key) {
53 | throw new InternalException(t('app', 'The JWT secret must be configured first.'));
54 | }
55 | $signer = $jwt->getSigner('HS256');
56 | $key = $jwt->getKey();
57 | $time = time();
58 | return (string)$jwt->getBuilder()
59 | ->issuedBy(params('appUrl'))
60 | ->identifiedBy(Yii::$app->name, true)
61 | ->issuedAt($time)
62 | ->expiresAt($time + 3600 * 72)
63 | ->withClaim('username', \user('username'))
64 | ->withClaim('id', \user('id'))
65 | ->getToken($signer, $key);
66 | }
67 |
68 |
69 | /**
70 | * @param string $value
71 | * @return User|ActiveRecord|null
72 | */
73 | public static function getUserByUsernameOrEmail(string $value)
74 | {
75 | $condition = strpos($value, '@') ? ['email' => $value] : ['username' => $value];
76 | return User::find()->where(['status' => UserStatus::ACTIVE])
77 | ->andWhere($condition)
78 | ->one();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/tests/api/CreateUserCest.php:
--------------------------------------------------------------------------------
1 | [
29 | 'username' => 'demo',
30 | 'email' => 'demo',
31 | 'password' => 'pass123',
32 | ],
33 | 'code' => ErrorCodes::INVALID_ARGUMENT_ERROR
34 | ],
35 | [
36 | 'data' => [
37 | 'username' => 'demo-sdsdkj',
38 | 'email' => 'demo@yii.com',
39 | 'password' => 'pass123',
40 | ],
41 | 'code' => ErrorCodes::INVALID_ARGUMENT_ERROR
42 | ],
43 | [
44 | 'data' => [
45 | 'username' => 'demo-sdsdkj',
46 | 'email' => 'demo@yii.com',
47 | 'password' => 'pass1',
48 | ],
49 | 'code' => ErrorCodes::INVALID_ARGUMENT_ERROR
50 | ],
51 | [
52 | 'data' => [
53 | 'username' => 'demo-sdsdkj',
54 | 'password' => 'pass1',
55 | ],
56 | 'code' => ErrorCodes::INVALID_ARGUMENT_ERROR
57 | ],
58 | ];
59 | }
60 |
61 | /**
62 | * @dataProvider makeValidateFailItems
63 | * @param ApiTester $I
64 | * @param Example $example
65 | */
66 | public function createUserViaAPIFail(ApiTester $I, Example $example)
67 | {
68 | $I->haveHttpHeader('content-type', 'application/json');
69 | $I->sendPOST('/join', $example['data']);
70 | $I->seeResponseCodeIs(HttpCode::OK); // 200
71 | $I->seeResponseIsJson();
72 | $I->seeResponseContainsJson(['code' => $example['code']]);
73 | }
74 |
75 | /**
76 | * @param ApiTester $I
77 | */
78 | public function createUser(ApiTester $I)
79 | {
80 | $I->haveHttpHeader('content-type', 'application/json');
81 | $I->sendPOST('/join', [
82 | 'username' => 'demo',
83 | 'email' => 'demo@yii.com',
84 | 'password' => 'pass123',
85 | ]);
86 | $I->seeResponseCodeIs(HttpCode::OK); // 200
87 | $I->seeResponseIsJson();
88 | $I->seeResponseContainsJson(['code' => 0]);
89 | $I->seeResponseJsonMatchesXpath('//data/username');
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "forecho/yii2-rest-api",
3 | "description": "Yii 2 REST API Project Template",
4 | "keywords": [
5 | "yii2",
6 | "framework",
7 | "rest",
8 | "basic",
9 | "project template"
10 | ],
11 | "type": "project",
12 | "license": "BSD-3-Clause",
13 | "support": {
14 | "issues": "https://github.com/forecho/yii2-rest-api/issues?state=open",
15 | "source": "https://github.com/forecho/yii2-rest-api"
16 | },
17 | "minimum-stability": "stable",
18 | "require": {
19 | "php": ">=7.2",
20 | "yiisoft/yii2": "~2.0.14",
21 | "yiisoft/yii2-swiftmailer": "~2.0.0 || ~2.1.0",
22 | "yiithings/yii2-dotenv": "^1.0",
23 | "sizeg/yii2-jwt": "^2.0",
24 | "yiier/yii2-helpers": "^2.0"
25 | },
26 | "require-dev": {
27 | "yiisoft/yii2-debug": "~2.1.0",
28 | "yiisoft/yii2-gii": "~2.1.0",
29 | "yiisoft/yii2-faker": "~2.0.0",
30 | "codeception/codeception": "^4.0",
31 | "codeception/verify": "~0.5.0 || ~1.1.0",
32 | "codeception/specify": "~0.4.6",
33 | "symfony/browser-kit": ">=2.7 <=4.2.4",
34 | "codeception/module-filesystem": "^1.0.0",
35 | "codeception/module-yii2": "^1.0.0",
36 | "codeception/module-asserts": "^1.0.0",
37 | "codeception/module-rest": "^1.0.0",
38 | "codeception/module-phpbrowser": "^1.0.0",
39 | "squizlabs/php_codesniffer": "^3.5.5",
40 | "phpro/grumphp": "^0.19.1",
41 | "mis/yii2-ide-helper": "^1.0"
42 | },
43 | "config": {
44 | "process-timeout": 1800,
45 | "fxp-asset": {
46 | "enabled": false
47 | }
48 | },
49 | "autoload": {
50 | "files": [
51 | "vendor/yiier/yii2-helpers/src/GlobalFunctions.php",
52 | "vendor/yiier/yii2-helpers/src/SupportFunctions.php"
53 | ]
54 | },
55 | "scripts": {
56 | "post-install-cmd": [
57 | "yii\\composer\\Installer::postInstall"
58 | ],
59 | "post-create-project-cmd": [
60 | "yii\\composer\\Installer::postCreateProject",
61 | "yii\\composer\\Installer::postInstall"
62 | ]
63 | },
64 | "extra": {
65 | "yii\\composer\\Installer::postCreateProject": {
66 | "setPermission": [
67 | {
68 | "runtime": "0777",
69 | "web/assets": "0777",
70 | "yii": "0755"
71 | }
72 | ]
73 | },
74 | "yii\\composer\\Installer::postInstall": {
75 | "generateCookieValidationKey": [
76 | "config/web.php"
77 | ]
78 | }
79 | },
80 | "repositories": [
81 | {
82 | "type": "composer",
83 | "url": "https://asset-packagist.org"
84 | }
85 | ]
86 | }
87 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | # GitHub Action for Yii Framework with MySQL
2 | name: Testing
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 | jobs:
9 | yii:
10 | name: Yii2 (PHP ${{ matrix.php-versions }})
11 | runs-on: ubuntu-latest
12 | strategy:
13 | fail-fast: false
14 | matrix:
15 | php-versions: ['7.2']
16 | # php-versions: ['7.2', '7.3', '7.4']
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v2
20 |
21 | - name: Setup PHP, with composer and extensions
22 | uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php
23 | with:
24 | php-version: ${{ matrix.php-versions }}
25 | extensions: mbstring, intl, gd, imagick, zip, dom, mysql
26 | coverage: xdebug #optional
27 |
28 | - name: Set up MySQL
29 | uses: mirromutth/mysql-action@v1.1
30 | with:
31 | collation server: utf8mb4_unicode_ci
32 | mysql version: 5.7
33 | mysql database: yii2
34 | mysql root password: root
35 |
36 | - name: Get composer cache directory
37 | id: composercache
38 | run: echo "::set-output name=dir::$(composer config cache-files-dir)"
39 |
40 | - name: Cache composer dependencies
41 | uses: actions/cache@v2
42 | with:
43 | path: ${{ steps.composercache.outputs.dir }}
44 | # Use composer.json for key, if composer.lock is not committed.
45 | # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
46 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
47 | restore-keys: ${{ runner.os }}-composer-
48 |
49 | - name: Install Composer dependencies
50 | run: |
51 | composer global require hirak/prestissimo
52 | composer install --no-progress --prefer-dist --no-interaction
53 |
54 | - name: Prepare the application
55 | run: |
56 | php -r "file_exists('.env') || copy('.env.example', '.env');"
57 | php yii generate/key
58 |
59 | - name: Run Tests
60 | run: |
61 | vendor/bin/codecept build
62 | php yii migrate --interactive=0
63 | nohup php -S localhost:8080 > yii.log 2>&1 &
64 | vendor/bin/codecept run --coverage --coverage-xml=coverage.clover
65 | wget https://scrutinizer-ci.com/ocular.phar
66 | php ocular.phar code-coverage:upload --format=php-clover tests/_output/coverage.clover
67 | # bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }} || echo 'Codecov did not collect coverage reports'
68 |
--------------------------------------------------------------------------------
/config/web.php:
--------------------------------------------------------------------------------
1 | 'basic',
10 | 'basePath' => dirname(__DIR__),
11 | 'aliases' => [
12 | '@bower' => '@vendor/bower-asset',
13 | '@npm' => '@vendor/npm-asset',
14 | ],
15 | 'modules' => [
16 | 'v1' => [
17 | 'class' => 'app\modules\v1\Module',
18 | ],
19 | ],
20 | 'components' => [
21 | 'request' => [
22 | 'parsers' => [
23 | 'application/json' => 'yii\web\JsonParser',
24 | ],
25 | // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
26 | 'cookieValidationKey' => env('COOKIE_VALIDATION_KEY')
27 | ],
28 | 'response' => [
29 | 'class' => 'yii\web\Response',
30 | 'on beforeSend' => function ($event) {
31 | yii::createObject([
32 | 'class' => yiier\helpers\ResponseHandler::class,
33 | 'event' => $event,
34 | ])->formatResponse();
35 | },
36 | ],
37 | 'jwt' => [
38 | 'class' => \sizeg\jwt\Jwt::class,
39 | 'key' => env('JWT_SECRET'),
40 | ],
41 | 'formatter' => [
42 | 'dateFormat' => 'yyyy-MM-dd',
43 | 'datetimeFormat' => 'yyyy-MM-dd HH:mm:ss',
44 | 'decimalSeparator' => ',',
45 | 'thousandSeparator' => ' ',
46 | 'currencyCode' => 'CNY',
47 | ],
48 | 'user' => [
49 | 'identityClass' => User::class,
50 | 'enableAutoLogin' => true,
51 | ],
52 | 'errorHandler' => [
53 | 'errorAction' => 'site/error',
54 | ],
55 | 'mailer' => [
56 | 'class' => 'yii\swiftmailer\Mailer',
57 | // send all mails to a file by default. You have to set
58 | // 'useFileTransport' to false and configure a transport
59 | // for the mailer to send real emails.
60 | 'useFileTransport' => true,
61 | ],
62 | 'urlManager' => [
63 | 'enablePrettyUrl' => true,
64 | 'showScriptName' => false,
65 | 'rules' => [
66 | "POST /" => '/user/',
67 | "GET health-check" => 'site/health-check',
68 | '///' => '//',
69 | ],
70 | ],
71 | ],
72 | 'params' => $params,
73 | ];
74 |
75 | if (YII_ENV_DEV) {
76 | // configuration adjustments for 'dev' environment
77 | $config['bootstrap'][] = 'debug';
78 | $config['modules']['debug'] = [
79 | 'class' => 'yii\debug\Module',
80 | // uncomment the following to add your IP if you are not connecting from localhost.
81 | 'allowedIPs' => ['*'],
82 | ];
83 |
84 | $config['bootstrap'][] = 'gii';
85 | $config['modules']['gii'] = [
86 | 'class' => 'yii\gii\Module',
87 | // uncomment the following to add your IP if you are not connecting from localhost.
88 | 'allowedIPs' => ['*'],
89 | ];
90 | }
91 |
92 | return \yii\helpers\ArrayHelper::merge($common, $config);
93 |
--------------------------------------------------------------------------------
/modules/v1/controllers/ActiveController.php:
--------------------------------------------------------------------------------
1 | 'yii\rest\Serializer',
27 | 'collectionEnvelope' => 'items',
28 | ];
29 |
30 | public function behaviors()
31 | {
32 | $behaviors = parent::behaviors();
33 |
34 | // 跨区请求 必须先删掉 authenticator
35 | $behaviors['authenticator'];
36 | unset($behaviors['authenticator']);
37 |
38 | $behaviors['corsFilter'] = [
39 | 'class' => Cors::class,
40 | 'cors' => [
41 | 'Origin' => ['*'],
42 | 'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
43 | 'Access-Control-Request-Headers' => ['*'],
44 | 'Access-Control-Allow-Credentials' => true,
45 | 'Access-Control-Max-Age' => 86400,
46 | 'Access-Control-Expose-Headers' => [],
47 | ]
48 | ];
49 | $behaviors['authenticator'] = [
50 | 'class' => JwtHttpBearerAuth::class,
51 | 'optional' => array_merge($this->noAuthActions, ['options']),
52 | ];
53 |
54 | return $behaviors;
55 | }
56 |
57 | public function actions()
58 | {
59 | $actions = parent::actions();
60 | $actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider'];
61 | return $actions;
62 | }
63 |
64 | /**
65 | * @return \yii\data\ActiveDataProvider
66 | */
67 | public function prepareDataProvider()
68 | {
69 | $modelClass = $this->modelClass;
70 |
71 | $searchModel = new SearchModel(
72 | [
73 | 'defaultOrder' => ['id' => SORT_DESC],
74 | 'model' => $modelClass,
75 | 'scenario' => 'default',
76 | 'pageSize' => $this->getPageSize()
77 | ]
78 | );
79 |
80 | return $searchModel->search(['SearchModel' => Yii::$app->request->queryParams]);
81 | }
82 |
83 | /**
84 | * @return int
85 | */
86 | protected function getPageSize()
87 | {
88 | if ($pageSize = (int)request('pageSize')) {
89 | if ($pageSize < self::MAX_PAGE_SIZE) {
90 | return $pageSize;
91 | }
92 | return self::MAX_PAGE_SIZE;
93 | }
94 | return self::DEFAULT_PAGE_SIZE;
95 | }
96 |
97 |
98 | /**
99 | * @param Model $model
100 | * @param array $params
101 | * @return Model
102 | * @throws InvalidArgumentException
103 | */
104 | public function validate(Model $model, array $params): Model
105 | {
106 | $model->load($params, '');
107 | if (!$model->validate()) {
108 | throw new InvalidArgumentException(Setup::errorMessage($model->firstErrors));
109 | }
110 | return $model;
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/config/common.php:
--------------------------------------------------------------------------------
1 | env('APP_TIME_ZONE'),
5 | 'language' => env('APP_LANGUAGE'),
6 | 'name' => env('APP_NAME'),
7 | 'bootstrap' => ['log', 'ideHelper', \app\core\EventBootstrap::class],
8 | 'components' => [
9 | 'ideHelper' => [
10 | 'class' => 'Mis\IdeHelper\IdeHelper',
11 | 'configFiles' => [
12 | 'config/web.php',
13 | 'config/common.php',
14 | 'config/console.php',
15 | ],
16 | ],
17 | 'requestId' => [
18 | 'class' => \yiier\helpers\RequestId::class,
19 | ],
20 | 'cache' => [
21 | 'class' => 'yii\caching\FileCache',
22 | ],
23 | 'db' => [
24 | 'class' => 'yii\db\Connection',
25 | 'dsn' => env('DB_DSN'),
26 | 'username' => env('DB_USERNAME'),
27 | 'password' => env('DB_PASSWORD'),
28 | 'tablePrefix' => env('DB_TABLE_PREFIX'),
29 | 'charset' => 'utf8mb4',
30 | 'enableSchemaCache' => YII_ENV_PROD,
31 | 'schemaCacheDuration' => 60,
32 | 'schemaCache' => 'cache',
33 | ],
34 | 'i18n' => [
35 | 'translations' => [
36 | 'app*' => [
37 | 'class' => 'yii\i18n\PhpMessageSource',
38 | 'basePath' => '@app/core/messages',
39 | 'fileMap' => [
40 | 'app' => 'app.php',
41 | 'app/error' => 'exception.php',
42 | ],
43 | ],
44 | ],
45 | ],
46 | 'log' => [
47 | 'traceLevel' => YII_DEBUG ? 3 : 0,
48 | 'targets' => [
49 | /**
50 | * 错误级别日志:当某些需要立马解决的致命问题发生的时候,调用此方法记录相关信息。
51 | * 使用方法:Yii::error()
52 | */
53 | [
54 | 'class' => 'yiier\helpers\FileTarget',
55 | // 日志等级
56 | 'levels' => ['error'],
57 | // 被收集记录的额外数据
58 | 'logVars' => ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION'],
59 | // 指定日志保存的文件名
60 | 'logFile' => '@app/runtime/logs/error/app.log',
61 | // 是否开启日志 (@app/runtime/logs/error/20151223_app.log)
62 | 'enableDatePrefix' => true,
63 | ],
64 | /**
65 | * 警告级别日志:当某些期望之外的事情发生的时候,使用该方法。
66 | * 使用方法:Yii::warning()
67 | */
68 | [
69 | 'class' => 'yiier\helpers\FileTarget',
70 | // 日志等级
71 | 'levels' => ['warning'],
72 | // 被收集记录的额外数据
73 | 'logVars' => ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION'],
74 | // 指定日志保存的文件名
75 | 'logFile' => '@app/runtime/logs/warning/app.log',
76 | // 是否开启日志 (@app/runtime/logs/warning/20151223_app.log)
77 | 'enableDatePrefix' => true,
78 | ],
79 | [
80 | 'class' => 'yiier\helpers\FileTarget',
81 | 'levels' => ['info'],
82 | 'categories' => ['request'],
83 | 'logVars' => [],
84 | 'maxFileSize' => 1024,
85 | 'logFile' => '@app/runtime/logs/request/app.log',
86 | 'enableDatePrefix' => true
87 | ],
88 | [
89 | 'class' => 'yiier\helpers\FileTarget',
90 | 'levels' => ['warning'],
91 | 'categories' => ['debug'],
92 | 'logVars' => [],
93 | 'maxFileSize' => 1024,
94 | 'logFile' => '@app/runtime/logs/debug/app.log',
95 | 'enableDatePrefix' => true
96 | ],
97 | ],
98 | ],
99 | ],
100 | ];
101 |
--------------------------------------------------------------------------------
/core/models/User.php:
--------------------------------------------------------------------------------
1 | TimestampBehavior::class,
47 | 'value' => date('Y-m-d H:i:s'),
48 | ],
49 | ];
50 | }
51 |
52 | /**
53 | * @inheritdoc
54 | */
55 | public function rules()
56 | {
57 | return [
58 | ['status', 'default', 'value' => UserStatus::ACTIVE],
59 | ['status', 'in', 'range' => [UserStatus::ACTIVE, UserStatus::UNACTIVATED]],
60 | [['username'], 'string', 'max' => 60],
61 | ];
62 | }
63 |
64 | /**
65 | * @inheritdoc
66 | */
67 | public static function findIdentity($id)
68 | {
69 | return static::find()
70 | ->where(['id' => $id, 'status' => UserStatus::ACTIVE])
71 | ->limit(1)
72 | ->one();
73 | }
74 |
75 |
76 | /**
77 | * @param mixed $token
78 | * @param null $type
79 | * @return void|IdentityInterface
80 | */
81 | public static function findIdentityByAccessToken($token, $type = null)
82 | {
83 | $userId = (string)$token->getClaim('id');
84 | return self::findIdentity($userId);
85 | }
86 |
87 | /**
88 | * {@inheritdoc}
89 | */
90 | public function getId()
91 | {
92 | return $this->getPrimaryKey();
93 | }
94 |
95 | /**
96 | * {@inheritdoc}
97 | */
98 | public function getAuthKey()
99 | {
100 | return $this->auth_key;
101 | }
102 |
103 | /**
104 | * @inheritdoc
105 | */
106 | public function validateAuthKey($authKey)
107 | {
108 | return $this->getAuthKey() === $authKey;
109 | }
110 |
111 | /**
112 | * Validates password
113 | *
114 | * @param string $password password to validate
115 | * @return bool if password provided is valid for current user
116 | */
117 | public function validatePassword($password)
118 | {
119 | return Yii::$app->security->validatePassword($password, $this->password_hash);
120 | }
121 |
122 | /**
123 | * Generates password hash from password and sets it to the model
124 | *
125 | * @param string $password
126 | * @throws \yii\base\Exception
127 | */
128 | public function setPassword($password)
129 | {
130 | $this->password_hash = Yii::$app->security->generatePasswordHash($password);
131 | }
132 |
133 | /**
134 | * Generates "remember me" authentication key
135 | * @throws \yii\base\Exception
136 | */
137 | public function generateAuthKey()
138 | {
139 | $this->auth_key = Yii::$app->security->generateRandomString();
140 | }
141 |
142 | /**
143 | * Generates new password reset token
144 | * @throws \yii\base\Exception
145 | */
146 | public function generatePasswordResetToken()
147 | {
148 | $this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time();
149 | }
150 |
151 | /**
152 | * Removes password reset token
153 | */
154 | public function removePasswordResetToken()
155 | {
156 | $this->password_reset_token = null;
157 | }
158 |
159 | /**
160 | * @return array
161 | */
162 | public function fields()
163 | {
164 | $fields = parent::fields();
165 | unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);
166 |
167 | $fields['created_at'] = function (self $model) {
168 | return DateHelper::datetimeToIso8601($model->created_at);
169 | };
170 |
171 | $fields['updated_at'] = function (self $model) {
172 | return DateHelper::datetimeToIso8601($model->updated_at);
173 | };
174 |
175 | return $fields;
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/requirements.php:
--------------------------------------------------------------------------------
1 | Error\n\n"
34 | . "The path to yii framework seems to be incorrect.
\n"
35 | . 'You need to install Yii framework via composer or adjust the framework path in file ' . basename(__FILE__) . ".
\n"
36 | . 'Please refer to the README on how to install Yii.
\n";
37 |
38 | if (!empty($_SERVER['argv'])) {
39 | // do not print HTML when used in console mode
40 | echo strip_tags($message);
41 | } else {
42 | echo $message;
43 | }
44 | exit(1);
45 | }
46 |
47 | require_once($frameworkPath . '/requirements/YiiRequirementChecker.php');
48 | $requirementsChecker = new YiiRequirementChecker();
49 |
50 | $gdMemo = $imagickMemo = 'Either GD PHP extension with FreeType support or ImageMagick PHP extension with PNG support is required for image CAPTCHA.';
51 | $gdOK = $imagickOK = false;
52 |
53 | if (extension_loaded('imagick')) {
54 | $imagick = new Imagick();
55 | $imagickFormats = $imagick->queryFormats('PNG');
56 | if (in_array('PNG', $imagickFormats)) {
57 | $imagickOK = true;
58 | } else {
59 | $imagickMemo = 'Imagick extension should be installed with PNG support in order to be used for image CAPTCHA.';
60 | }
61 | }
62 |
63 | if (extension_loaded('gd')) {
64 | $gdInfo = gd_info();
65 | if (!empty($gdInfo['FreeType Support'])) {
66 | $gdOK = true;
67 | } else {
68 | $gdMemo = 'GD extension should be installed with FreeType support in order to be used for image CAPTCHA.';
69 | }
70 | }
71 |
72 | /**
73 | * Adjust requirements according to your application specifics.
74 | */
75 | $requirements = array(
76 | // Database :
77 | array(
78 | 'name' => 'PDO extension',
79 | 'mandatory' => true,
80 | 'condition' => extension_loaded('pdo'),
81 | 'by' => 'All DB-related classes',
82 | ),
83 | array(
84 | 'name' => 'PDO SQLite extension',
85 | 'mandatory' => false,
86 | 'condition' => extension_loaded('pdo_sqlite'),
87 | 'by' => 'All DB-related classes',
88 | 'memo' => 'Required for SQLite database.',
89 | ),
90 | array(
91 | 'name' => 'PDO MySQL extension',
92 | 'mandatory' => false,
93 | 'condition' => extension_loaded('pdo_mysql'),
94 | 'by' => 'All DB-related classes',
95 | 'memo' => 'Required for MySQL database.',
96 | ),
97 | array(
98 | 'name' => 'PDO PostgreSQL extension',
99 | 'mandatory' => false,
100 | 'condition' => extension_loaded('pdo_pgsql'),
101 | 'by' => 'All DB-related classes',
102 | 'memo' => 'Required for PostgreSQL database.',
103 | ),
104 | // Cache :
105 | array(
106 | 'name' => 'Memcache extension',
107 | 'mandatory' => false,
108 | 'condition' => extension_loaded('memcache') || extension_loaded('memcached'),
109 | 'by' => 'MemCache',
110 | 'memo' => extension_loaded('memcached') ? 'To use memcached set MemCache::useMemcached to true.' : ''
111 | ),
112 | // CAPTCHA:
113 | array(
114 | 'name' => 'GD PHP extension with FreeType support',
115 | 'mandatory' => false,
116 | 'condition' => $gdOK,
117 | 'by' => 'Captcha',
118 | 'memo' => $gdMemo,
119 | ),
120 | array(
121 | 'name' => 'ImageMagick PHP extension with PNG support',
122 | 'mandatory' => false,
123 | 'condition' => $imagickOK,
124 | 'by' => 'Captcha',
125 | 'memo' => $imagickMemo,
126 | ),
127 | // PHP ini :
128 | 'phpExposePhp' => array(
129 | 'name' => 'Expose PHP',
130 | 'mandatory' => false,
131 | 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"),
132 | 'by' => 'Security reasons',
133 | 'memo' => '"expose_php" should be disabled at php.ini',
134 | ),
135 | 'phpAllowUrlInclude' => array(
136 | 'name' => 'PHP allow url include',
137 | 'mandatory' => false,
138 | 'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"),
139 | 'by' => 'Security reasons',
140 | 'memo' => '"allow_url_include" should be disabled at php.ini',
141 | ),
142 | 'phpSmtp' => array(
143 | 'name' => 'PHP mail SMTP',
144 | 'mandatory' => false,
145 | 'condition' => strlen(ini_get('SMTP')) > 0,
146 | 'by' => 'Email sending',
147 | 'memo' => 'PHP mail SMTP server required',
148 | ),
149 | );
150 |
151 | // OPcache check
152 | if (!version_compare(phpversion(), '5.5', '>=')) {
153 | $requirements[] = array(
154 | 'name' => 'APC extension',
155 | 'mandatory' => false,
156 | 'condition' => extension_loaded('apc'),
157 | 'by' => 'ApcCache',
158 | );
159 | }
160 |
161 | $result = $requirementsChecker->checkYii()->check($requirements)->getResult();
162 | $requirementsChecker->render();
163 | exit($result['summary']['errors'] === 0 ? 0 : 1);
164 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Yii 2 REST API Project Template
6 |
7 |
8 |
9 | Yii 2 REST API Project Template is a skeleton [Yii 2](http://www.yiiframework.com/) application best for
10 | rapidly creating small rest api projects.
11 |
12 | The template contains the basic features including user join/login api.
13 | It includes all commonly used configurations that would allow you to focus on adding new
14 | features to your application.
15 |
16 | [](https://github.com/forecho/yii2-rest-api/actions)
17 | [](https://github.com/forecho/yii2-rest-api/actions)
18 | [](https://scrutinizer-ci.com/g/forecho/yii2-rest-api/?branch=master)
19 | [](https://scrutinizer-ci.com/g/forecho/yii2-rest-api/?branch=master)
20 | [](https://packagist.org/packages/forecho/yii2-rest-api)
21 | [](https://packagist.org/packages/forecho/yii2-rest-api)
22 | [](https://packagist.org/packages/forecho/yii2-rest-api)
23 | [](https://packagist.org/packages/forecho/yii2-rest-api)
24 |
25 | REQUIREMENTS
26 | ------------
27 |
28 | The minimum requirement by this project template that your Web server supports PHP 7.2.0.
29 |
30 | INSTALLATION
31 | ------------
32 |
33 | ### Install via Composer
34 |
35 | If you do not have [Composer](http://getcomposer.org/), you may install it by following the instructions
36 | at [getcomposer.org](http://getcomposer.org/doc/00-intro.md#installation-nix).
37 |
38 | You can then install this project template using the following command:
39 |
40 | ~~~
41 | composer create-project --prefer-dist forecho/yii2-rest-api
42 | cd
43 | cp .env.example .env
44 | php yii generate/key # optional
45 | chmod 777 -R runtime/
46 | ~~~
47 |
48 | Now you should be able to access the application through the following URL, assuming `rest-api` is the directory
49 | directly under the Web root.
50 |
51 | ~~~
52 | http://localhost//web/
53 | ~~~
54 |
55 | ### Install from GitHub
56 |
57 | Accessing [Use this template](https://github.com/forecho/yii2-rest-api/generate) Create a new repository from yii2-rest-api
58 |
59 | ```sh
60 | git clone xxxx
61 | cd
62 | cp .env.example .env
63 | chmod 777 -R runtime/
64 | ```
65 |
66 | You can then access the application through the following URL:
67 |
68 | ~~~
69 | http://localhost//web/
70 | ~~~
71 |
72 |
73 | ### Install with Docker
74 |
75 | Update your vendor packages
76 |
77 | ```sh
78 | docker-compose run --rm php composer update --prefer-dist
79 | ```
80 |
81 | Run the installation triggers (creating cookie validation code)
82 |
83 | ```sh
84 | docker-compose run --rm php composer install
85 | ```
86 |
87 | Start the container
88 |
89 | ```sh
90 | docker-compose up -d
91 | ```
92 |
93 | You can then access the application through the following URL:
94 |
95 | ```
96 | http://127.0.0.1:8000
97 | ```
98 |
99 | **NOTES:**
100 | - Minimum required Docker engine version `17.04` for development (see [Performance tuning for volume mounts](https://docs.docker.com/docker-for-mac/osxfs-caching/))
101 | - The default configuration uses a host-volume in your home directory `.docker-composer` for composer caches
102 |
103 | Check out the packages
104 | ------------
105 |
106 | - [yiithings/yii2-doten](https://github.com/yiithings/yii2-doten)
107 | - [sizeg/yii2-jwt](https://github.com/sizeg/yii2-jwt)
108 | - [yiier/yii2-helpers](https://github.com/yiier/yii2-helpers)
109 |
110 | Use
111 | ------------
112 |
113 | At this time, you have a RESTful API server running at `http://127.0.0.1:8000`. It provides the following endpoints:
114 |
115 | * `GET /health-check`: a health check service provided for health checking purpose (needed when implementing a server cluster)
116 | * `POST /v1/join`: create a user
117 | * `POST /v1/login`: authenticates a user and generates a JWT
118 | * `POST /v1/refresh-token`: refresh a JWT
119 |
120 | Try the URL `http://localhost:8000/health-check` in a browser, and you should see something like `{"code":0,"data":"OK","message":"成功"}` displayed.
121 |
122 | If you have `cURL` or some API client tools (e.g. [Postman](https://www.getpostman.com/)), you may try the following
123 | more complex scenarios:
124 |
125 | ```shell
126 | # create a user via: POST /v1/join
127 | curl -X POST -H "Content-Type: application/json" -d '{"username":"demo","email":"demo@email.com","password":"pass123"}' http://localhost:8000/v1/join
128 | # should return like: {"code":0,"data":{"username":"demo","email":"demo@email.com","status":1,"created_at":"2020-07-18T16:38:11+08:00","updated_at":"2020-07-18T16:38:11+08:00","id":17},"message":"成功"}
129 |
130 | # authenticate the user via: POST /v1/login
131 | curl -X POST -H "Content-Type: application/json" -d '{"username": "demo", "password": "pass123"}' http://localhost:8000/v1/login
132 | # should return like: {"code":0,"data":{"user":{"id":4,"username":"dem211o1","avatar":"","email":"de21mo1@mail.com","status":1,"created_at":"2020-07-17T23:49:39+08:00","updated_at":"2020-07-17T23:49:39+08:00"},"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImp0aSI6IllpaS1SRVNULUFQSSJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3QiLCJqdGkiOiJZaWktUkVTVC1BUEkiLCJpYXQiOjE1OTUwNjQ5NzIsImV4cCI6MTU5NTMyNDE3MiwidXNlcm5hbWUiOiJkZW0yMTFvMSIsImlkIjo0fQ.y2NSVQe-TQ08RnXnF-o55h905G9WHo6GYHNaUWlKjDE"},"message":"成功"}
133 |
134 | # refresh a JWT
135 | curl -X POST -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImp0aSI6IllpaS1SRVNULUFQSSJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3QiLCJqdGkiOiJZaWktUkVTVC1BUEkiLCJpYXQiOjE1OTUwNjQ5NzIsImV4cCI6MTU5NTMyNDE3MiwidXNlcm5hbWUiOiJkZW0yMTFvMSIsImlkIjo0fQ.y2NSVQe-TQ08RnXnF-o55h905G9WHo6GYHNaUWlKjDE' http://localhost:8000/v1/refresh-token
136 | # should return like: {"code":0,"data":{"user":{"id":4,"username":"dem211o1","avatar":"","email":"de21mo1@mail.com","status":1,"created_at":"2020-07-17T23:49:39+08:00","updated_at":"2020-07-17T23:49:39+08:00"},"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImp0aSI6IllpaS1SRVNULUFQSSJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3QiLCJqdGkiOiJZaWktUkVTVC1BUEkiLCJpYXQiOjE1OTUwNjQ5NzIsImV4cCI6MTU5NTMyNDE3MiwidXNlcm5hbWUiOiJkZW0yMTFvMSIsImlkIjo0fQ.y2NSVQe-TQ08RnXnF-o55h905G9WHo6GYHNaUWlKjDE"},"message":"成功"}
137 | ```
138 |
--------------------------------------------------------------------------------