NoteBook - 基于 Hyperf 的记事本项目

技术选型

note-book

后端选型

  1. overtrue/wechat 微信SDK
  2. hyperf/validation 验证器
  3. firebase/php-jwt JWT组件

前端选型

本打算使用 wepy 来做小程序,但发现写法又忘干净了。。所以选择 uniapp 来做。

登录模块

Uniapp 支持打包成各种小程序,但我们暂时只支持 微信小程序,所以代码设计中,暂不考虑其他情况。

创建用户表

CREATE TABLE `users` (
  `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
  `nickname` varchar(256) NOT NULL DEFAULT '' COMMENT '昵称',
  `avatar` varchar(256) NOT NULL DEFAULT '' COMMENT '头像',
  `gender` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '1男',
  `openid` varchar(64) NOT NULL DEFAULT '' COMMENT 'OPENID',
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UNIQUE_OPENID` (`openid`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

创建模型

<?php

declare (strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://doc.hyperf.io
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
 */
namespace App\Model;

/**
 * @property int $id
 * @property string $nickname
 * @property string $avatar
 * @property int $gender
 * @property string $openid
 * @property \Carbon\Carbon $created_at
 * @property \Carbon\Carbon $updated_at
 */
class User extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'users';
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['id', 'nickname', 'avatar', 'gender', 'openid', 'created_at', 'updated_at'];
    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = ['id' => 'integer', 'gender' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime'];
}

登录流程

用户注册

增加对应路由

config.php

Router::post('/regist', 'App\Controller\UserController@regist');

实现微信登录

因为 EasyWeChat 的设计还是以 FPM 为基础的,所以我们不能直接将 $app 存在内存中。

<?php

declare(strict_types=1);

namespace App\Kernel\Oauth;

use EasyWeChat\Factory;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Guzzle\CoroutineHandler;
use Hyperf\Guzzle\HandlerStackFactory;
use Overtrue\Socialite\Providers\AbstractProvider;
use Psr\Container\ContainerInterface;

class WeChatFactory
{
    /**
     * @var ContainerInterface
     */
    protected $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
        $this->config = $container->get(ConfigInterface::class)->get('oauth.wechat');

        // 设置 OAuth 授权的 Guzzle 配置
        AbstractProvider::setGuzzleOptions([
            'http_errors' => false,
            'handler' => HandlerStack::create(new CoroutineHandler()),
        ]);
    }

    /**
     * @return \EasyWeChat\MiniProgram\Application
     */
    public function create()
    {
        $app = Factory::miniProgram($this->config);

        // 设置 HttpClient,当前设置没有实际效果,在数据请求时会被 guzzle_handler 覆盖,但不保证 EasyWeChat 后面会修改这里。
        $config = $app['config']->get('http', []);
        $config['handler'] = $this->container->get(HandlerStackFactory::class)->create();
        $app->rebind('http_client', new Client($config));

        // 重写 Handler
        $app['guzzle_handler'] = $this->container->get(HandlerStackFactory::class)->create();

        return $app;
    }
}

完善注册

前端登录后把获得的 code,encrypted_dataiv 传上来,后端进行解密,并保存用户。

<?php

declare(strict_types=1);

namespace App\Controller;

use App\Request\LoginRequest;
use App\Request\RegistRequest;
use App\Service\Formatter\UserFormatter;
use App\Service\UserService;
use Hyperf\Di\Annotation\Inject;

class UserController extends Controller
{
    public function regist(RegistRequest $request)
    {
        $code = (string) $request->input('code');
        $encryptedData = (string) $request->input('encrypted_data');
        $iv = (string) $request->input('iv');

        [$token, $user] = $this->service->regist($code, $encryptedData, $iv);

        return $this->response->success([
            'token' => $token,
            'user' => UserFormatter::instance()->base($user),
        ]);
    }
}

以下 UserDao的使用,只是一个数据库操作的封装,这里不做介绍。

<?php

declare(strict_types=1);

namespace App\Service;

use App\Constants\ErrorCode;
use App\Exception\BusinessException;
use App\Kernel\Oauth\WeChatFactory;
use App\Service\Dao\UserDao;
use App\Service\Instance\JwtInstance;
use App\Service\Redis\UserCollection;
use Hyperf\Di\Annotation\Inject;

class UserService extends Service
{
    /**
     * @Inject
     * @var WeChatFactory
     */
    protected $factory;

    /**
     * @Inject
     * @var UserDao
     */
    protected $dao;

    public function regist($code, $encrypted_data, $iv)
    {
        $app = $this->factory->create();

        $session = $app->auth->session($code);

        $userInfo = $app->encryptor->decryptData($session['session_key'], $iv, $encrypted_data);

        $user = $this->dao->create($userInfo);

        $token = JwtInstance::instance()->encode($user);

        return [$token, $user];
    }
}

另外这里 Token 的存取直接使用 Jwt 来做,但真正开发时,就算使用 Jwt 来做授权,也要在后端验证 Token 是否存在等逻辑,比如用户 Jwt Token 被窃取,登出系统后,也可使 Token 作废,及时止损。

<?php

declare(strict_types=1);

namespace App\Service\Instance;

use App\Constants\ErrorCode;
use App\Exception\BusinessException;
use App\Model\User;
use App\Service\Dao\UserDao;
use Firebase\JWT\JWT;
use Hyperf\Utils\Traits\StaticInstance;

class JwtInstance
{
    use StaticInstance;

    const KEY = 'NoteBook';

    /**
     * @var int
     */
    public $id;

    /**
     * @var User
     */
    public $user;

    public function encode(User $user)
    {
        $this->id = $user->id;
        $this->user = $user;

        return JWT::encode(['id' => $user->id], self::KEY);
    }

    public function decode(string $token): self
    {
        try {
            $decoded = (array) JWT::decode($token, self::KEY, ['HS256']);
        } catch (\Throwable $exception) {
            return $this;
        }

        if ($id = $decoded['id'] ?? null) {
            $this->id = $id;
            $this->user = di()->get(UserDao::class)->first($id);
        }

        return $this;
    }

    public function build(): self
    {
        if (empty($this->id)) {
            throw new BusinessException(ErrorCode::TOKEN_INVALID);
        }

        return $this;
    }

    /**
     * @return int
     */
    public function getId(): ?int
    {
        return $this->id;
    }

    /**
     * @return User
     */
    public function getUser(): ?User
    {
        if ($this->user === null && $this->id) {
            $this->user = di()->get(UserDao::class)->first($this->id);
        }
        return $this->user;
    }
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
Any fool can write code that a computer can understand. Good programmers write code that humans can understand.
本帖由系统于 4年前 自动加精
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!