hyperf 的 hyperf-passport 组件,支持对多种用户进行登录授权支持Oauth2.0的四种授权模式,目前密码授权模式已完全可用。下载地址:https://github.com/richard8768/hyperf-passport
本组件参考了 laravel 的 passport 组件设计,使用体验大体和 laravel 的 passport 差不多。

> 任何问题请加QQ提问:444626008

安装前的准备 - before install

PHP>=7.4 安装依赖包

#授权依赖包
$ composer require 96qbhy/hyperf-auth
$ php bin/hyperf.php vendor:publish 96qbhy/hyperf-auth
#加密依赖包
$ composer require hyperf-ext/encryption
$ php bin/hyperf.php vendor:publish hyperf-ext/encryption
$ composer require hyperf-ext/hashing
$ php bin/hyperf.php vendor:publish hyperf-ext/hashing
#模板引擎和视图
$ composer require hyperf/view
$ php bin/hyperf.php vendor:publish hyperf/view
$ composer require league/plates
#hyperf的session
$ composer require hyperf/session
$ php bin/hyperf.php vendor:publish hyperf/session

使用 php bin/hyperf.php gen:key 命令来生成密钥,并将KEY值复制到文件 config/autoload/encryption.php中的env('AES_KEY', 'place_to_hold_key')

编辑文件config/autoload/view.php配置视图默认引擎:

<?php
declare(strict_types=1);use Hyperf\View\Engine\PlatesEngine;
use Hyperf\View\Mode;return [// 使用的渲染引擎'engine' => PlatesEngine::class,// 不填写则默认为 Task 模式,推荐使用 Task 模式'mode' => Mode::TASK,'config' => [// 若下列文件夹不存在请自行创建'view_path' => BASE_PATH . '/storage/view/','cache_path' => BASE_PATH . '/runtime/view/',],
];
?>

在文件 config/autoload/middlewares.php中添加全局中间件

<?php
return [// 这里的 http 对应默认的 server name,如您需要在其它 server 上使用 Session,需要对应的配置全局中间件'http' => [\Hyperf\Session\Middleware\SessionMiddleware::class,],
];
?>

安装 - install

$ composer require richard8768/hyperf-passport
php bin/hyperf.php vendor:publish richard8768/hyperf-passport

配置 - configuration

编辑文件 config/autoload/passport.php

在文件中引入填写自己的session用户登录URL

'session_user_login_uri' => '/your/user-login/path',

以下为passport.php文件样板

<?phpdeclare(strict_types=1);
return ['session_user_login_uri' => '/user_login','key_store_path' => 'storage','client_uuids' => false,'key' => 'E3Wxizr8gUXuBuyG7CecmGX9E9lbRzdFmqQpG2yP85eDuXzqOj','token_days' => null,'refresh_token_days' => null,'person_token_days' => null,'database_connection' => env('DB_CONNECTION', 'default'),'is_revoke_user_others_token' => true,//when user login if revoke user's all token except current one
];

编辑文件 config/autoload/exceptions.php

在文件中引入session中间件验证的异常处理器和passport中间件验证的异常处理器

Richard\HyperfPassport\Exception\Handler\SessionAuthenticationExceptionHandler::class Richard\HyperfPassport\PassportExceptionHandler::class

用户也可以定义自己的PassportExceptionHandler

以下为exceptions.php文件样板

<?phpdeclare(strict_types=1);
/*** This file is part of Hyperf.** @link     https://www.hyperf.io* @document https://hyperf.wiki* @contact  group@hyperf.io* @license  https://github.com/hyperf/hyperf/blob/master/LICENSE*/
return ['handler' => ['http' => [Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler::class,App\Exception\Handler\AppExceptionHandler::class,\Richard\HyperfPassport\Exception\Handler\SessionAuthenticationExceptionHandler::class,//you can use your own exception handler instead of \Richard\HyperfPassport\PassportExceptionHandler::class//App\Exception\Handler\AppPassportExceptionHandler::class,//user defined PassportExceptionHandler to replace below one\Richard\HyperfPassport\PassportExceptionHandler::class,      ],],
];

编辑文件 config/autoload/auth.php

在文件中引入Provicer和Guard

use Richard\HyperfPassport\PassportUserProvider;

use Richard\HyperfPassport\Guard\TokenGuard;

在guards里面填写

'passport' => [ 'driver' => TokenGuard::class, 'provider' => 'users', ]

并在users里面定义相应的provider

'users' => [ 'driver' => PassportUserProvider::class,

'model' => App\Model\User::class,

]

以下为auth.php文件样板

<?phpdeclare(strict_types=1);use HPlus\Admin\Model\Admin\Administrator;
use Qbhy\HyperfAuth\Provider\EloquentProvider;
use Qbhy\SimpleJwt\Encoders\Base64UrlSafeEncoder;
use Qbhy\SimpleJwt\EncryptAdapters\PasswordHashEncrypter;
use Richard\HyperfPassport\PassportUserProvider;
use Richard\HyperfPassport\Guard\TokenGuard;return ['default' => ['guard' => 'jwt','provider' => 'admin',],'guards' => [// 开发者可以在这里添加自己的 guard ,guard Qbhy\HyperfAuth\AuthGuard 接口'jwt' => ['driver' => Qbhy\HyperfAuth\Guard\JwtGuard::class,'provider' => 'admin','secret' => env('JWT_SECRET', 'hyperf.plus'),'ttl' => 60 * 60, // 单位秒'default' => PasswordHashEncrypter::class,'encoder' => new Base64UrlSafeEncoder(),'cache' => function () {return make(Qbhy\HyperfAuth\HyperfRedisCache::class);},],'session' => ['driver' => Qbhy\HyperfAuth\Guard\SessionGuard::class,'provider' => 'users',],'passport' => ['driver' => TokenGuard::class,'provider' => 'users',],],'providers' => ['admin' => ['driver' => EloquentProvider::class, // user provider 需要实现 Qbhy\HyperfAuth\UserProvider 接口'model' => Administrator::class, //  需要实现 Qbhy\HyperfAuth\Authenticatable 接口],'users' => ['driver' => PassportUserProvider::class, // user provider 需要实现 Qbhy\HyperfAuth\UserProvider 接口'model' => App\Model\User::class, //  需要实现 Qbhy\HyperfAuth\Authenticatable 接口],'merchants' => ['driver' => PassportUserProvider::class, // user provider 需要实现 Qbhy\HyperfAuth\UserProvider 接口'model' => App\Model\Merchant::class, //  需要实现 Qbhy\HyperfAuth\Authenticatable 接口],],
];
?>

执行迁移

php bin/hyperf.php migrate

php bin/hyperf.php migrate:status

安装passport

php bin/hyperf.php passport:install --force --length=4096

php bin/hyperf.php passport:purge

你还可以根据providers配置项里面的元素生成对应的client,默认生成授权码模式的client

php bin/hyperf.php passport:client --password --name="your client name"//生成密码模式的client

php bin/hyperf.php passport:client --personal --name="your client name"//生成Personal Access模式的client

php bin/hyperf.php passport:client --client --name="your client name"//生成客户端模式的client

如果有数据填充文件可以执行 php bin/hyperf.php db:seed --path=seeders/user_table_seeder.php

其中seeders/user_table_seeder.php为填充文件路径

注意填充文件中密码的格式为 \HyperfExt\Hashing\Hash::make('your password'),否则会导致passport密码校验失败

在用户模型中引入\Richard\HyperfPassport\HasApiTokens和\Richard\HyperfPassport\Auth\AuthenticatableTrait以及\Qbhy\HyperfAuth\AuthAbility

用户登录默认是验证email如果希望验证其他字段可以在模型中添加findForPassport方法,然后编写自己的代码逻辑

用户密码默认存储字段是password如果希望验证其他字段可以在模型中添加getAuthPassword方法,然后返回自己的密码字段

以下为模型文件User.php样板

<?phpdeclare (strict_types=1);namespace App\Model;use Hyperf\DbConnection\Db;
use Qbhy\HyperfAuth\AuthAbility;
use Qbhy\HyperfAuth\Authenticatable;
use Richard\HyperfPassport\Auth\AuthenticatableTrait;
use Richard\HyperfPassport\HasApiTokens;/***/
class User extends Model implements Authenticatable {use HasApiTokens;use AuthenticatableTrait;use AuthAbility;public $timestamps = false;/*** The table associated with the model.** @var string*/protected $table = 'member';/*** The attributes that are mass assignable.** @var array*/protected $fillable = [];/*** The attributes that should be cast to native types.** @var array*/protected $casts = [];/*** 修改认证时的默认username字段*/public function findForPassport($username) {if (strpos($username, '@') !== false) {return $this->findByEmailForPassport($username);} else {if ((is_numeric($username)) && (strlen($username) == 11)) {return $this->findByMobileForPassport($username);} else {return $this->findByUsernameForPassport($username);}}}protected function findByEmailForPassport($username) {return $this->where('email', $username)->first();}protected function findByMobileForPassport($username) {return $this->where('mobile', $username)->first();}protected function findByUsernameForPassport($username) {return $this->where('member_name', $username)->first();}/*** Get the password for the user.** @return string*/public function getAuthPassword() {return $this->member_pass;}}
?>

使用 - usage

以下是伪代码,仅供参考。

接口名称

  • 登录以获取会话令牌和刷新令牌

请求地址

  • /oauth/token

请求方式

  • POST

参数

参数名 必选 类型 说明
username string 用户名/邮箱/手机号
password string 用户密码
grant_type string 授权类型 一般填password
client_id string 服务端分配的client_id
client_secret string 服务端分配的client_id对应的密钥

响应信息

{"token_type": "Bearer","expires_in": 31536000,"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIyIiwianRpIjoiMjFhZWY3ODJkNjhhMjE3YmQzYTg1YTFlMTY3MWYwMjBmMzIxOGIwMTNlZDA4ZmNlMjIzYTFiNGFkODM1ZGY2MDE3YThjODg0YjU3ZDVhMDUiLCJpYXQiOjE2Mjg1NjkzMzQsIm5iZiI6MTYyODU2OTMzNCwiZXhwIjoxNjYwMTA1MzM0LCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.Z2PCWbJeL4NsDtJqLyER-Gg0Kf-DV-66mV91bVLryXJtl3YBhZcPsALQZt5WVTa4gC2s-GBOvMBnREZeJ4RfBaBKoBmEF1_6Cj1I8u0zdazF3mGh-V5JpiDJw73XmdQQ61lqiOp0Lxx34H7ZSkhGYhn9QqSkHmqtrRT_8NYY-bJ1GOE4i0EO-ckFqrHhtvWrVooZ5eN3SkxG3bEf24LLuvCj9EhtKPdF818dYjjWxiA2pl_3OAakQDOHTVh1MhvFW1TTnjBGf0aG2_7gcZWyFzJydx59U_8knOvIS5upTB9aP13q2dXGTGf9Q-1EvVDLNyxB_ppDCkCogc7daTkgs_aqvoC1EsC5pqPDZQDCO5RbVcJ881GGNdjtrmud7qapc9HO7e6JZSKg_cx72IB8jziKwWiqTMJMtDrlaWJ4gkGMO5MeEnberIp6J6Yut0iWR6CUWVBTDPymeOpdbZqpwINhcFh4qq_YSNh8IE9tW9-HbYi6NrAX1I1KSaDWgHI9m56nkY2afT8le0IbEJ5AjwcWBATuQbJfj3S2jfyIembJoq9egKeGMrG9KADM151phFR1h6vItJMlGbjwMx6Pry6E5fvQUIxfi3N5-k-ptfKEsb2x-ENffzg_W8aeEEbObt4kV7OczxKGGdGqk3WY7_suASyEkN-7oEiUd77EJps","refresh_token": "def50200961f6b024f098f4ef9416c07515a6449f5feb7e3db6e2e19f4bb260f2758e9bc71b81e49587a96f83658c05f8b93243a6cd5342f1a6a7eee8582e3dedab8915ce24f41875077cd5e22926a53ea0b4675eeb86f3322285848cbac96086eedb0782d6d99a8f9bbe39bcdf1c3215ae127e0a40a9536bdb3496e36f03026015ebf88c81c1a860c6c15a8a48edc7bc8c4a150948b1cfad76c29b01e403711a25f6a6969aaec0777ef6919a7fc707ea63c780e744ceb593f8d7cfd8aef7af59769f1ba5be7b6479c45cdd1c15d3827dd6ba4d0193bead299840c4fea66356a56e2ca407add2d904b1a97a4f0977ad4fcc256cc8f805d3e1fe0379e77478c32d2c22b3f3b31ac289645873cae6de46fa50523238826942846746b0ee4270e6dffcd79994b14a939ada51af7afcc86047f5350b178f0a1d18ba4a3c72b5327dd366a4224252571e1a238fd11748703dbd439f620809b6706fd0d485c29b5c04feb"
}

接口名称

  • 使用刷新令牌获得新的会话令牌和刷新令牌

请求地址

  • /oauth/token

请求方式

  • POST

参数

参数名 必选 类型 说明
grant_type string 授权类型 refresh_token
refresh_token string 登录获得的刷新令牌
client_id string 服务端分配的client_id
client_secret string 服务端分配的client_id对应的密钥

响应信息

{"token_type": "Bearer","expires_in": 31536000,"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIyIiwianRpIjoiZTAwYzY5ZmU1YzJmZjJhY2RlYjU5ZGU5YTY3YzIwOTNiOWNmZGJmNjFjYWIwMDg5OWNmYTI1NWJiZWVhYmNlYTJhN2JiNGFhNTUyMzg3NzEiLCJpYXQiOjE2Mjg1Njk0ODgsIm5iZiI6MTYyODU2OTQ4OCwiZXhwIjoxNjYwMTA1NDg4LCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.qljYkIJUOkrMFsdxrMrieuj9uanUMo5lKqwvWBvg1cvHFvjXA0FxhTb6cnnKNKdUCFmKCIwWhCY-MleNDy5rso5NF_1EWiWTmaWgpGibVZvbuvrPSL6md8OWiMp3WBa-twO0F-YGkinRu2zycpi_3eVFp5OLL1vWTsOPNuUAHIqWc0bOjoKdGLy8z3bGY3K6iOzBDk2E9FNZG-af8ZDL-cA-0wDcsLRibexBon8rzNbpCK_rmuk8tm2u4xiEQ8xtvJysQ5d8vm19oUpXY61WkiePXbJolxpctCJAkmyZftwFIH1J9nQbLXxyeDenWqkB5Yi2L8wbgmf6y4xhNfDCDvNlionJl32COeT90zj0CUijQyUy6xrUW_kieC2OIjQ6FWLfMA_tMg2RmoS4BAyQah9cFq2B9g0K-SkVyQKpm7Tb7oTqJ3b3mMcL_SYGcwQH5QrAAFI0ngiHMdXJKW_HAcwV5qycQDRkeSqdkNExqawSeFVuM9xqstv8Q-y0i2Y4MmweBo8WHi4UL5NdALGKeK-rT_vQePcb-6l30d3PODH4UpZKwTddfNaP60m3OAqSOP4b1ZeV9shLSOotdUobDHzI3Y1Geujv30uphp1FyvPdsxKUba7o_94GOAsj7ggIAY1K5VLdjpUF0AxL611BRTWZ2NA5whwPbGOg5WGQpOc","refresh_token": "def50200c3c742e60906d59ef5f9628de44af8cf2fbc77b2782c540ed0c9a98a149b2c134d6156ec38f8ea340f09aa096623e0fb7000dfb6169c140d5ed08ebe50f0daa5d186fd05937e35f1bcdd4be81cc01e6bf9d5dcf32dbeeb124e1a729acf089089f7a2ab53d94ebdacb020834b831b6b9bba56e644eb0a320ebe2ce1cbdaf5c825b195396782ab0d8d8967d68e8edc9052d276d72f4f62529182fd054cc9f150ef84ae8f2aa895a62ae109e432bc045b7d5afeb6f9d0b0a44c9de7a2a84f298354baed67728fa57e866af742b6a22f0deaa022a446060c6e339e9151ab1adf118f76e9b5738d00c9fa67c3293399f6ca2c844eaa75d08ca21e592d45f5ac53b433344590ba5c0658f1891a7b9cbe4cd917183af060858813702ca5c0c1d0d296927dc6514a595c4fa02dcde225229881ba9ff8068d8f049004c752f00e715ac4761b34113b716e53142a9449e9f6274b489a5256022f53917673d21e8de4"
}

获得用户信息

由于passport支持多种类型用户,clientid是基于provicder发放的所以获取用户信息时需要提供client

请求头信息

Authorization Bearer access_token(注意Bearer后面含有空格)

X-Client-Id client_id

控制器代码

<?phpdeclare(strict_types=1);namespace App\Controller;use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\RequestMapping;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\Middleware;
use Hyperf\HttpServer\Annotation\Middlewares;
use Richard\HyperfPassport\PassportAuthMiddleware;
use Richard\HyperfPassport\AuthManager;/*** @Controller*/
class DemoController extends AbstractController {/*** @Inject* @var AuthManager*/protected $auth;/*** @Middlewares({*    @Middleware(PassportAuthMiddleware::class)* })* @RequestMapping(path="index", methods="get,post,options")*/public function index(RequestInterface $request, ResponseInterface $response) {$user = $this->auth->guard('passport')->user();//var_dump($user);$userId = (int) $user->getId();return ['user_id' => $userId];}}
?>

授权码模式使用指南

1 安装授权码模式client

2 假定 当前系统当前地址是 http://192.168.56.141:15403/,

当前系统用户登录成功后的回调地址是 http://192.168.56.141:15403/demo/callback,

当用户发起应用授权的时候系统将跳转到passport的授权页 http://192.168.56.141:15403/oauth/authorize?response_type=code&client_id=5&redirect_uri=http%3A%2F%2F192.168.56.141%3A15403%2Fdemo%2Fcallback&scope= 该页面将检查当前用户是否登录,如果用户没有登录系统将跳转至passport.php中指定的session_user_login_uri位置让用户登录

3 当用户登录成功并给应用授权后系统将跳转到指定的成功后的回调地址并携带授权码code,即http://192.168.56.141:15403/demo/callback?code=thisiscodedata 在此页面用户需要使用此code向服务器发起获得会话令牌和刷新令牌的请求,以下为示例代码:

<?php
declare(strict_types=1);
/*** This file is part of Hyperf.** @link     https://www.hyperf.io* @document https://hyperf.wiki* @contact  group@hyperf.io* @license  https://github.com/hyperf/hyperf/blob/master/LICENSE*/namespace App\Controller;use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\RequestMapping;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\Middleware;
use Hyperf\HttpServer\Annotation\Middlewares;
use Richard\HyperfPassport\SessionAuthMiddleware;
use Richard\HyperfPassport\AuthManager;
use Hyperf\HttpServer\Annotation\AutoController;/*** @Controller*/
class DemoController extends AbstractController {/*** @Inject* @var AuthManager*/protected $auth;/*** @Middlewares({*    @Middleware(SessionAuthMiddleware::class)* })* @RequestMapping(path="index", methods="get,post,options")*/public function index(RequestInterface $request, ResponseInterface $response) {$user = $this->auth->guard('session')->user();var_dump($user, $this->auth->guard('session')->getName(), $this->auth->guard('session')->getProvider()->getProviderName());$userId = (int) $user->getId();$name = isset($user->login_name) ? $user->login_name : $user->member_name;return ['user_id' => $userId, 'user_name' => $name];}/*** @RequestMapping(path="callback", methods="get,post,options")*/public function callback(RequestInterface $request, ResponseInterface $response) {//first http://192.168.56.141:15403/oauth/authorize?response_type=code&client_id=5&redirect_uri=http%3A%2F%2F192.168.56.141%3A15403%2Fdemo%2Fcallback&scope=var_dump($request->all());$code = $request->input('code');echo $code;echo '<br/>';$client = new \GuzzleHttp\Client(['base_uri' => 'http://192.168.56.141:15403/',//http://your-app.com/'handler' => \GuzzleHttp\HandlerStack::create(new \Hyperf\Guzzle\CoroutineHandler()),'timeout' => 5,'swoole' => ['timeout' => 10,'socket_buffer_size' => 1024 * 1024 * 2,],]);$formParams=['grant_type' => 'authorization_code','client_id' => '5','client_secret' => 'HqD1wOvLc14DFkr7cckNNb1GD38d05PlBMVtA9TD','redirect_uri' => 'http://192.168.56.141:15403/demo/callback','code' => $code,];$response = $client->post('/oauth/token',['form_params' => $formParams]);$body = $response->getBody();var_dump($body);$bodyStr = (string)$body;var_dump($bodyStr);return json_decode($bodyStr,true);}}
?>

Hyperf插件之passport认证插件richard8768/hyperf-passport相关推荐

  1. mysql 8.0认证失败_解决mysql8.0因密码认证插件导致的链接不上

    简介 今天在迁移zabbix的数据库,每次链接到自己的mysql都报错, mysqlAuthentication plugin 'caching_sha2_password' cannot be lo ...

  2. 本机号码一键登录!推荐 Flutter 极光认证插件

    本机号码一键登录基本成为各个 APP 的标配了. 传统的手机验证码登录方式: 输入手机号: 前端判断手机号是否有效: 后台判断手机号是否有效: 借助第三方下发手机验证码: 前端有一个倒计时,防止验证码 ...

  3. 【插件】最新代刷跳彩虹易支付认证插件

    介绍 [插件]最新代刷跳彩虹易支付认证插件 这是插件,不是完整的易支付 彩虹易支付绕过认证插件原理: 通过绕过彩虹set.php文件远程验证域名的方式,进行修改mysql表单内容,让未认证的域名进行绕 ...

  4. kong笔记——kong的权限认证插件选择参考

    kong笔记--目录导航 kong自身共提供了这么几个权限认证插件: basic auth; key auth; hmac auth; jwt auth; oauth2 auth 接下来来逐个介绍其特 ...

  5. Udesk 即时通讯网页插件客户身份认证(二、插件传入篇)

    作者:张振琦 上篇我们实现了服务端的签名算法,我接下来看WebIM插件里如何传入客户信息.调用的接口如下: // SHA1示例 ud({"customer": {"non ...

  6. MySQL8认证插件—LDAP Pluggable Authentication

    MySQL8认证插件-LDAP Pluggable Authentication 原创2023-05-05 19:00·贺浦力特 MySQL Enterprise Edition支持一种身份验证方法, ...

  7. 开源IDaaS方舟一账通ArkID系统内置OIDC 认证插件配置流程

    OIDC 是基于OAuth2和OpenID整合的新身份认证授权标准协议,且完全兼容 OAuth.Access Token 来解决授权第三方客户端访问受保护资源的问题,OIDC 在这个基础上提供了ID ...

  8. 在kibana V6.5.1上开发认证插件的踩坑记录

    之前在6.0.0版本上做过一次,那时是用template-kibana-plugin也就是sao来生成开发插件的模板的.需要自己去下载npm install -g sao然后再跑起来.还是想用这个方法 ...

  9. 【译】Tim Rose 的kibana插件教程-自定义App插件

    kibana官方没有插件的开发教程,Tim Rose的教程写的十分详尽,也是官方推荐的.由于这个系列的教程是英文版的,且基于kibana4,近日需要做kibana的开发,硬啃下这些教程之后,虽然这些教 ...

最新文章

  1. ASP.NET-Cookies的用法
  2. python文件输出-python 文件的输入输出
  3. 巨亏的旷视科技,是AI独角兽还是物联网企业?
  4. 一个即将毕业的软件工程大学生的感悟
  5. 数据可视化,必须注意的30个小技巧!
  6. 赛博朋克世界的声景塑造 — 以游戏《底特律:成为人类》为例 (第一部分)
  7. hive中的UDAF的使用流程记载
  8. 谷歌18年博士生奖研金出炉,八位入选华人学生均毕业于国内高校
  9. 前Overstock CEO推特账户被封 其为比特币早期拥护者
  10. ajax如何向action发送数据的
  11. x86汇编语言复习笔记
  12. Spring安全权限管理(Spring Security的配置使用)
  13. 层次分析法——matlab实操
  14. python获取扫描枪数据线_【转】C#中判断扫描枪输入与键盘输入
  15. R16之Access to Unlicensed Spectrum(3)
  16. 网络程序设计课程总结
  17. 社区发现研究现状(一)
  18. echarts柱状图
  19. js获取唯一设备码_HTML5+下用js获取设备的唯一识别码和本地数据库的操作
  20. 使用log4j将日志输送到控制台、文件或数据库中

热门文章

  1. flash中遮罩+形状补间实现场景转换
  2. 【引流技术】最新QQ客源大师多功能引流营销脚本,解放双手自动引流工具【永久脚本+软件使用视频教程】
  3. C++面向对象程序设计入门(第一节)
  4. 解决:DeprecationWarning: There is no current event loop
  5. 新版SteamVR插件基础操作手册
  6. 掌财社:全面注册制的影响有哪些?
  7. 电商淘宝补单系统源码程序的开发与部署
  8. 中国女篮63-51战胜欧洲劲旅拉脱维亚 获钻石杯季军
  9. python 爬虫代码 爬隐藏_爬虫隐藏
  10. 洛谷 P1332 血色先锋队