添加微信登录

main
liutk 2024-05-18 19:07:39 +08:00
parent 894bcaa534
commit bda8aa44c2
15 changed files with 1467 additions and 13 deletions

View File

@ -88,7 +88,7 @@ class ActivityController extends AdminController
amis()->TextControl('name', __('admin.activities.name'))->required(true), amis()->TextControl('name', __('admin.activities.name'))->required(true),
Components::make()->cropImageControl('cover', __('admin.activities.cover'), 1.5)->required(true), Components::make()->cropImageControl('cover', __('admin.activities.cover'), 1.5)->required(true),
amis()->DateRangeControl('start_at', __('admin.activities.activity_at'))->extraName('end_at')->clearable(false)->format('YYYY-MM-DD HH:mm:ss')->required(true), amis()->DateRangeControl('start_at', __('admin.activities.activity_at'))->extraName('end_at')->clearable(false)->format('YYYY-MM-DD HH:mm:ss')->required(true),
amis()->TextareaControl('rules', __('admin.activities.rules')) amis()->TextareaControl('rules', __('admin.activities.rules'))->minRows(30)
]); ]);
} }
@ -240,7 +240,7 @@ class ActivityController extends AdminController
amis()->TextControl('gift_name', __('admin.activity_gifts.name'))->required(true), amis()->TextControl('gift_name', __('admin.activity_gifts.name'))->required(true),
Components::make()->cropImageControl('logo', __('admin.activity_gifts.logo')), Components::make()->cropImageControl('logo', __('admin.activity_gifts.logo')),
Components::make()->sortControl('rank', __('admin.activity_gifts.rank'))->required(true), Components::make()->sortControl('rank', __('admin.activity_gifts.rank'))->required(true),
amis()->TextareaControl('explain', __('admin.activity_gifts.explain')) amis()->TextareaControl('explain', __('admin.activity_gifts.explain'))->minRows(6)
]) ])
])->size('lg') ])->size('lg')
) )
@ -262,7 +262,7 @@ class ActivityController extends AdminController
amis()->TextControl('name', __('admin.activity_gifts.name'))->required(true), amis()->TextControl('name', __('admin.activity_gifts.name'))->required(true),
Components::make()->cropImageControl('logo', __('admin.activity_gifts.logo')), Components::make()->cropImageControl('logo', __('admin.activity_gifts.logo')),
Components::make()->sortControl('rank', __('admin.activity_gifts.rank'))->required(true), Components::make()->sortControl('rank', __('admin.activity_gifts.rank'))->required(true),
amis()->TextareaControl('explain', __('admin.activity_gifts.explain')) amis()->TextareaControl('explain', __('admin.activity_gifts.explain'))->minRows(6)
]) ])
])->size('lg')), ])->size('lg')),
amisMake()->AjaxAction()->label('删除')->level('link') amisMake()->AjaxAction()->label('删除')->level('link')

View File

@ -87,7 +87,7 @@ class ArticleController extends AdminController
amis()->Wrapper()->body([ amis()->Wrapper()->body([
amis()->TextControl('title', __('admin.articles.title'))->required(true), amis()->TextControl('title', __('admin.articles.title'))->required(true),
Components::make()->parentControl(admin_url('api/keywords/tree-list?parent_name=article_category&has_owner=0'), 'category', __('admin.articles.category'), 'name', 'key'), Components::make()->parentControl(admin_url('api/keywords/tree-list?parent_name=article_category&has_owner=0'), 'category', __('admin.articles.category'), 'name', 'key'),
Components::make()->keywordsTagControl('t_ids', __('admin.articles.tags'), 'article_tag'), // Components::make()->keywordsTagControl('t_ids', __('admin.articles.tags'), 'article_tag'),
Components::make()->cropImageControl('cover', __('admin.articles.cover')), Components::make()->cropImageControl('cover', __('admin.articles.cover')),
amis()->TextControl('source', __('admin.articles.source')), amis()->TextControl('source', __('admin.articles.source')),
Components::make()->sortControl('sort', __('admin.articles.sort')), Components::make()->sortControl('sort', __('admin.articles.sort')),

View File

@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Response;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class ApiController extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
public function __construct()
{
if (property_exists($this, 'serviceName')) {
$this->service = $this->serviceName::make();
}
}
public function json($data, $code = Response::HTTP_OK, $message = '')
{
$result = ['data' => $data, 'code' => $code, 'message' => $message];
return response()->json($result);
}
public function success($data = [], $message = '')
{
return $this->json($data, Response::HTTP_OK, $message);
}
public function error($message = '', $code = Response::HTTP_BAD_REQUEST, $data = [])
{
return $this->json($data, $code, $message);
}
}

View File

@ -1,7 +1,46 @@
<?php <?php
namespace App\Controllers\Api; namespace App\Http\Controllers\Api;
class AuthController{ use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Services\Api\UserService;
use App\Http\Resources\Api\UserResource;
class AuthController extends ApiController
{
protected string $serviceName = UserService::class;
public function codeToSession()
{
//微信小程序-todo
// dd();
$openid = '132465';
$user = $this->service->register($openid);
$apiToken = $this->service->login($user);
return $this->success([
'user' => UserResource::make($user)->resolve(),
'api_token' => $apiToken,
'expire_in' => Carbon::parse()->addDay()->toDateTimeString()
]);
}
/**
*
*/
public function refreshToken(Request $request)
{
$user = auth('api')->user();
//撤销当前令牌
$request->user()->currentAccessToken()->delete();
//颁发新令牌
return $this->success([
'api_token' => $user->createToken(
name: 'api',
expiresAt: now()->addDay(),
)->plainTextToken,
'expire_in' => Carbon::parse()->addDay()->toDateTimeString()
]);
}
} }

View File

@ -39,6 +39,7 @@ class Kernel extends HttpKernel
], ],
'api' => [ 'api' => [
\App\Http\Middleware\AcceptHeader::class,
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api', \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,

View File

@ -0,0 +1,22 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class AcceptHeader
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$request->headers->set('Accept', 'application/json');
return $next($request);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class HasBindPhone
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$user = auth('api')->user();
if(!($user && $user->phone)){
return response()->json(['data'=>[], 'code'=>Response::HTTP_FORBIDDEN, 'message'=>'请先登录']);
}
return $next($request);
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Resources\Api;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'nick_name' => $this->nick_name ??'',
'avatar' => $this->avatar ??'',
'phone' => $this->phone ??'',
];
}
public function with($request)
{
return ['code' => Response::HTTP_OK, 'message' => ''];
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Services\Api;
use App\Models\User;
/**
* @method User getModel()
* @method User|\Illuminate\Database\Query\Builder query()
*/
class UserService
{
public static function make(): static
{
return new static;
}
public function register($miniOpenid)
{
if(! $user = User::where('mini_openid', $miniOpenid)->first()){
$user = new User();
$user->mini_openid = $miniOpenid;
$user->save();
}
return $user;
}
public function login(User $user)
{
//记录当前登录时间、IP
$ip = request()->getClientIp();
$user->last_login_ip = ip2long($ip);
$user->last_login_at = now();
$user->save();
//撤销当前所有令牌;
$user->tokens()->delete();
return $user->createToken(
name: 'api',
expiresAt: now()->addDay(),
)->plainTextToken;
}
}

View File

@ -11,6 +11,7 @@
"laravel/framework": "^10.10", "laravel/framework": "^10.10",
"laravel/sanctum": "^3.3", "laravel/sanctum": "^3.3",
"laravel/tinker": "^2.8", "laravel/tinker": "^2.8",
"overtrue/laravel-wechat": "^7.2",
"rap2hpoutre/fast-excel": "^5.4", "rap2hpoutre/fast-excel": "^5.4",
"slowlyo/owl-admin": "^3.5", "slowlyo/owl-admin": "^3.5",
"tucker-eric/eloquentfilter": "^3.2" "tucker-eric/eloquentfilter": "^3.2"

1070
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -40,6 +40,11 @@ return [
'driver' => 'session', 'driver' => 'session',
'provider' => 'users', 'provider' => 'users',
], ],
'api' => [
'driver' => 'sanctum',
'provider' => 'users',
],
], ],
/* /*

View File

@ -0,0 +1,168 @@
<?php
return [
/*
* 默认配置,将会合并到各模块中
*/
'defaults' => [
'http' => [
'timeout' => 5.0,
],
],
/*
* 公众号
*/
// 'official_account' => [
// 'default' => [
// 'app_id' => env('WECHAT_OFFICIAL_ACCOUNT_APPID', ''), // AppID
// 'secret' => env('WECHAT_OFFICIAL_ACCOUNT_SECRET', ''), // AppSecret
// 'token' => env('WECHAT_OFFICIAL_ACCOUNT_TOKEN', ''), // Token
// 'aes_key' => env('WECHAT_OFFICIAL_ACCOUNT_AES_KEY', ''), // EncodingAESKey
/*
* OAuth 配置
*
* scopes公众平台snsapi_userinfo / snsapi_base开放平台snsapi_login
* callbackOAuth授权完成后的回调页地址(如果使用中间件,则随便填写。。。)
* enforce_https是否强制使用 HTTPS 跳转
*/
// 'oauth' => [
// 'scopes' => array_map('trim', explode(',', env('WECHAT_OFFICIAL_ACCOUNT_OAUTH_SCOPES', 'snsapi_userinfo'))),
// 'callback' => env('WECHAT_OFFICIAL_ACCOUNT_OAUTH_CALLBACK', '/examples/oauth_callback.php'),
// 'enforce_https' => true,
// ],
/**
* 接口请求相关配置,超时时间等,具体可用参数请参考:
* https://github.com/symfony/symfony/blob/6.0/src/Symfony/Contracts/HttpClient/HttpClientInterface.php#L26
*/
//'http' => [
// 'timeout' => 5.0,
// // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri
// 'base_uri' => 'https://api.weixin.qq.com/',
//],
// ],
// ],
/*
* 开放平台第三方平台
*/
// 'open_platform' => [
// 'default' => [
// 'app_id' => env('WECHAT_OPEN_PLATFORM_APPID', ''),
// 'secret' => env('WECHAT_OPEN_PLATFORM_SECRET', ''),
// 'token' => env('WECHAT_OPEN_PLATFORM_TOKEN', ''),
// 'aes_key' => env('WECHAT_OPEN_PLATFORM_AES_KEY', ''),
/**
* 接口请求相关配置,超时时间等,具体可用参数请参考:
* https://github.com/symfony/symfony/blob/6.0/src/Symfony/Contracts/HttpClient/HttpClientInterface.php#L26
*/
// 'http' => [
// 'timeout' => 5.0,
// // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri
// 'base_uri' => 'https://api.weixin.qq.com/',
// ],
// ],
// ],
/*
* 小程序
*/
'mini_app' => [
'default' => [
'app_id' => env('WECHAT_MINI_APP_APPID', ''),
'secret' => env('WECHAT_MINI_APP_SECRET', ''),
'token' => env('WECHAT_MINI_APP_TOKEN', ''),
'aes_key' => env('WECHAT_MINI_APP_AES_KEY', ''),
/**
* 接口请求相关配置,超时时间等,具体可用参数请参考:
* https://github.com/symfony/symfony/blob/6.0/src/Symfony/Contracts/HttpClient/HttpClientInterface.php#L26
*/
'http' => [
'timeout' => 5.0,
// 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri
'base_uri' => 'https://api.weixin.qq.com/',
],
],
],
/*
* 微信支付
*/
// 'pay' => [
// 'default' => [
// 'app_id' => env('WECHAT_PAY_APPID', ''),
// 'mch_id' => env('WECHAT_PAY_MCH_ID', 'your-mch-id'),
// 'private_key' => '/data/private/certs/apiclient_key.pem',
// 'certificate' => '/data/private/certs/apiclient_cert.pem',
// 'notify_url' => 'http://example.com/payments/wechat-notify', // 默认支付结果通知地址
// /**
// * 证书序列号,可通过命令从证书获取:
// * `openssl x509 -in application_cert.pem -noout -serial`
// */
// 'certificate_serial_no' => '6F2BADBE1738B07EE45C6A85C5F86EE343CAABC3',
//
// 'http' => [
// 'base_uri' => 'https://api.mch.weixin.qq.com/',
// ],
//
// // v2 API 秘钥
// //'v2_secret_key' => '26db3e15cfedb44abfbb5fe94fxxxxx',
//
// // v3 API 秘钥
// //'secret_key' => '43A03299A3C3FED3D8CE7B820Fxxxxx',
//
// // 注意 此处为微信支付平台证书 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/wechatpay5_1.shtml
// 'platform_certs' => [
// '/data/private/certs/platform_key.pem',
// ],
// ],
// ],
/*
* 企业微信
*/
// 'work' => [
// 'default' => [
// 'corp_id' => env('WECHAT_WORK_CORP_ID', ''),
// 'secret' => env('WECHAT_WORK_SECRET', ''),
// 'token' => env('WECHAT_WORK_TOKEN', ''),
// 'aes_key' => env('WECHAT_WORK_AES_KEY', ''),
/**
* 接口请求相关配置,超时时间等,具体可用参数请参考:
* https://github.com/symfony/symfony/blob/6.0/src/Symfony/Contracts/HttpClient/HttpClientInterface.php#L26
*/
// 'http' => [
// 'timeout' => 5.0,
// // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri
// 'base_uri' => 'https://api.weixin.qq.com/',
// ],
// ],
// ],
/*
* 企业微信开放平台
*/
// 'open_work' => [
// 'default' => [
// 'corp_id' => env('WECHAT_OPEN_WORK_CORP_ID', ''),
// 'provider_secret' => env('WECHAT_OPEN_WORK_SECRET', ''),
// 'token' => env('WECHAT_OPEN_WORK_TOKEN', ''),
// 'aes_key' => env('WECHAT_OPEN_WORK_AES_KEY', ''),
/**
* 接口请求相关配置,超时时间等,具体可用参数请参考:
* https://github.com/symfony/symfony/blob/6.0/src/Symfony/Contracts/HttpClient/HttpClientInterface.php#L26
*/
// 'http' => [
// 'timeout' => 5.0,
// // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri
// 'base_uri' => 'https://api.weixin.qq.com/',
// ],
// ],
// ],
];

View File

@ -13,16 +13,19 @@ return new class extends Migration
{ {
Schema::create('users', function (Blueprint $table) { Schema::create('users', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('mini_openid')->comment('小程序openid'); $table->string('mini_openid')->unique()->comment('小程序openid');
$table->string('union_id')->nullable()->default('')->comment('微信唯一标识'); $table->string('union_id')->nullable()->comment('微信唯一标识');
$table->string('nick_name')->nullable()->default('')->comment('微信昵称'); $table->string('nick_name')->nullable()->default('')->comment('微信昵称');
$table->string('avatar')->nullable()->default('')->comment('微信头像'); $table->string('avatar')->nullable()->default('')->comment('微信头像');
$table->string('phone')->nullable()->default('')->comment('手机号'); $table->string('phone')->nullable()->default('')->comment('手机号');
$table->timestamp('bind_phone_at')->nullable()->comment('绑定手机号时间');
$table->timestamp('last_login_at')->nullable()->comment('上次登录时间'); $table->timestamp('last_login_at')->nullable()->comment('上次登录时间');
$table->timestamp('last_login_ip')->nullable()->comment('上次登录ip'); $table->bigInteger('last_login_ip')->nullable()->comment('上次登录ip');
$table->rememberToken(); $table->rememberToken();
$table->timestamps(); $table->timestamps();
}); });
} }

View File

@ -2,6 +2,7 @@
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use App\Http\Middleware\HasBindPhone;
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -13,7 +14,16 @@ use Illuminate\Support\Facades\Route;
| be assigned to the "api" middleware group. Make something great! | be assigned to the "api" middleware group. Make something great!
| |
*/ */
Route::group(['prefix' => 'miniprogram', 'namespace' => 'Api\Miniprogram'], function () {
// code2session
Route::post('code-to-session', [App\Http\Controllers\Api\AuthController::class, 'codeToSession']);
Route::middleware('auth:sanctum')->group(function(){
// 令牌刷新
Route::post('refresh-token', [App\Http\Controllers\Api\AuthController::class, 'refreshToken']);
// 已授权绑定手机号
Route::middleware([HasBindPhone::class])->group(function(){
Route::middleware('auth:sanctum')->get('/user', function (Request $request) { });
return $request->user(); });
}); });