tobento/app-user-web

为应用程序提供用户Web支持,包括登录、注册、密码重置、注销等功能。

1.0.1 2024-09-28 10:28 UTC

This package is auto-updated.

Last update: 2024-09-28 10:29:43 UTC


README

用户Web提供以下认证功能:

  • 登录/注销
  • 双因素登录
  • 记住我
  • 忘记密码
  • 通道验证(电子邮件和智能手机)
  • 简单的个人资料页面,用户可以更新个人资料数据
  • 用户通知页面
  • 多语言支持

目录

入门指南

使用以下命令添加应用程序用户Web项目的最新版本。

composer require tobento/app-user-web

要求

  • PHP 8.0 或更高版本

文档

应用程序

如果您使用的是骨架,请查看 应用程序骨架

您还可以查看 应用程序,了解更多关于应用程序的信息。

用户Web启动

用户Web启动执行以下操作:

  • 安装和加载用户_web 配置
  • 安装视图和翻译文件
use Tobento\App\AppFactory;

// Create the app
$app = (new AppFactory())->createApp();

// Add directories:
$app->dirs()
    ->dir(realpath(__DIR__.'/../'), 'root')
    ->dir(realpath(__DIR__.'/../app/'), 'app')
    ->dir($app->dir('app').'config', 'config', group: 'config')
    ->dir($app->dir('root').'public', 'public')
    ->dir($app->dir('root').'vendor', 'vendor');

// Adding boots
$app->boot(\Tobento\App\User\Web\Boot\UserWeb::class);

// Run the app
$app->run();

用户Web配置

用户Web的配置位于默认应用程序骨架配置位置下的 app/config/user_web.php 文件中。

功能

简单地说,在 app/config/user_web.php 功能部分中配置您想要支持的功能。

主页功能

主页功能提供了一个简单的首页。如果您不使用此功能,您需要在其他功能中调整“主页”路由或添加另一个名为 home 的路由。

配置

配置文件 中,您可以配置主页功能

'features' => [
    new Feature\Home(),
    
    // Or:
    new Feature\Home(
        // The view to render:
        view: 'user/home',

        // A menu name to show the home link or null if none.
        menu: 'main',
        menuLabel: 'Home',
        // A menu parent name (e.g. 'user') or null if none.
        menuParent: null,

        // If true, routes are being localized.
        localizeRoute: false,
    ),
],

登录功能

登录功能提供了一个简单的登录页面,用户可以通过电子邮件、智能手机或用户名和密码登录。

配置

配置文件 中,您可以配置登录功能

use Tobento\App\RateLimiter\Symfony\Registry\SlidingWindow;

'features' => [
    new Feature\Login(),
    
    // Or:
    new Feature\Login(
        // The view to render:
        view: 'user/login',

        // A menu name to show the login link or null if none.
        menu: 'header',
        menuLabel: 'Log in',
        // A menu parent name (e.g. 'user') or null if none.
        menuParent: null,
        
        // Specify a rate limiter:
        rateLimiter: new SlidingWindow(limit: 10, interval: '5 Minutes'),
        // see: https://github.com/tobento-ch/app-rate-limiter#available-rate-limiter-registries

        // Specify the identity attributes to be checked on login.
        identifyBy: ['email', 'username', 'smartphone', 'password'],
        // You may set a user verifier(s), see: https://github.com/tobento-ch/app-user#user-verifier
        /*userVerifier: function() {
            return new \Tobento\App\User\Authenticator\UserRoleVerifier('editor', 'author');
        },*/

        // The period of time from the present after which the auth token MUST be considered expired.
        expiresAfter: 1500, // int|\DateInterval

        // If you want to support remember. If set and the user wants to be remembered,
        // this value replaces the expiresAfter parameter.
        remember: new \DateInterval('P6M'), // null|int|\DateInterval

        // The message and redirect route if a user is authenticated.
        authenticatedMessage: 'You are logged in!',
        authenticatedRedirectRoute: 'home', // or null (no redirection)

        // The message shown if a login attempt fails.
        failedMessage: 'Invalid user or password.',

        // The redirect route after a successful login.
        successRoute: 'home',
            
        // The message shown after a user successfully log in.
        successMessage: 'Welcome back :greeting.', // or null

        // If set, it shows the forgot password link. Make sure the Feature\ForgotPassword is set too.
        forgotPasswordRoute: 'forgot-password.identity', // or null
        
        // The two factor authentication route.
        twoFactorRoute: 'twofactor.code.show',

        // If true, routes are being localized.
        localizeRoute: false,
    ),
],

支持“记住我”功能

您可以通过以下步骤支持“记住我”功能:

1. 启用“记住我”

在配置中,指定 remember 参数的值

'features' => [
    new Feature\Login(
        // If you want to support remember. If set and the user wants to be remembered,
        // this value replaces the expiresAfter parameter.
        remember: new \DateInterval('P6M'), //null|int|\DateInterval
    ),
],

认证令牌将在6个月后过期,除非用户登出!

2. 使用合适的令牌存储

由于“记住我”令牌通常有效期长,请确保使用仓库存储来存储令牌,这是默认配置。

3. 添加RememberedToken中间件(可选)

app/config/user.php中,在User::class中间件之后添加RememberedToken::class中间件,并指定从现在起令牌被视为记住的时间段。

use Tobento\App\User;

'middlewares' => [
    // You may uncomment it and set it on each route individually
    // using the User\Middleware\AuthenticationWith::class!
    User\Middleware\Authentication::class,
    User\Middleware\User::class,

    [User\Web\Middleware\RememberedToken::class, 'isRememberedAfter' => 1500],
    
    // or with date interval:
    [User\Web\Middleware\RememberedToken::class, 'isRememberedAfter' => new \DateInterval('PT2H')],
],

令牌被视为记住后,将创建一个新的令牌,并将参数authenticatedVia设置为remembered。此外,在每次请求中,它将使用中间件中定义的令牌验证器验证令牌,例如检查密码散列。

一旦添加了中间件,您可以使用Authenticated Middleware强制用户在访问某些资源之前重新进行身份验证,如果令牌被视为记住。

use Tobento\App\User\Middleware\Authenticated;

$app->route('GET', 'account-info', function() {
    return 'account';
})->middleware([
    Authenticated::class,
    'exceptVia' => 'remembered',
    'redirectRoute' => 'login',
]);

双因素认证代码功能

双因素认证代码功能提供了使用验证码进行双因素认证的简单方法。

配置

配置文件中,您可以配置此功能

'features' => [
    new Feature\TwoFactorAuthenticationCode(),
    
    // Or:
    new Feature\TwoFactorAuthenticationCode(
        // The view to render:
        view: 'user/twofactor-code',

        // The period of time from the present after which the verification code MUST be considered expired.
        codeExpiresAfter: 300, // int|\DateInterval

        // The seconds after a new code can be reissued.
        canReissueCodeAfter: 60,

        // The message and redirect route if a user is unauthenticated.
        unauthenticatedMessage: 'You have insufficient rights to access the requested resource!',
        unauthenticatedRedirectRoute: 'home', // or null (no redirection)

        // The redirect route after a successful code verification.
        successRoute: 'home',

        // The message shown after a successful code verification.
        successMessage: 'Welcome back :greeting.', // or null

        // If true, routes are being localized.
        localizeRoute: false,
    ),
],

确定何时需要双因素认证

要启用双因素认证,您需要确定何时需要双因素认证,通过扩展Login::class并自定义isTwoFactorRequiredFor方法来实现

use Tobento\App\User\Web\Feature\Login;
use Tobento\App\User\UserInterface;

class CustomLoginFeature extends Login
{
    /**
     * Returns true if the user is required to perform two factor authentication, otherwise false.
     *
     * @param UserInterface $user
     * @return bool
     */
    protected function isTwoFactorRequiredFor(UserInterface $user): bool
    {
        // Your conditions here:
        if (in_array($user->getRoleKey(), ['business'])) {
            return true;
        }
        
        return false;
    }
}

config/user_web.php中,用您的自定义配置替换默认的登录功能

'features' => [
    new CustomLoginFeature(
        //...
    ),
],

一旦设置完成,在成功登录后,具有业务角色的任何用户将被重定向到双因素认证页面,他可以在那里确认发送的代码。

然而,用户不必确认代码,他可以只是离开双因素认证页面,并以正常方式登录。如何处理取决于您。您可以使用用户权限策略中的任何一种,或者您可以创建一个中间件,在用户可以访问其他路由之前,强制他确认代码。

双因素认证的用户权限策略

使用认证中间件

最简单的方法是使用Authenticated::class中间件保护未通过双因素认证的用户路由,并定义via参数为twofactor-code

use Tobento\App\User\Middleware\Authenticated;

$app->route('GET', 'account-info', function() {
    return 'account';
})->middleware([
    Authenticated::class,
    'via' => 'twofactor-code',
    'redirectRoute' => 'home',
]);

您可以查看Authenticated Middleware部分以获取更多详细信息。

使用自定义令牌验证器来更改用户角色

当用户刚刚登录并需要(要求)执行双因素认证时,您可以更改用户角色。

首先,创建一个自定义令牌验证器,并使用authenticatedVia令牌方法检查由登录功能设置的loginform-twofactor值,该值在需要双因素认证时设置。一旦用户确认了代码,authenticatedVia令牌方法的值将被设置为twofactor-code,并且将再次使用用户的原始角色。

use Tobento\App\User\Authentication\Token\TokenInterface;
use Tobento\App\User\Authenticator\TokenAuthenticator;
use Tobento\App\User\Authenticator\TokenVerifierInterface;
use Tobento\App\User\Exception\AuthenticationException;
use Tobento\App\User\UserInterface;
use Tobento\App\User\UserRepositoryInterface;
use Tobento\Service\Acl\AclInterface;

class CustomTokenAuthenticator extends TokenAuthenticator
{
    public function __construct(
        protected AclInterface $acl,
        protected UserRepositoryInterface $userRepository,
        protected null|TokenVerifierInterface $tokenVerifier = null,
    ) {}
    
    /**
     * Authenticate token.
     *
     * @param TokenInterface $token
     * @return UserInterface
     * @throws AuthenticationException If authentication fails.
     */
    public function authenticate(TokenInterface $token): UserInterface
    {
        $user = parent::authenticate($token);
        
        if ($token->authenticatedVia() === 'loginform-twofactor') {
            
            $role = $this->acl->getRole('registered');
            
            if (is_null($role)) {
                throw new AuthenticationException('Registered role not set up');
            }
            
            $user->setRole($role);
            $user->setRoleKey($role->key());
            $user->setPermissions([]); // clear user specific permissions too.
        }
        
        return $user;
    }
}

接下来,在config/user.php文件中实现您创建的自定义令牌验证器

use Tobento\App\User\Authenticator;

'interfaces' => [
    // ...
    
    Authenticator\TokenAuthenticatorInterface::class => CustomTokenAuthenticator::class,
    
    // ...
],

最后,只需使用Verify Permission MiddlewareVerify Role Middleware来保护任何路由,防止未经授权的用户访问。

注销功能

登出功能提供简单的登出功能。

配置

配置文件中,您可以配置登出功能

'features' => [
    new Feature\Logout(),
    
    // Or:
    new Feature\Logout(
        // A menu name to show the logout link or null if none.
        menu: 'header',
        menuLabel: 'Log out',
        // A menu parent name (e.g. 'user') or null if none.
        menuParent: null,

        // The redirect route after a successful logout.
        redirectRoute: 'home',

        // The message and redirect route if a user is unauthenticated.
        unauthenticatedMessage: 'You have insufficient rights to access the requested resource!',
        unauthenticatedRedirectRoute: 'home', // or null (no redirection)

        // If true, routes are being localized.
        localizeRoute: false,
    ),
],

忘记密码功能

忘记密码功能为用户提供了一种简单的方式来重置他们遗忘的密码。

配置

配置文件中,您可以配置忘记密码功能。

'features' => [
    new Feature\ForgotPassword(),
    
    // Or:
    new Feature\ForgotPassword(
        viewIdentity: 'user/forgot-password/identity',
        viewReset: 'user/forgot-password/reset',

        // Specify the identity attributes to be checked on identity.
        identifyBy: ['email', 'username', 'smartphone'],
        // You may set a user verifier(s), see: https://github.com/tobento-ch/app-user#user-verifier
        /*userVerifier: function() {
            return new \Tobento\App\User\Authenticator\UserRoleVerifier('editor', 'author');
        },*/
        
        // The period of time from the present after which the verification token MUST be considered expired.
        tokenExpiresAfter: 300, // int|\DateInterval
        
        // The seconds after a new token can be reissued.
        canReissueTokenAfter: 60,
            
        // The message shown if an identity attempt fails.
        identityFailedMessage: 'Invalid name or user.',

        // The message and redirect route if a user is authenticated.
        authenticatedMessage: 'You are logged in!',
        authenticatedRedirectRoute: 'home', // or null (no redirection)

        // The redirect route after a successful password reset.
        successRedirectRoute: 'home',
        
        // The message shown after a successful password reset.
        successMessage: 'Your password has been reset!', // or null

        // If true, routes are being localized.
        localizeRoute: false,
    ),
],

自定义重置密码通知

您可以通过两种方式自定义密码重置通知:

通过添加自定义通知

请参阅自定义通知

通过自定义功能

扩展Tobento\App\User\Web\Feature\ForgotPassword::class并自定义sendLinkNotification方法。在这个方法中,您可以使用您自己创建的任何通知类来发送通知。

use Tobento\App\User\Web\Feature\ForgotPassword;
use Tobento\App\User\Web\TokenInterface;
use Tobento\App\User\Web\Notification;
use Tobento\App\User\UserInterface;
use Tobento\Service\Notifier\NotifierInterface;
use Tobento\Service\Notifier\UserRecipient;
use Tobento\Service\Routing\RouterInterface;

class CustomForgotPasswordFeature extends ForgotPassword
{
    protected function sendLinkNotification(
        TokenInterface $token,
        UserInterface $user,
        NotifierInterface $notifier,
        RouterInterface $router,
    ): void {
        $notification = new Notification\ResetPassword(
            token: $token,
            url: (string)$router->url('forgot-password.reset', ['token' => $token->id()]),
        );
        
        // The receiver of the notification:
        $recipient = new UserRecipient(user: $user);

        // Send the notification to the recipient:
        $notifier->send($notification, $recipient);
    }
}

最后,在配置中用您自定义的替换默认的忘记密码功能。

'features' => [
    new CustomForgotPasswordFeature(),
],

注册功能

注册功能为用户提供了一种简单的方式来注册。

配置

配置文件中,您可以配置注册功能。

'features' => [
    new Feature\Register(),
    
    // Or:
    new Feature\Register(
        // The view to render:
        view: 'user/register',

        // A menu name to show the register link or null if none.
        menu: 'header',
        menuLabel: 'Register',
        // A menu parent name (e.g. 'user') or null if none.
        menuParent: null,

        // The message and redirect route if a user is authenticated.
        authenticatedMessage: 'You are logged in!',
        authenticatedRedirectRoute: 'home',

        // The default role key for the registered user.
        roleKey: 'registered',

        // The redirect route after a successful registration.
        successRedirectRoute: 'login',
        // You may redirect to the verification account page
        // see: https://github.com/tobento-ch/app-user-web#account-verification

        // If true, user has the option to subscribe to the newsletter.
        newsletter: false,

        // If a terms route is specified, users need to agree terms and conditions.
        termsRoute: null,
        /*termsRoute: static function (RouterInterface $router): string {
            return (string)$router->url('blog.show', ['key' => 'terms']);
        },*/

        // If true, routes are being localized.
        localizeRoute: false,
    ),
],

自定义注册字段

您可以通过以下步骤自定义注册字段:

1. 自定义视图

views/user/目录中创建一个新文件custom-register.php,在此文件中编写您自定义的视图代码。

1.B 使用主题自定义视图(推荐方式)

在您的主题中创建一个新文件my-view-theme/user/register.php,在此文件中编写您自定义的视图代码。

查看应用视图 - 主题部分以了解更多信息。

2. 自定义验证规则

通过扩展Register::class并自定义validationRules方法来自定义与自定义视图(步骤1)对应的注册规则。

use Tobento\App\User\Web\Feature\Register;

class CustomRegisterFeature extends Register
{
    protected function validationRules(): array
    {
        return [
            'user_type' => 'string',
            'address.fistname' => 'required|string',
            'address.lastname' => 'required|string',
            // ...
        ];
    }
}

最后,在配置中用您自定义的替换默认的注册功能。

'features' => [
    new CustomRegisterFeature(
        // specify your custom view if (step 1.A):
        view: 'user/custom-register',
        
        // No need to change the default view for (step 1.B)
        view: 'user/register',
        //...
    ),
],

自定义注册用户的角色

您可以通过扩展Register::class并自定义determineRoleKey方法来自定义注册用户的角色。

use Tobento\App\User\Web\Feature\Register;
use Tobento\Service\Acl\AclInterface;
use Tobento\Service\Validation\ValidationInterface;

class CustomRegisterFeature extends Register
{
    protected function determineRoleKey(AclInterface $acl, ValidationInterface $validation): string
    {
        return match ($validation->valid()->get('user_type', '')) {
            'business' => $acl->hasRole('business') ? 'business' : $this->roleKey,
            default => $this->roleKey,
        };
    }
}

在配置中用您自定义的替换默认的注册功能。

'features' => [
    new CustomRegisterFeature(
        //...
    ),
],

请确保您已经添加了角色,否则将使用guest角色键作为后备。

注册后自动登录

默认情况下,在成功注册后,用户不会自动登录。

如果您希望他们自动登录,请将AutoLoginAfterRegistration::class监听器添加到config/event.php文件中。

use Tobento\App\User\Web\Listener\AutoLoginAfterRegistration;

'listeners' => [
    \Tobento\App\User\Web\Event\Registered::class => [
        AutoLoginAfterRegistration::class,
        
        // Or you may set the expires after:
        [AutoLoginAfterRegistration::class, ['expiresAfter' => 1500]], // in seconds
        [AutoLoginAfterRegistration::class, ['expiresAfter' => new \DateInterval('PT1H')]],
    ],
],

config/user_web.php文件中,您可以将用户重定向到个人资料编辑页面或您希望的其他页面。

'features' => [
    new Feature\Register(
        // redirect users to the profile page
        // after successful registration:
        successRedirectRoute: 'profile.edit',
    ),
],

账户验证

在用户成功注册后,您可能需要要求他们在使用应用程序或个人路由之前至少验证一个渠道,例如他们的电子邮件地址。您可以通过以下步骤实现这一点:

1. 在成功注册后自动登录用户

config/event.php文件中添加AutoLoginAfterRegistration::class监听器

'listeners' => [
    \Tobento\App\User\Web\Event\Registered::class => [
        \Tobento\App\User\Web\Listener\AutoLoginAfterRegistration::class,
    ],
],

因为只有已认证的用户才能验证其账户!

2. 在成功注册后将用户重定向到验证页面

config/user_web.php文件中

'features' => [
    new Feature\Register(
        // redirect users to the verification account page
        // after successful registration:
        successRedirectRoute: 'verification.account',
    ),
    
    // make sure the verification feature is set:
    Feature\Verification::class,
],

3. 保护路由免受未验证用户的侵害

使用验证中间件来保护任何路由免受未验证用户的侵害。

4. 保护个人资料功能免受未验证用户的侵害

扩展Profile::class并自定义configureMiddlewares方法

use Tobento\App\User\Web\Feature\Profile;
use Tobento\App\AppInterface;
use Tobento\App\User\Middleware\Authenticated;
use Tobento\App\User\Middleware\Verified;

class CustomProfileFeature extends Profile
{
    protected function configureMiddlewares(AppInterface $app): array
    {
        return [
            // The Authenticated::class middleware protects routes from unauthenticated users:
            [
                Authenticated::class,

                // you may specify a custom message to show to the user:
                'message' => $this->unauthenticatedMessage,

                // you may specify a message level:
                //'messageLevel' => 'notice',

                // you may specify a route name for redirection:
                'redirectRoute' => $this->unauthenticatedRedirectRoute,
            ],
            // The Verified::class middleware protects routes from unverified users:
            [
                Verified::class,

                // you may specify a custom message to show to the user:
                'message' => 'You have insufficient rights to access the requested resource!',

                // you may specify a message level:
                'messageLevel' => 'notice',

                // you may specify a route name for redirection:
                'redirectRoute' => 'verification.account',
            ],
        ];
    }
}

在配置中用您自定义的替换默认的个人资料功能。

'features' => [
    new CustomProfileFeature(
        //...
    ),
],

5. 保护个人资料设置功能免受未验证用户的侵害

与步骤4相同,只是使用Tobento\App\User\Web\Feature\ProfileSettings::class

仅针对特定用户角色的账户验证

您可以为所有用户执行账户验证,也可以只为特定用户角色执行。您可以通过以下步骤实现这一点:

1. 自定义注册功能

扩展Register::class并自定义configureSuccessRedirectRoute方法

use Tobento\App\User\Web\Feature\Register;
use Tobento\App\User\UserInterface;

class CustomRegisterFeature extends Register
{
    protected function configureSuccessRedirectRoute(UserInterface $user): string
    {
        if (in_array($user->getRoleKey(), ['business'])) {
            return 'verification.account';
        }
        
        return 'login';
    }
}

2. (可选)自定义注册用户的角色

查看为注册用户自定义角色部分。

3. 注册后自动登录用户

您需要自动登录需要验证其账户的用户,因为只有已认证的用户才能验证其账户

config/event.php文件中添加CustomAutoLoginAfterRegistration::class监听器

use Tobento\App\User\Web\Listener\AutoLoginAfterRegistration;

'listeners' => [
    \Tobento\App\User\Web\Event\Registered::class => [
        [AutoLoginAfterRegistration::class, ['userRoles' => ['business']]],
    ],
],

4. 保护个人资料和设置功能免受未验证用户以及其他路由的侵害

参见账户验证步骤3、4和5。

服务条款和条件协议

在用户注册之前,您可以让他们同意您的条款和条件

config/user_web.php文件中

'features' => [
    new Feature\Register(
        // If a terms route is specified, users need to agree terms and conditions.
        termsRoute: 'your.terms.route.name',
        
        // Or you may use router directly:
        termsRoute: static function (RouterInterface $router): string {
            return (string)$router->url('blog.show', ['key' => 'terms']);
        },
    ),
],

确保您已在应用程序中的某个位置注册了条款路由。

注册时的垃圾邮件保护

默认情况下,注册表单使用App Spam包来防止垃圾邮件。它使用默认的垃圾邮件检测器,因为定义的名为register的检测器不存在。为了使用自定义检测器,您只需在app/config/spam.php文件中定义它即可

use Tobento\App\Spam\Factory;

'detectors' => [
    'register' => new Factory\Composite(
        new Factory\Honeypot(inputName: 'hp'),
        new Factory\MinTimePassed(inputName: 'mtp', milliseconds: 1000),
    ),
]

通知功能

通知功能为用户提供了一种简单的方式来查看他们的通知。

配置

配置文件中,您可以配置通知功能

'features' => [
    new Feature\Notifications(),
    
    // Or:
    new Feature\Notifications(
        // The view to render:
        view: 'user/notifications',

        // The notifier storage channel used to retrieve notifications.
        notifierStorageChannel: 'storage',

        // A menu name to show the notifications link or null if none.
        menu: 'main',
        menuLabel: 'Notifications',
        // A menu parent name (e.g. 'user') or null if none.
        menuParent: null,

        // The message and redirect route if a user is unauthenticated.            
        unauthenticatedMessage: 'You have insufficient rights to access the requested resource!',
        unauthenticatedRedirectRoute: 'login', // or null (no redirection)

        // If true, routes are being localized.
        localizeRoute: false,
    ),
],

创建和发送通知

要将通知发送到配置的存储通道以在通知页面上显示,您需要将其发送到存储通道

use Tobento\Service\Notifier\NotifierInterface;
use Tobento\Service\Notifier\Notification;
use Tobento\Service\Notifier\Recipient;

class SomeService
{
    public function send(NotifierInterface $notifier): void
    {
        // Create a Notification that has to be sent:
        // using the "email" and "sms" channel
        $notification = new Notification(
            subject: 'New Invoice',
            content: 'You got a new invoice for 15 EUR.',
            channels: ['mail', 'sms', 'storage'],
        );
        
        // with specific storage message. Will be displayed on the notifications page:
        $notification->addMessage('storage', new Message\Storage([
            'message' => 'You received a new order.',
            'action_text' => 'View Order',
            'action_route' => 'orders.view',
            'action_route_parameters' => ['id' => 55],
        ]));

        // The receiver of the notification:
        $recipient = new Recipient(
            email: 'mail@example.com',
            phone: '15556666666',
            id: 'unique-user-id',
        );

        // Send the notification to the recipient:
        $notifier->send($notification, $recipient);
    }
}

格式化通知

查看存储通知格式化部分以了解有关格式化显示通知的更多信息。

自定义未读通知计数

您可以通过扩展Notifications::class并自定义getUnreadNotificationsCount方法来自定义菜单徽章的未读通知计数逻辑。

示例缓存计数

安装App Cache包以支持缓存。

use Tobento\App\AppInterface;
use Tobento\App\User\Web\Feature\Notifications;
use Tobento\Service\Notifier\ChannelsInterface;
use Tobento\Service\Notifier\Storage;
use Psr\SimpleCache\CacheInterface;

class CustomNotificationsFeature extends Notifications
{
    /**
     * Returns the user's unread notifications count for the menu badge.
     *
     * @param ChannelsInterface $channels
     * @param UserInterface $user
     * @param AppInterface $app
     * @return int
     */
    protected function getUnreadNotificationsCount(
        ChannelsInterface $channels,
        UserInterface $user,
        AppInterface $app,
    ): int {
        $channel = $channels->get(name: $this->notifierStorageChannel);
        
        if (!$channel instanceof Storage\Channel) {
            return 0;
        }
        
        $key = sprintf('unread_notifications_count:%s', (string)$user->id());
        
        $cache = $app->get(CacheInterface::class);
        
        if ($cache->has($key)) {
            return $cache->get($key);
        }
        
        $count = $channel->repository()->count(where: [
            'recipient_id' => $user->id(),
            'read_at' => ['null'],
        ]);
        
        $cache->set($key, $count, 60);
        
        return $count;
    }
}

在配置中,将默认的通知功能替换为您的自定义

'features' => [
    new CustomNotificationsFeature(
        //...
    ),
],

清除已读通知

如果您已安装App Console,则可以通过运行以下命令轻松删除已读通知

php ap notifications:clear --read-only --channel=storage --older-than-days=10

您可以通过查看App Notifier - 清除通知命令部分来获取有关notifications:clear命令的更多信息。

如果您想自动化此过程,请考虑安装App Schedule包并使用命令任务

use Tobento\Service\Schedule\Task;
use Butschster\CronExpression\Generator;

$schedule->task(
    (new Task\CommandTask(
        command: 'notifications:clear --read-only',
    ))
    // schedule task:
    ->cron(Generator::create()->weekly())
);

个人资料功能

个人资料功能为用户提供了一种简单的方式来更新他们的个人资料数据、删除他们的账户并验证他们的渠道。

配置

配置文件中,您可以配置个人资料功能

'features' => [
    new Feature\Profile(),
    
    // Or:
    new Feature\Profile(
        // The view to render:
        view: 'user/profile/edit',

        // A menu name to show the profile link or null if none.
        menu: 'main',
        menuLabel: 'Profile',
        // A menu parent name (e.g. 'user') or null if none.
        menuParent: null,

        // The message and redirect route if a user is unauthenticated.            
        unauthenticatedMessage: 'You have insufficient rights to access the requested resource!',
        unauthenticatedRedirectRoute: 'login', // or null (no redirection)

        // If true, it displays the channel verification section to verify channels.
        // Make sure the Verification Feature is enabled.
        channelVerifications: true,

        // The redirect route after a successfully account deletion.
        successDeleteRedirectRoute: 'home',

        // If true, routes are being localized.
        localizeRoute: false,
    ),
],

自定义信息字段

您可以通过以下步骤自定义信息字段

1. 自定义视图

views/user/目录中创建一个新的文件profile/custom-edit.php,在其中编写您自定义的视图代码。

1.B 使用主题自定义视图(推荐方式)

在您的主题中创建一个新的文件my-view-theme/user/profile/edit.php,在其中编写您自定义的视图代码。

查看应用视图 - 主题部分以了解更多信息。

2. 自定义验证规则

通过扩展Profile::class并自定义validationUpdateRules方法来自定义与自定义视图(步骤1)对应的设置规则

use Tobento\App\User\Web\Feature\Profile;
use Tobento\App\User\UserInterface;

class CustomProfileFeature extends Profile
{
    /**
     * Returns the validation rules for updating the user's profile information.
     *
     * @param UserInterface $user
     * @return array
     */
    protected function validationUpdateRules(UserInterface $user): array
    {
        // your rules:
        $rules = [
            'user_type' => 'string',
            'address.fistname' => 'required|string',
            'address.lastname' => 'required|string',
        ];
        
        // your may merge the default rules.
        return array_merge($rules, parent::validationUpdateRules($user));
    }
}

最后,在配置中,将默认的个人资料设置功能替换为您的自定义

'features' => [
    new CustomProfileFeature(
        // specify your custom view if (step 1.A):
        view: 'user/profile/custom-edit',
        
        // No need to change the default view for (step 1.B)
        view: 'user/profile/edit',
        //...
    ),
],

自定义显示的可验证通道

您可以通过扩展Profile::class并自定义configureAvailableChannels方法来自定义可用的验证渠道

use Tobento\App\User\Web\Feature\Profile;
use Tobento\App\User\UserInterface;
use Tobento\App\Notifier\AvailableChannelsInterface;

class CustomProfileFeature extends Profile
{
    /**
     * Configure the available verification channels to display.
     *
     * @param AvailableChannelsInterface $channels
     * @param UserInterface $user
     * @return AvailableChannelsInterface
     */
    protected function configureAvailableChannels(
        AvailableChannelsInterface $channels,
        UserInterface $user,
    ): AvailableChannelsInterface {
        if (! $this->channelVerifications) {
            // do not display any at all:
            return $channels->only([]);
        }
        
        return $channels->only(['mail', 'sms']);
    }
}

在配置中用您自定义的替换默认的个人资料功能。

'features' => [
    new CustomProfileFeature(
        //...
    ),
],

如果您允许除mailsms之外的其他渠道,您需要自定义验证功能

个人资料设置功能

个人资料设置功能为用户提供了一种简单的方式来更新他们的个人资料设置,例如他们首选的区域设置和通知渠道。

配置

配置文件中,您可以配置个人资料功能

'features' => [
    new Feature\ProfileSettings(),
    
    // Or:
    new Feature\ProfileSettings(
        // The view to render:
        view: 'user/profile/settings',

        // A menu name to show the profile settings link or null if none.
        menu: 'main',
        menuLabel: 'Profile Settings',
        // A menu parent name (e.g. 'user') or null if none.
        menuParent: null,

        // The message and redirect route if a user is unauthenticated.            
        unauthenticatedMessage: 'You have insufficient rights to access the requested resource!',
        unauthenticatedRedirectRoute: 'login', // or null (no redirection)

        // If true, routes are being localized.
        localizeRoute: false,
    ),
],

自定义设置字段

您可以通过以下步骤自定义设置字段:

1. 自定义视图

views/user/ 目录中创建一个新文件 profile/custom-settings.php,在其中编写您自定义的视图代码。

1.B 使用主题自定义视图(推荐方式)

在您的主题中创建一个新文件 my-view-theme/user/profile/settings.php,在其中编写您自定义的视图代码。

查看应用视图 - 主题部分以了解更多信息。

2. 自定义验证规则

通过扩展 ProfileSettings::class 并自定义 validationRules 方法来定制对应于自定义视图(步骤1)的设置规则。

use Tobento\App\User\Web\Feature\ProfileSettings;
use Tobento\App\User\UserInterface;

class CustomProfileSettingsFeature extends ProfileSettings
{
    /**
     * Returns the validation rules for updating the user's profile settings.
     *
     * @param UserInterface $user
     * @return array
     */
    protected function validationRules(UserInterface $user): array
    {
        // your rules:
        $rules = [
            'settings.something' => 'required|string',
        ];
        
        // your may merge the default rules.
        return array_merge($rules, parent::validationRules($user));
    }
}

最后,在配置中,将默认的个人资料设置功能替换为您的自定义

'features' => [
    new CustomProfileSettingsFeature(
        // specify your custom view if (step 1.A):
        view: 'user/profile/custom-settings',
        
        // No need to change the default view for (step 1.B)
        view: 'user/profile/settings',
        //...
    ),
],

自定义可用的通知渠道

您可以通过扩展 ProfileSettings::class 并自定义 configureAvailableChannels 方法来自定义可用的通知通道。

use Tobento\App\User\Web\Feature\ProfileSettings;
use Tobento\App\User\UserInterface;
use Tobento\App\Notifier\AvailableChannelsInterface;

class CustomProfileSettingsFeature extends ProfileSettings
{
    /**
     * Configure the available channels.
     *
     * @param AvailableChannelsInterface $channels
     * @param UserInterface $user
     * @return AvailableChannelsInterface
     */
    protected function configureAvailableChannels(
        AvailableChannelsInterface $channels,
        UserInterface $user,
    ): AvailableChannelsInterface {
        return $channels
            ->only(['mail', 'sms', 'storage'])
            ->withTitle('storage', 'Account')
            ->sortByTitle();
        
        // Or you may return no channels at all:
        return $channels->only([]);
    }
}

在配置文件中,用您的自定义设置替换默认的配置文件。

'features' => [
    new CustomProfileSettingsFeature(
        //...
    ),
],

验证功能

验证功能为用户提供了一种简单的方法来验证他们的电子邮件和智能手机。

配置

配置文件 中,您可以配置验证功能。

'features' => [
    new Feature\Verification(),
    
    // Or:
    new Feature\Verification(
        // The view to render:
        viewAccount: 'user/verification/account',
        viewChannel: 'user/verification/channel',

        // The period of time from the present after which the verification code MUST be considered expired.
        codeExpiresAfter: 300, // int|\DateInterval

        // The seconds after a new code can be reissued.
        canReissueCodeAfter: 60,
            
        // The message and redirect route if a user is unauthenticated.            
        unauthenticatedMessage: 'You have insufficient rights to access the requested resource!',
        unauthenticatedRedirectRoute: 'login', // or null (no redirection)

        // The redirect route after a verified channel.
        verifiedRedirectRoute: 'home',

        // If true, routes are being localized.
        localizeRoute: false,
    ),
],

保护未验证用户的路由

使用验证中间件来保护任何路由免受未验证用户的侵害。

自定义可验证的通道

您可以通过扩展 Verification::class 并自定义 configureAvailableChannelsgetNotifierChannelForcanVerifyChannel 方法来自定义可验证的通道。

不要忘记在 app/config/notifier.php 配置文件中配置通知器通道!

use Tobento\App\User\Web\Feature\Verification;
use Tobento\App\User\UserInterface;
use Tobento\App\Notifier\AvailableChannelsInterface;

class CustomVerificationFeature extends Verification
{
    /**
     * Configure the available channels.
     *
     * @param AvailableChannelsInterface $channels
     * @param UserInterface $user
     * @return AvailableChannelsInterface
     */
    protected function configureAvailableChannels(
        AvailableChannelsInterface $channels,
        UserInterface $user,
    ): AvailableChannelsInterface {
        //return $channels->only(['mail', 'sms']); // default
        
        return $channels->only(['mail', 'sms', 'chat/slack']);
    }
    
    /**
     * Returns the notifier channel for the given verification channel.
     *
     * @param string $channel
     * @return null|string
     */
    protected function getNotifierChannelFor(string $channel): null|string
    {
        return match ($channel) {
            'email' => 'mail',
            'smartphone' => 'sms',
            'slack' => 'chat/slack',
            default => null,
        };
    }
    
    /**
     * Determine if the channel can be verified.
     *
     * @param string $channel
     * @param AvailableChannelsInterface $channels
     * @param null|UserInterface $user
     * @return bool
     */
    protected function canVerifyChannel(string $channel, AvailableChannelsInterface $channels, null|UserInterface $user): bool
    {
        if (is_null($user) || !$user->isAuthenticated()) {
            return false;
        }
        
        if (! $channels->has((string)$this->getNotifierChannelFor($channel))) {
            return false;
        }
        
        return match ($channel) {
            'email' => !empty($user->email()) && ! $user->isVerified([$channel]),
            'smartphone' => !empty($user->smartphone()) && ! $user->isVerified([$channel]),
            'slack' => !empty($user->setting('slack')) && ! $user->isVerified([$channel]),
            default => false,
        };
    }
}

在配置中用您自定义的替换默认的个人资料功能。

'features' => [
    new CustomVerificationFeature(
        //...
    ),
],

删除过期的令牌

验证器令牌

以下功能使用令牌或PIN码验证器创建令牌,即使已过期,这些令牌也仍然存在于您的令牌存储库中。

如果您已安装了 App Console,您可以通过运行以下命令轻松删除这些记录:

php ap user-web:clear-tokens

如果您想自动化此过程,请考虑安装App Schedule包并使用命令任务

use Tobento\Service\Schedule\Task;
use Butschster\CronExpression\Generator;

$schedule->task(
    (new Task\CommandTask(
        command: 'user-web:clear-tokens',
    ))
    // schedule task:
    ->cron(Generator::create()->weekly())
);

身份验证令牌

php ap auth:purge-tokens

或使用命令计划任务来自动化此过程

use Tobento\Service\Schedule\Task;
use Butschster\CronExpression\Generator;

$schedule->task(
    (new Task\CommandTask(
        command: 'auth:purge-tokens',
    ))
    // schedule task:
    ->cron(Generator::create()->weekly())
);

有关更多详细信息,请访问 用户 - 控制台

查看

ACL 视图宏

在您的视图文件中,您可以使用 acl 宏来检查权限,例如。

use Tobento\Service\Acl\AclInterface;

var_dump($view->acl() instanceof AclInterface);
// bool(true)

if ($view->acl()->can('comments.write')) {
    <div>Only users with the permission "comments.write" can see this!</div>
}

事件

可用事件

use Tobento\App\User\Web\Event;

了解更多

使用智能手机登录

默认情况下,启用使用智能手机登录。确保您已配置 app/config/notifier.php 文件中的 短信 通道,以便发送短信以验证其账户,例如。

列出可用的路由

使用 路由列表命令 获取可用路由的概述。

新闻通讯订阅

您可以使用提供的 事件 订阅/取消订阅注册用户到新闻提供商。

监听器示例

use Tobento\App\User\Web\Event;

class UserNewsletterSubscriber
{
    public function subscribe(Event\Registered $event): void
    {
        if ($event->user()->newsletter()) {
            // subscribe...
        }
    }

    public function resubscribe(Event\UpdatedProfile $event): void
    {
        if ($event->user()->email() !== $event->oldUser()->email()) {
            // unsubscribe user with the old email address...
            // subscribe user with the new email address...
        }
    }
    
    public function subscribeOrUnsubscribe(Event\UpdatedProfileSettings $event): void
    {
        if ($event->user()->newsletter()) {
            // subscribe...
        } else {
            // unsubscribe...
        }
    }
    
    public function unsubscribe(Event\DeletedAccount $event): void
    {
        // just unsubscribe...
    }
}

config/event.php 文件中添加监听器。

'listeners' => [
    // Specify listeners without event:
    'auto' => [
        UserNewsletterSubscriber::class,
    ],
],

有关更多详细信息,请查看 App Event - 添加监听器 部分。

自定义验证码通知

您可以通过两种方式自定义验证码通知:

通过添加自定义通知

请参阅自定义通知

通过自定义PIN码验证器

扩展 Tobento\App\User\Web\PinCodeVerificator::class 并自定义 createNotification 方法。在此方法中,您可以使用您自己创建的任何通知类发送通知。

use Tobento\App\User\Web\Notification;
use Tobento\App\User\Web\PinCodeVerificator;
use Tobento\App\User\Web\TokenInterface;
use Tobento\App\User\UserInterface;
use Tobento\Service\Notifier\NotifierInterface;
use Tobento\Service\Routing\RouterInterface;

class CustomPinCodeVerificator extends PinCodeVerificator
{
    protected function createNotification(
        TokenInterface $token,
        UserInterface $user,
        array $channels,
    ): NotificationInterface {
        return (new Notification\VerificationCode(token: $token))->channels($channels);
    }
}

最后,在 配置文件 中用您的自定义实现替换默认实现。

use Tobento\App\User\Web;

'interfaces' => [
    Web\PinCodeVerificatorInterface::class => CustomPinCodeVerificator::class,
],

本地化

如果您启用功能路由本地化,您可以在 app/config/language.php 中定义您支持的语言。

配置文件

'features' => [
    new Feature\Home(
        localizeRoute: true,
    ),
    new Feature\Register(
        localizeRoute: true,
    ),
],

查看 App Language 了解有关语言的更多信息。

翻译

默认情况下,提供 ende 翻译。如果您想支持更多地区,请查看 App Translation 了解更多信息。

致谢