添加 实人认证

This commit is contained in:
iVampireSP.com 2023-01-15 05:37:25 +08:00
parent a49add6f03
commit 741e47c971
No known key found for this signature in database
GPG Key ID: 2F7B001CA27A8132
26 changed files with 611 additions and 77 deletions

View File

@ -5,6 +5,10 @@ APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://lae.test APP_URL=http://lae.test
NODE_TYPE=master
NODE_ID=
NODE_IP=
LOG_CHANNEL=daily LOG_CHANNEL=daily
LOG_DEPRECATIONS_CHANNEL=null LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug LOG_LEVEL=debug
@ -111,6 +115,6 @@ TRUSTED_PROXIES=
# Roadrunner 的版本,比如 2.12.1, 默认也是 2.12.1 # Roadrunner 的版本,比如 2.12.1, 默认也是 2.12.1
ROADRUNNER_VERSION= ROADRUNNER_VERSION=
NODE_TYPE=master # 实人认证的配置
NODE_ID= SUPPORT_REAL_NAME_APP_CODE=
NODE_IP=

View File

@ -59,6 +59,10 @@ public function register(): void
// } // }
// return response(); // return response();
// }); // });
$this->reportable(function (Throwable $e) {
//
});
} }
/** /**

View File

@ -62,98 +62,76 @@ public function error($message = '', $code = 400): JsonResponse
return $this->apiResponse($message, $code); return $this->apiResponse($message, $code);
} }
// bad request public function failed($message = 'Failed', $code = 400): JsonResponse
{
return $this->apiResponse($message, $code);
}
public function serviceUnavailable($message = 'Service unavailable'): JsonResponse public function serviceUnavailable($message = 'Service unavailable'): JsonResponse
{ {
return $this->error($message, 503); return $this->error($message, 503);
} }
// created
public function forbidden($message = 'Forbidden'): JsonResponse public function forbidden($message = 'Forbidden'): JsonResponse
{ {
return $this->error($message, 403); return $this->error($message, 403);
} }
// accepted
public function notFound($message = 'Not found'): JsonResponse public function notFound($message = 'Not found'): JsonResponse
{ {
return $this->error($message, 404); return $this->error($message, 404);
} }
// no content
public function methodNotAllowed($message = 'Method not allowed'): JsonResponse public function methodNotAllowed($message = 'Method not allowed'): JsonResponse
{ {
return $this->error($message, 405); return $this->error($message, 405);
} }
// updated
public function tooManyRequests($message = 'Too many requests'): JsonResponse public function tooManyRequests($message = 'Too many requests'): JsonResponse
{ {
return $this->error($message, 429); return $this->error($message, 429);
} }
// deleted
public function serverError($message = 'Server error'): JsonResponse public function serverError($message = 'Server error'): JsonResponse
{ {
return $this->error($message, 500); return $this->error($message, 500);
} }
// not allowed
public function unauthorized($message = 'Unauthorized'): JsonResponse public function unauthorized($message = 'Unauthorized'): JsonResponse
{ {
return $this->error($message, 401); return $this->error($message, 401);
} }
// conflict
public function accepted($message = 'Accepted'): JsonResponse public function accepted($message = 'Accepted'): JsonResponse
{ {
return $this->success($message, 202); return $this->success($message, 202);
} }
// too many requests
public function updated($message = 'Updated'): JsonResponse public function updated($message = 'Updated'): JsonResponse
{ {
return $this->success($message); return $this->success($message);
} }
// server error
public function deleted($message = 'Deleted'): JsonResponse public function deleted($message = 'Deleted'): JsonResponse
{ {
return $this->success($message); return $this->success($message);
} }
// service unavailable
public function notAllowed($message = 'Not allowed'): JsonResponse public function notAllowed($message = 'Not allowed'): JsonResponse
{ {
return $this->error($message, 405); return $this->error($message, 405);
} }
// method not allowed
public function conflict($message = 'Conflict'): JsonResponse public function conflict($message = 'Conflict'): JsonResponse
{ {
return $this->error($message, 409); return $this->error($message, 409);
} }
// not acceptable
public function notAcceptable($message = 'Not acceptable'): JsonResponse public function notAcceptable($message = 'Not acceptable'): JsonResponse
{ {
return $this->error($message, 406); return $this->error($message, 406);
} }
// precondition failed
public function preconditionFailed($message = 'Precondition failed'): JsonResponse public function preconditionFailed($message = 'Precondition failed'): JsonResponse
{ {
return $this->error($message, 412); return $this->error($message, 412);

View File

@ -37,10 +37,18 @@ public function index(Request $request): View
$users = $users->where('email', 'like', '%' . $request->input('email') . '%'); $users = $users->where('email', 'like', '%' . $request->input('email') . '%');
} }
if ($request->filled('real_name')) {
$users = $users->where('real_name', 'like', '%' . $request->input('real_name') . '%');
}
if ($request->has('banned_at')) { if ($request->has('banned_at')) {
$users = $users->whereNotNull('banned_at'); $users = $users->whereNotNull('banned_at');
} }
if ($request->has('real_name_verified_at')) {
$users = $users->whereNotNull('real_name_verified_at');
}
$users = $users->with('user_group')->paginate(50)->withQueryString(); $users = $users->with('user_group')->paginate(50)->withQueryString();
return view('admin.users.index', compact('users')); return view('admin.users.index', compact('users'));
@ -69,8 +77,6 @@ public function show(User $user): RedirectResponse
*/ */
public function edit(User $user): View public function edit(User $user): View
{ {
//
$hosts = (new Host)->where('user_id', $user->id)->latest()->paginate(50, ['*'], 'hosts_page'); $hosts = (new Host)->where('user_id', $user->id)->latest()->paginate(50, ['*'], 'hosts_page');
$workOrders = (new WorkOrder)->where('user_id', $user->id)->latest()->paginate(50, ['*'], 'workOrders_page'); $workOrders = (new WorkOrder)->where('user_id', $user->id)->latest()->paginate(50, ['*'], 'workOrders_page');
$balances = (new Balance)->where('user_id', $user->id)->latest()->paginate(50, ['*'], 'balances_page'); $balances = (new Balance)->where('user_id', $user->id)->latest()->paginate(50, ['*'], 'balances_page');
@ -92,6 +98,7 @@ public function update(Request $request, User $user): RedirectResponse
// //
$request->validate([ $request->validate([
'balance' => 'nullable|numeric|min:0.01|max:1000', 'balance' => 'nullable|numeric|min:0.01|max:1000',
'id_card' => 'nullable|string|size:18',
]); ]);
$transaction = new Transaction(); $transaction = new Transaction();
@ -129,6 +136,14 @@ public function update(Request $request, User $user): RedirectResponse
$user->user_group_id = $request->input('user_group_id'); $user->user_group_id = $request->input('user_group_id');
} }
if ($request->has('real_name')) {
$user->real_name = $request->input('real_name');
}
if ($request->has('id_card')) {
$user->id_card = $request->input('id_card');
}
if ($user->isDirty()) { if ($user->isDirty()) {
$user->save(); $user->save();
} }

View File

@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers\Public;
use App\Http\Controllers\Controller;
use App\Models\Transaction;
use App\Models\User;
use App\Support\RealNameSupport;
use Illuminate\Http\Request;
class RealNameController extends Controller
{
public function verify(Request $request)
{
$result = (new RealNameSupport())->verify($request->all());
if (!$result) {
return $this->error('实名认证失败。');
}
$user = User::find($result['user_id']);
$user->real_name = $result['name'];
$user->id_card = $result['id_card'];
$user->save();
$transaction = new Transaction();
$transaction->reduceAmount($user->id, 0.7, '实名认证费用。');
return $this->success('实名认证成功。');
}
public function process()
{
return view('real_name.process');
}
}

View File

@ -27,12 +27,23 @@ class AuthController extends Controller
public function index(Request $request): View|RedirectResponse public function index(Request $request): View|RedirectResponse
{ {
// if logged in // if logged in
if ($request->input('callback')) { if ($request->filled('callback')) {
session(['callback' => $request->input('callback')]); $callback = $request->input('callback');
session(['callback' => $callback]);
if (Auth::guard('web')->check()) {
$callbackHost = parse_url($callback, PHP_URL_HOST);
$dashboardHost = parse_url(config('settings.dashboard.base_url'), PHP_URL_HOST);
if ($callbackHost === $dashboardHost) {
$token = $request->user()->createToken('Dashboard')->plainTextToken;
return redirect($callback . '?token=' . $token);
}
if (Auth::check()) {
return redirect()->route('confirm_redirect'); return redirect()->route('confirm_redirect');
} else { } else {
return redirect()->route('login'); return redirect()->route('login');
@ -108,9 +119,6 @@ public function callback(Request $request): RedirectResponse
} }
$oauth_user = json_decode($oauth_user); $oauth_user = json_decode($oauth_user);
if (is_null($oauth_user->verified_at)) {
return redirect()->route('not_verified');
}
$user_sql = (new User)->where('email', $oauth_user->email); $user_sql = (new User)->where('email', $oauth_user->email);
$user = $user_sql->first(); $user = $user_sql->first();
@ -125,8 +133,8 @@ public function callback(Request $request): RedirectResponse
$user->email = $email; $user->email = $email;
$user->password = null; $user->password = null;
$user->email_verified_at = $email_verified_at; $user->email_verified_at = $email_verified_at;
$user->real_name = $oauth_user->real_name; // $user->real_name = $oauth_user->real_name;
$user->birthday_at = $oauth_user->birthday; // $user->birthday_at = $oauth_user->birthday;
$user->save(); $user->save();
$request->session()->put('auth.password_confirmed_at', time()); $request->session()->put('auth.password_confirmed_at', time());

View File

@ -0,0 +1,49 @@
<?php
namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller;
use App\Support\RealNameSupport;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class RealNameController extends Controller
{
public function create()
{
return view('real_name.create');
}
public function store(Request $request)
{
$request->validate([
'real_name' => 'required|string',
'id_card' => 'required|string|size:18|unique:users,id_card',
]);
$user = $request->user();
if ($user->real_name_verified_at !== null) {
return back()->with('error', '您已经实名认证过了。');
}
if ($user->balance < 1) {
return back()->with('error', '您的余额不足。请保证余额大于 1 元。');
}
$realNameSupport = new RealNameSupport();
$output = $realNameSupport->create($user->id, $request->input('real_name'), $request->input('id_card'));
// 标记用户正在实名,缓存 600s
if (Cache::has('real_name:user:' . $user->id)) {
// 获取缓存
$output = Cache::get('real_name:user:' . $user->id);
return back()->with('error', '因为您有一个正在进行的实名认证,请等待 10 分钟后重试。')->with('output', $output);
}
Cache::set('real_name:user:' . $user->id, $output, 600);
return redirect($output);
}
}

View File

@ -7,6 +7,7 @@
use App\Http\Middleware\EncryptCookies; use App\Http\Middleware\EncryptCookies;
use App\Http\Middleware\JsonResponse; use App\Http\Middleware\JsonResponse;
use App\Http\Middleware\PreventRequestsDuringMaintenance; use App\Http\Middleware\PreventRequestsDuringMaintenance;
use App\Http\Middleware\RealNamed;
use App\Http\Middleware\RedirectIfAuthenticated; use App\Http\Middleware\RedirectIfAuthenticated;
use App\Http\Middleware\TrimStrings; use App\Http\Middleware\TrimStrings;
use App\Http\Middleware\TrustProxies; use App\Http\Middleware\TrustProxies;
@ -97,5 +98,6 @@ class Kernel extends HttpKernel
'verified' => EnsureEmailIsVerified::class, 'verified' => EnsureEmailIsVerified::class,
'banned' => ValidateUserIfBanned::class, 'banned' => ValidateUserIfBanned::class,
'admin.validateReferer' => ValidateReferer::class, 'admin.validateReferer' => ValidateReferer::class,
'real_named' => RealNamed::class,
]; ];
} }

View File

@ -0,0 +1,40 @@
<?php
namespace App\Http\Middleware;
use App\Helpers\ApiResponse;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class RealNamed
{
use ApiResponse;
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @param string $guard
*
* @return Response|RedirectResponse|JsonResponse
*/
public function handle(Request $request, Closure $next, string $guard = 'web'): Response|RedirectResponse|JsonResponse
{
// 检测用户是否登录
if (auth($guard)->check()) {
if ($request->user($guard)->real_name_verified_at === null) {
if ($request->expectsJson()) {
return $this->error('您还没有实名认证。');
}
return redirect()->route('real_name.create');
}
}
return $next($request);
}
}

View File

@ -3,12 +3,14 @@
namespace App\Models; namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail; // use Illuminate\Contracts\Auth\MustVerifyEmail;
use Carbon\Exceptions\InvalidFormatException;
use GeneaLabs\LaravelModelCaching\Traits\Cachable; use GeneaLabs\LaravelModelCaching\Traits\Cachable;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Crypt;
use Laravel\Sanctum\HasApiTokens; use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable class User extends Authenticatable
@ -35,10 +37,12 @@ class User extends Authenticatable
protected $hidden = [ protected $hidden = [
'password', 'password',
'remember_token', 'remember_token',
'id_card',
]; ];
protected $casts = [ protected $casts = [
'email_verified_at' => 'datetime', 'email_verified_at' => 'datetime',
'real_name_verified_at' => 'datetime',
'balance' => 'decimal:2', 'balance' => 'decimal:2',
'banned_at' => 'datetime', 'banned_at' => 'datetime',
'birthday_at' => 'date', 'birthday_at' => 'date',
@ -52,13 +56,29 @@ protected static function boot()
$user->email_md5 = md5($user->email); $user->email_md5 = md5($user->email);
}); });
static::updating(function ($model) { static::updating(function (self $user) {
if ($model->isDirty('banned_at')) { if ($user->isDirty('banned_at')) {
if ($model->banned_at) { if ($user->banned_at) {
$model->tokens()->delete(); $user->tokens()->delete();
$model->hosts()->update(['status' => 'suspended', 'suspended_at' => now()]); $user->hosts()->update(['status' => 'suspended', 'suspended_at' => now()]);
} else { } else {
$model->hosts()->update(['status' => 'stopped']); $user->hosts()->update(['status' => 'stopped']);
}
}
if ($user->isDirty('email')) {
$user->email_md5 = md5($user->email);
}
if ($user->isDirty('id_card')) {
$user->real_name_verified_at = now();
// 更新生日
try {
$user->birthday_at = $user->getBirthdayFromIdCard();
} catch (InvalidFormatException) {
$user->birthday_at = null;
} }
} }
}); });
@ -86,4 +106,14 @@ public function selectPublic(): User
// 过滤掉私有字段 // 过滤掉私有字段
return $this->select(['id', 'name', 'email_md5', 'created_at']); return $this->select(['id', 'name', 'email_md5', 'created_at']);
} }
private function getBirthdayFromIdCard(): string
{
$idCard = $this->id_card;
$year = substr($idCard, 6, 4);
$month = substr($idCard, 10, 2);
$day = substr($idCard, 12, 2);
return $year . '-' . $month . '-' . $day;
}
} }

View File

@ -29,7 +29,7 @@ public function boot(): void
$this->configureRateLimiting(); $this->configureRateLimiting();
$this->routes(function () { $this->routes(function () {
Route::middleware(['api', 'auth:sanctum']) Route::middleware(['api', 'auth:sanctum', 'real_named:sanctum'])
->prefix('api') ->prefix('api')
->as('api.') ->as('api.')
->group(base_path('routes/api.php')); ->group(base_path('routes/api.php'));
@ -51,6 +51,11 @@ public function boot(): void
Route::middleware('web') Route::middleware('web')
->group(base_path('routes/web.php')); ->group(base_path('routes/web.php'));
Route::middleware(['api'])
->prefix('public')
->as('public.')
->group(base_path('routes/public.php'));
}); });
} }

View File

@ -0,0 +1,142 @@
<?php
namespace App\Support;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
/**
* 实名认证支持
*/
class RealNameSupport
{
private string $url = 'https://faceidh5.market.alicloudapi.com';
private string $app_code;
private PendingRequest $http;
public function __construct()
{
$this->app_code = config('settings.supports.real_name.code');
$this->http = Http::withHeaders([
'Authorization' => 'APPCODE ' . $this->app_code,
'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8',
'Accept' => 'application/json',
])->baseUrl($this->url);
}
/**
* 创建实名认证请求
*
* @param $user_id
* @param $name
* @param $id_card
*
* @return string
*/
public function create($user_id, $name, $id_card): string
{
$id = Str::random(32);
Cache::remember('real_name:' . $id, 600, function () use ($user_id, $name, $id_card) {
return [
'user_id' => $user_id,
'name' => $name,
'id_card' => $id_card,
];
});
return $this->submit($id);
}
/**
* 验证实名认证请求
*
* @param array $request
*
* @return array|bool
*/
public function verify(array $request): array|bool
{
$data = json_decode($request['data'], true);
$verify = $this->verifyIfSuccess($request['data'], $request['sign']);
if (!$verify) {
return false;
}
if ($data['code'] !== 'PASS') {
return false;
}
return Cache::get('real_name:' . $data['bizNo'], false);
}
/** 实名认证服务 发送请求
*
* @param string $id
*
* @return string
*/
private function submit(string $id): string
{
$real_name = Cache::get('real_name:' . $id);
if (!$real_name) {
abort(404, '找不到实名认证请求');
}
$data = [
'bizNo' => $id,
'idNumber' => $real_name['id_card'],
'idName' => $real_name['name'],
'pageTitle' => config('app.display_name') . ' 实名认证',
'notifyUrl' => route('public.real-name.notify'),
'procedureType' => 'video',
'txtBgColor' => '#cccccc',
'ocrIncIdBack' => 'false',
'ocrOnly' => 'false',
'pageBgColor' => 'false',
'retIdImg' => 'false',
'returnImg' => 'false',
'returnUrl' => route('public.real-name.process'),
];
$resp = $this->http->asForm()->post('/edis_ctid_id_name_video_ocr_h5', $data)->json();
if (!$resp || $resp['code'] !== '0000') {
abort(500, '调用远程服务器时出现了问题,请检查身份证号码是否正确。');
}
return $resp['verifyUrl'];
}
private function verifyIfSuccess(string $request, string $sign): bool
{
$public_key = <<<EOF
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWKKJoLwh6XEBkTeCfVbKSB3zkkycbIdd8SBabj2jpWynXx0pBZvdFpbb9AEiyrnM8bImhpz8YOXc2yUuN1ui/w==
-----END PUBLIC KEY-----
EOF;
$sign = base64_decode($sign);
$public_key = openssl_pkey_get_public($public_key);
if (!$public_key) {
abort(500, '公钥错误');
}
$flag = openssl_verify($request, $sign, $public_key, OPENSSL_ALGO_SHA256);
return $flag === 1;
}
}

View File

@ -28,7 +28,8 @@
"simplesoftwareio/simple-qrcode": "^4.2", "simplesoftwareio/simple-qrcode": "^4.2",
"spiral/roadrunner": "^2.8.2", "spiral/roadrunner": "^2.8.2",
"symfony/psr-http-message-bridge": "^2.1", "symfony/psr-http-message-bridge": "^2.1",
"yansongda/laravel-pay": "~3.2.0" "yansongda/laravel-pay": "~3.2.0",
"ext-openssl": "*"
}, },
"require-dev": { "require-dev": {
"beyondcode/laravel-query-detector": "^1.6", "beyondcode/laravel-query-detector": "^1.6",

View File

@ -2,6 +2,11 @@
$cors_origin = explode(',', env('CORS_ORIGINS')); $cors_origin = explode(',', env('CORS_ORIGINS'));
if (env('APP_ENV') === 'local') {
$cors_origin = ['*'];
}
return [ return [
/* /*

View File

@ -23,4 +23,9 @@
'roadrunner' => [ 'roadrunner' => [
'version' => env('ROADRUNNER_VERSION', '2.12.1'), 'version' => env('ROADRUNNER_VERSION', '2.12.1'),
], ],
'supports' => [
'real_name' => [
'code' => env('SUPPORT_REAL_NAME_APP_CODE')
]
],
]; ];

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('modules', function (Blueprint $table) {
$table->decimal('balance', 20, 2)->default(0)->after('name');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('modules', function (Blueprint $table) {
$table->dropColumn('balance');
});
}
};

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->timestamp('real_name_verified_at')->nullable()->comment('实名认证时间')->after('email_verified_at');
$table->string('id_card')->nullable()->comment('身份证号')->after('email_md5');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('real_name_verified_at');
$table->dropColumn('id_card');
});
}
};

View File

@ -4,6 +4,12 @@
@section('content') @section('content')
<h3>{{ $user->name }}</h3> <h3>{{ $user->name }}</h3>
@if ($user->real_name_verified_at)
<span class="badge bg-success">已完成实人验证</span>
@endif
<a href="{{ route('admin.users.show', $user) }}">作为 {{ $user->name }} 登录</a> <a href="{{ route('admin.users.show', $user) }}">作为 {{ $user->name }} 登录</a>
@if ($user->banned_at) @if ($user->banned_at)
@ -20,8 +26,9 @@
<p>邮箱: {{ $user->email }}</p> <p>邮箱: {{ $user->email }}</p>
@if ($user->birthday_at)
<p>生日: {{ $user->birthday_at }}</p> <p>生日: {{ $user->birthday_at }}</p>
@endif
{{-- hosts --}} {{-- hosts --}}
@ -196,4 +203,26 @@
<button type="submit" class="btn btn-primary mt-3">提交</button> <button type="submit" class="btn btn-primary mt-3">提交</button>
</form> </form>
{{-- 实人认证 --}}
<h3 class="mt-3">实人认证</h3>
<p>您应该保持此信息保密。</p>
<form action="{{ route('admin.users.update', $user) }}" method="post">
@csrf
@method('PATCH')
<div class="form-group">
<label for="real_name">姓名</label>
<input type="text" class="form-control" id="real_name" name="real_name" placeholder="姓名"
value="{{ $user->real_name }}" autocomplete="off">
</div>
<div class="form-group">
<label for="id_card">身份证号</label>
<input type="text" class="form-control" id="id_card" name="id_card" placeholder="身份证号"
value="{{ $user->id_card }}" maxlength="18" autocomplete="off">
</div>
<button type="submit" class="btn btn-primary mt-3">提交</button>
</form>
@endsection @endsection

View File

@ -2,4 +2,4 @@
@section('title', __('Server Error')) @section('title', __('Server Error'))
@section('code', '500') @section('code', '500')
@section('message', __('Server Error')) @section('message', $exception->getMessage())

View File

@ -6,12 +6,31 @@
<p>嗨,游客</p> <p>嗨,游客</p>
<p>您需要先登录,才能继续使用 莱云。</p> <p>您需要先登录,才能继续使用 莱云。</p>
<p>如果您继续登录,则代表您已经阅读并同意 <a href="https://www.laecloud.com/tos/" target="_blank" class="text-decoration-underline">服务条款</a></p> <p>如果您继续登录,则代表您已经阅读并同意 <a href="https://www.laecloud.com/tos/" target="_blank"
class="text-decoration-underline">服务条款</a></p>
<a href="{{ route('login') }}" class="btn btn-primary">登录</a> <a href="{{ route('login') }}" class="btn btn-primary">登录</a>
@endguest @endguest
@auth @auth
@if(!auth('web')->user()->real_name_verified_at)
<div>
<div class="alert alert-danger d-flex align-items-center alert-dismissible fade show" role="alert">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Danger:">
<use xlink:href="#exclamation-triangle-fill"/>
</svg>
<div>
<div>
全站实名认证状态已刷新,您需要进行实人认证。
<hr />
您还没有完成实人认证,请尽快完成实人认证。
<br />
<a href="{{ route('real_name.create') }}">点击这里实人认证</a>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</div>
</div>
@endif
@if (session('token')) @if (session('token'))
<p style="color:green">这是新的 Token请妥善保管{{ session('token') }}</p> <p style="color:green">这是新的 Token请妥善保管{{ session('token') }}</p>
@ -45,5 +64,4 @@
</form> </form>
@endauth @endauth
@endsection @endsection

View File

@ -1,10 +0,0 @@
@extends('layouts.app')
@section('title', '请先完成实名认证')
@section('content')
<h1>我们无法让您继续</h1>
<p>您的账户尚未通过实名验证,因此无法使用此功能。</p>
<p>请到<a href="https://www.lae.email/zh-CN/real-name-authentication">这里</a>实名验证,然后再重新登录。</p>
<a href="{{ route('login') }}">重新登录</a>
@endsection

View File

@ -0,0 +1,63 @@
@extends('layouts.app')
@section('content')
@php($user = auth('web')->user())
@if ($user->real_name_verified_at)
<div class="alert alert-success d-flex align-items-center alert-dismissible fade show" role="alert">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Success:">
<use xlink:href="#check-circle-fill"/>
</svg>
<div>
<div>
您已经完成实人认证。
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</div>
@else
@if ($user->balance < 1)
您的余额不足 1 元,无法完成实人认证。
<br/>
请充值后再进行实人认证。
<hr/>
<a href="{{ route('balances.index') }}" class="btn btn-primary">充值</a>
<hr/>
@endif
<p>
由于实人认证接口费用高昂,在实人认证成功后,我们需要收取 1 元左右的手续费。
<br/>
人脸识别需要使用手机摄像头,所以请使用手机浏览器进行实人认证。
</p>
<h3>实人认证</h3>
{{-- if https --}}
@if (request()->isSecure())
<p>您的数据已加密传输。</p>
@else
<p class="text-danger">您的数据未加密传输,请使用 https 访问。</p>
@endif
<form action="{{ route('real_name.store') }}" method="post">
@csrf
<div class="mb-3">
<label for="real_name" class="form-label">姓名</label>
<input required type="text" class="form-control" id="real_name" name="real_name" placeholder="请输入您的姓名"
autocomplete="off" maxlength="6">
</div>
<div class="mb-3">
<label for="id_card" class="form-label">身份证号</label>
<input required type="text" class="form-control" id="id_card" name="id_card"
placeholder="请输入您的身份证号" autocomplete="off" maxlength="18">
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
@endif
@endsection

View File

@ -0,0 +1,13 @@
@extends('layouts.app')
@section('content')
<h3>正在验证你的身份。</h3>
<p>
此过程需要一段时间。
<br/>
如果验证通过,您将不会收到实名提示。
<br />
反之,您依旧会收到实名提示。
</p>
@endsection

12
routes/public.php Normal file
View File

@ -0,0 +1,12 @@
<?php
/**
* 公共路由,不需要登录。这里存放 异步回调 请求路由。
*/
use App\Http\Controllers\Public\RealNameController;
use Illuminate\Support\Facades\Route;
Route::post('real_name/notify', [RealNameController::class, 'verify'])->name('real-name.notify');
Route::match(['post', 'get'], 'real_name/process', [RealNameController::class, 'process'])->name('real-name.process');

View File

@ -2,6 +2,7 @@
use App\Http\Controllers\Web\AuthController; use App\Http\Controllers\Web\AuthController;
use App\Http\Controllers\Web\BalanceController; use App\Http\Controllers\Web\BalanceController;
use App\Http\Controllers\Web\RealNameController;
use App\Http\Controllers\Web\TransferController; use App\Http\Controllers\Web\TransferController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@ -15,27 +16,45 @@
}); });
Route::middleware(['auth', 'banned'])->group(function () { Route::middleware(['auth', 'banned'])->group(
Route::view('banned', 'banned')->name('banned')->withoutMiddleware('banned'); function () {
Route::post('logout', [AuthController::class, 'logout'])->name('logout')->withoutMiddleware('banned'); /* Start 账户区域 */
Route::view('banned', 'banned')->name('banned')->withoutMiddleware('banned');
Route::post('logout', [AuthController::class, 'logout'])->name('logout')->withoutMiddleware('banned');
Route::get('confirm_redirect', [AuthController::class, 'confirm_redirect'])->name('confirm_redirect'); Route::get('confirm_redirect', [AuthController::class, 'confirm_redirect'])->name('confirm_redirect')->middleware('real_named');
Route::post('newToken', [AuthController::class, 'newToken'])->name('newToken'); Route::post('newToken', [AuthController::class, 'newToken'])->name('newToken')->middleware('real_named');
Route::delete('deleteAll', [AuthController::class, 'deleteAll'])->name('deleteAll');
Route::get('transactions', [BalanceController::class, 'transactions'])->name('transactions'); Route::delete('deleteAll', [AuthController::class, 'deleteAll'])->name('deleteAll');
/* End 账户区域 */
Route::resource('balances', BalanceController::class)->except('show');
Route::get('/balances/{balance:order_id}', [BalanceController::class, 'show'])->name('balances.show')->withoutMiddleware('auth');
Route::get('transfer', [TransferController::class, 'index'])->name('transfer'); /* Start 财务 */
Route::post('transfer', [TransferController::class, 'transfer']); Route::get('transactions', [BalanceController::class, 'transactions'])->name('transactions');
});
Route::resource('balances', BalanceController::class)->except('show');
Route::get('/balances/{balance:order_id}', [BalanceController::class, 'show'])->name('balances.show')->withoutMiddleware('auth');
Route::middleware(['real_named'])->group(
function () {
Route::get('transfer', [TransferController::class, 'index'])->name('transfer');
Route::post('transfer', [TransferController::class, 'transfer']);
}
);
/* End 财务 */
/* Start 实名认证 */
Route::get('real_name', [RealNameController::class, 'create'])->name('real_name.create');
Route::post('real_name', [RealNameController::class, 'store'])->name('real_name.store');
/* End 实名认证 */
}
);
Route::view('contact', 'contact')->name('contact'); Route::view('contact', 'contact')->name('contact');
Route::view('not_verified', 'not_verified')->name('not_verified');
Route::match(['get', 'post'], '/balances/notify/{payment}', [BalanceController::class, 'notify'])->name('balances.notify'); Route::match(['get', 'post'], '/balances/notify/{payment}', [BalanceController::class, 'notify'])->name('balances.notify');