驾驶舱大屏

master
lion 3 days ago
parent e27622f0a1
commit 8542c7fcfd

@ -2,10 +2,14 @@
namespace App\Http\Controllers\Admin;
use App\Helpers\StarterResponseCode;
use App\Http\Middleware\Rbac;
use App\Models\Admin;
use App\Models\OperateLog;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Spatie\Permission\Models\Role;
class AdminController extends CommonController
@ -96,9 +100,35 @@ class AdminController extends CommonController
public function store()
{
$displayAccount = request()->boolean('is_display_account');
if ($displayAccount) {
$rbac = app(Rbac::class);
$rbac->guardName = $this->guardName;
if (!$rbac->checkPermissionByUrl()) {
return $this->fail(StarterResponseCode::RBAC_FAIL);
}
$validator = Validator::make(request()->all(), [
'name' => 'required|string|max:255',
'username' => 'required|string|max:255|unique:admins,username',
'password' => 'required|string|min:12|max:72',
'mobile' => 'nullable|digits:11|unique:admins,mobile',
'department_id' => 'nullable|integer',
]);
if ($validator->fails()) {
return $this->fail([StarterResponseCode::START_ERROR_PARAMETER, $validator->errors()->first()]);
}
}
DB::beginTransaction();
try {
$data = (new Admin())->filterRequestColumns(request(), ["id"]);
if ($displayAccount) {
$data = $validator->validated();
$data['password'] = Hash::make($data['password']);
} else {
$data = (new Admin())->filterRequestColumns(request(), ['id', 'is_display_account']);
}
$data['is_display_account'] = $displayAccount;
$model = Admin::create($data);
DB::commit();
// 加日志
@ -114,11 +144,28 @@ class AdminController extends CommonController
public function update()
{
DB::beginTransaction();
$model = Admin::find(request()->id);
if ($model && $model->isDisplayOnly()) {
$rbac = app(Rbac::class);
$rbac->guardName = $this->guardName;
if (!$rbac->checkPermissionByUrl()) {
return $this->fail(StarterResponseCode::RBAC_FAIL);
}
if (request()->filled('password')) {
$validator = Validator::make(request()->all(), ['password' => 'string|min:12|max:72']);
if ($validator->fails()) {
return $this->fail([StarterResponseCode::START_ERROR_PARAMETER, $validator->errors()->first()]);
}
}
}
DB::beginTransaction();
try {
$data = $model->filterRequestColumns(request(), ["id"]);
$data = $model->filterRequestColumns(request(), ['id', 'is_display_account']);
$model->update($data);
if ($model->isDisplayOnly() && $model->wasChanged('password')) {
$model->tokens()->delete();
}
DB::commit();
// 加日志
OperateLog::addLogs($this->getUser(), "更新管理员[{$model->name}]信息成功");
@ -179,6 +226,9 @@ class AdminController extends CommonController
public function setRoles(Request $request)
{
$admin = (new Admin())->find($request->id);
if ($admin && $admin->isDisplayOnly()) {
return $this->fail(StarterResponseCode::RBAC_FAIL);
}
$roles = (new Role())->whereIn("id", (array)$request->role_id)->get();
try {

@ -13,6 +13,7 @@ use App\Models\OperateLog;
use App\Models\Permission;
use App\Models\RoleHasPermission;
use App\Services\AdminSmsChallengeService;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
@ -66,10 +67,9 @@ class AuthController extends Controller
}
$user = $this->guard()->user();
$token = $user->createToken("token")->plainTextToken;
// 加日志
OperateLog::addLogs($user, "用户[{$user->name}]登陆成功");
return $this->respondWithToken($token);
return $this->respondToLogin($user);
}
/**
@ -87,6 +87,9 @@ class AuthController extends Controller
*/
public function me()
{
if ($this->guard()->user()->isDisplayOnly()) {
return $this->success(['display_only' => true]);
}
$user = Admin::with(['department', 'roles'])->find($this->guard()->user()->id);
return $this->success($user);
}
@ -107,6 +110,13 @@ class AuthController extends Controller
public function logout()
{
$user = $this->guard()->user();
if ($user->isDisplayOnly()) {
$user->currentAccessToken()->delete();
$this->guard()->logout();
OperateLog::addLogs($user, "用户[$user->name]退出登陆");
return $this->success(["msg" => "登出成功"])
->withCookie(cookie()->forget(Admin::DISPLAY_COOKIE, Admin::DISPLAY_COOKIE_PATH));
}
$user->tokens()->delete();
$this->guard()->logout();
// 加日志
@ -210,10 +220,9 @@ class AuthController extends Controller
if (!$result['ok']) {
return $this->fail([ResponseCode::ERROR_BUSINESS, $result['message']]);
}
$token = $admin->createToken("token")->plainTextToken;
// 加日志
OperateLog::addLogs($admin, "用户[{$admin->name}]短信登陆成功");
return $this->respondWithToken($token);
return $this->respondToLogin($admin);
}
/**
@ -300,6 +309,23 @@ class AuthController extends Controller
*
* @return \Illuminate\Http\JsonResponse
*/
private function respondToLogin(Admin $admin)
{
if (!$admin->isDisplayOnly()) {
return $this->respondWithToken($admin->createToken('token')->plainTextToken);
}
$admin->tokens()->where('name', 'dashboard')->where('created_at', '<=', now()->subMinutes(Admin::DISPLAY_SESSION_MINUTES))->delete();
$token = $admin->createToken('dashboard', [Admin::DISPLAY_ABILITY])->plainTextToken;
return $this->success(['display_only' => true])
->header('Cache-Control', 'no-store')
->withCookie(cookie(
Admin::DISPLAY_COOKIE, Crypt::encryptString($token), Admin::DISPLAY_SESSION_MINUTES,
Admin::DISPLAY_COOKIE_PATH, null, true, true, false, 'strict'
));
}
protected function respondWithToken($token)
{
$ttl = config("sanctum.expiration");

@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Config;
use Illuminate\Support\Arr;
class DisplayController extends CommonController
{
public function dashboard(OtherController $statistics)
{
$data = $statistics->homeV2Data();
$coverage = json_decode(Config::where('key', 'threeCoverageItems')->orderBy('id', 'desc')->value('value') ?? '[]', true);
return $this->success([
'list' => Arr::only($data['list'], [
'schoolmate_total', 'schoolmate_year', 'course_signs_pass',
'course_signs_pass_unique', 'genban_total', 'company_invested_total',
]),
'suzhou' => collect($data['suzhou'])->map(fn ($area) => Arr::only($area, ['area', 'total', 'total_unique'])),
'country' => collect($data['country'])->map(fn ($area) => Arr::only($area, ['area', 'total'])),
'monthCourses' => collect($data['monthCourses'])->map(fn ($item) => [
'name' => data_get($item, 'title') ?: data_get($item, 'course.name', ''),
'location' => data_get($item, 'address') ?: data_get($item, 'course.address_detail', ''),
'teacher' => collect(data_get($item, 'course.teacher_detail'))->pluck('name')->filter()->implode('、'),
]),
'yearConfigs' => collect($data['yearConfigs'])->map(fn ($year) => [
'name' => data_get($year, 'name'),
'course_periods_total' => data_get($year, 'course_periods_total'),
'course_signs_unique_total' => data_get($year, 'course_signs_unique_total'),
'courseTypes' => collect(data_get($year, 'courseTypes'))->map(fn ($type) => [
'name' => data_get($type, 'name'),
'course_periods_total' => data_get($type, 'course_periods_total'),
'course_signs_total' => data_get($type, 'course_signs_total'),
])->values(),
])->values(),
'coverage' => collect(is_array($coverage) ? $coverage : [])
->filter(fn ($item) => is_array($item))
->map(fn ($item) => Arr::only($item, ['name', 'total', 'complete', 'unit']))
->values(),
]);
}
}

@ -128,12 +128,17 @@ class OtherController extends CommonController
* )
*/
public function homeV2()
{
return $this->success($this->homeV2Data());
}
public function homeV2Data(): array
{
// 生成缓存键(该方法无参数,使用固定键名)
$cacheKey = 'home_v2_data';
// 尝试从缓存获取数据,使用 CourseSign 模型定义的缓存时间
$data = Cache::remember($cacheKey, CourseSign::CACHE_TIME_STATISTICS, function () {
return Cache::remember($cacheKey, CourseSign::CACHE_TIME_STATISTICS, function () {
// 默认开始时间
$start_date = CourseType::START_DATE;
// 默认结束日期一年以后
@ -329,9 +334,6 @@ class OtherController extends CommonController
// 返回数据数组
return compact('list', 'suzhou', 'country', 'monthCourses', 'time_axis', 'article', 'yearConfigs');
});
// 返回响应
return $this->success($data);
}
/**

@ -4,7 +4,10 @@ namespace App\Http\Middleware;
use App\Helpers\ApiResponse;
use App\Helpers\StarterResponseCode;
use App\Models\Admin;
use Closure;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Support\Facades\Crypt;
use Laravel\Sanctum\PersonalAccessToken;
class SanctumJWT
@ -20,12 +23,27 @@ class SanctumJWT
*/
public function handle($request, Closure $next, $guard)
{
$token = $request->bearerToken() ?? $request->token;
$route = $request->route();
$displayRoute = $guard === 'admin' && $route->named('display.*');
$acceptDisplayCookie = $displayRoute || ($guard === 'admin' && $route->named('admin.me', 'admin.logout'));
$displayCookie = $acceptDisplayCookie ? $request->cookie(Admin::DISPLAY_COOKIE) : null;
$token = $request->bearerToken() ?? ($displayRoute ? null : $request->token);
$fromDisplayCookie = $token === null && is_string($displayCookie) && $displayCookie !== '';
if ($fromDisplayCookie) {
try {
$token = Crypt::decryptString($displayCookie);
} catch (DecryptException $e) {
return $this->fail(StarterResponseCode::JWT_AUTH_FAIL);
}
}
if (!is_string($token) || $token === '') return $this->fail(StarterResponseCode::JWT_AUTH_FAIL);
// 这里入口一个debug模式打开时候可以直接用户id做token测试
if (config('app.debug') && stripos($token, "-")) {
list($guard, $id) = explode('-', $token);
auth()->guard($guard)->loginUsingId($id);
if (!$displayRoute && config('app.debug') && preg_match('/^([a-z]+)-(\d+)$/', $token, $parts)) {
if ($parts[1] !== $guard) return $this->fail(StarterResponseCode::JWT_AUTH_FAIL);
$user = auth()->guard($guard)->loginUsingId($parts[2]);
if (!$user || ($user instanceof Admin && $user->isDisplayOnly())) {
return $this->fail(StarterResponseCode::JWT_AUTH_FAIL);
}
return $next($request);
}
$token = PersonalAccessToken::findToken($token);
@ -33,19 +51,25 @@ class SanctumJWT
return $this->fail(StarterResponseCode::JWT_AUTH_FAIL);
}
$tokenable = $token->tokenable;
$guards = config("auth.guards");
foreach ($guards as $k => $v) {
if ($k != $guard) continue;
if (!isset($tokenable->id) || empty($tokenable->id)) continue;
$auth = auth()->guard($k)->loginUsingId($tokenable->id);
if (!$auth) {
continue;
$provider = config("auth.guards.{$guard}.provider");
$model = config("auth.providers.{$provider}.model");
if (!$model || !($tokenable instanceof $model)) {
return $this->fail(StarterResponseCode::JWT_AUTH_FAIL);
}
$isDisplayToken = in_array(Admin::DISPLAY_ABILITY, $token->abilities ?? [], true);
$isDisplayAccount = $tokenable instanceof Admin && $tokenable->isDisplayOnly();
if ($isDisplayToken || $isDisplayAccount || $displayRoute) {
if (!$isDisplayToken || !$isDisplayAccount || !$acceptDisplayCookie) {
return $this->fail(StarterResponseCode::RBAC_FAIL);
}
if (get_class($auth) == get_class($tokenable)) {
return $next($request);
if (!$fromDisplayCookie || $request->has('token')
|| $token->created_at->lte(now()->subMinutes(Admin::DISPLAY_SESSION_MINUTES))) {
return $this->fail(StarterResponseCode::JWT_AUTH_FAIL);
}
}
return $this->fail(StarterResponseCode::JWT_AUTH_FAIL);
auth()->guard($guard)->setUser($tokenable->withAccessToken($token));
$response = $next($request);
return $isDisplayToken ? $response->header('Cache-Control', 'no-store') : $response;
}
}

@ -13,6 +13,11 @@ use Spatie\Permission\Traits\HasRoles;
class Admin extends Authenticatable implements Auditable
{
public const DISPLAY_ABILITY = 'dashboard:display';
public const DISPLAY_COOKIE = 'dashboard_session';
public const DISPLAY_COOKIE_PATH = '/api/admin';
public const DISPLAY_SESSION_MINUTES = 1440;
use SoftDeletes;
use Notifiable;
use HasRoles;
@ -22,6 +27,12 @@ class Admin extends Authenticatable implements Auditable
protected $table = "admins";
private $guard = "admin";
protected $casts = ['is_display_account' => 'boolean'];
public function isDisplayOnly(): bool
{
return (bool) $this->is_display_account;
}
/**
* Get the identifier that will be stored in the subject claim of the JWT.

@ -26,9 +26,9 @@ class OperateLog extends SoftDeletesModel
'admin_id'=>$admin->id,
'department_id'=>$admin->department_id,
'name'=>$name,
'ip'=>$_SERVER["REMOTE_ADDR"],
'ip'=>request()->ip(),
'url'=>request()->url(),
'req_parameter'=>json_encode(request()->all(),JSON_UNESCAPED_UNICODE),
'req_parameter'=>json_encode(request()->except(['password', 'old_password', 'new_password', 'token', 'code']),JSON_UNESCAPED_UNICODE),
'remark'=>$remark
]);
}

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('admins', function (Blueprint $table) {
$table->boolean('is_display_account')->default(false);
});
}
public function down(): void
{
Schema::table('admins', function (Blueprint $table) {
$table->dropColumn('is_display_account');
});
}
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -34,14 +34,6 @@ Route::get('user/qrcode-refresh', [\App\Http\Controllers\Mobile\UserController::
*/
Route::group(["namespace" => "Admin", "prefix" => "admin"], function () {
Route::get('users/index', [\App\Http\Controllers\Admin\UserController::class, "index"]);
Route::get('other/table-fileds', [\App\Http\Controllers\Admin\OtherController::class, "tableFileds"]);
Route::get('other/home', [\App\Http\Controllers\Admin\OtherController::class, "home"]);
Route::get('other/home-v2', [\App\Http\Controllers\Admin\OtherController::class, "homeV2"]);
Route::get('other/courses-home', [\App\Http\Controllers\Admin\OtherController::class, "coursesHome"]);
Route::get('other/courses-home-export', [\App\Http\Controllers\Admin\OtherController::class, "coursesHomeExport"]);
// 验证码登陆
Route::post('auth/sms-login', [\App\Http\Controllers\Admin\AuthController::class, "smsLogin"]);
Route::post('auth/send-sms', [\App\Http\Controllers\Admin\AuthController::class, "sendSms"]);
@ -49,6 +41,15 @@ Route::group(["namespace" => "Admin", "prefix" => "admin"], function () {
Route::get('company/config', [\App\Http\Controllers\Admin\CompanyController::class, "config"]);
Route::group(['middleware' => ['sanctum.jwt:admin']], function () {
Route::get('other/table-fileds', [\App\Http\Controllers\Admin\OtherController::class, 'tableFileds']);
Route::get('other/home', [\App\Http\Controllers\Admin\OtherController::class, 'home']);
Route::get('other/home-v2', [\App\Http\Controllers\Admin\OtherController::class, 'homeV2']);
Route::get('other/courses-home', [\App\Http\Controllers\Admin\OtherController::class, 'coursesHome']);
Route::get('other/courses-home-export', [\App\Http\Controllers\Admin\OtherController::class, 'coursesHomeExport']);
Route::get('display/dashboard', [\App\Http\Controllers\Admin\DisplayController::class, 'dashboard'])->name('display.dashboard');
Route::get('display/alumni-news', [\App\Http\Controllers\Admin\CompanyNewsController::class, 'dashboard'])->name('display.alumni-news');
// 清除缓存
Route::post('other/clear-cache', [\App\Http\Controllers\Admin\OtherController::class, "clearCache"]);

@ -5,34 +5,36 @@ use Illuminate\Support\Facades\Route;
Route::group(["namespace" => "Admin", "prefix" => "api/admin"], function () {
Route::post('auth/login', [\App\Http\Controllers\Admin\AuthController::class, "login"]);
Route::post('auth/logout', [\App\Http\Controllers\Admin\AuthController::class, "logout"]);
Route::post('auth/me', [\App\Http\Controllers\Admin\AuthController::class, "me"]);
Route::post('auth/logout', [\App\Http\Controllers\Admin\AuthController::class, "logout"])->name('admin.logout');
Route::post('auth/me', [\App\Http\Controllers\Admin\AuthController::class, "me"])->name('admin.me');
Route::get('auth/permissions', [\App\Http\Controllers\Admin\AuthController::class, "getPermissions"]);
// 数据参数字典
Route::get("parameter/index", [\App\Http\Controllers\Admin\ParameterController::class, "index"]);
Route::post("parameter/store", [\App\Http\Controllers\Admin\ParameterController::class, "store"]);
// 小程序读取公开参数。
Route::get("parameter/show", [\App\Http\Controllers\Admin\ParameterController::class, "show"]);
Route::post("parameter/save", [\App\Http\Controllers\Admin\ParameterController::class, "save"]);
Route::get("parameter/delete", [\App\Http\Controllers\Admin\ParameterController::class, "delete"]);
Route::get("parameter/detail-delete", [\App\Http\Controllers\Admin\ParameterController::class, "deleteDetail"]);
// 权限
Route::get("auth-node/index", [\App\Http\Controllers\Admin\AuthNodeController::class, "index"]);
Route::post("auth-node/store", [\App\Http\Controllers\Admin\AuthNodeController::class, "store"]);
Route::get("auth-node/show", [\App\Http\Controllers\Admin\AuthNodeController::class, "show"]);
Route::post("auth-node/save", [\App\Http\Controllers\Admin\AuthNodeController::class, "save"]);
Route::get("auth-node/delete", [\App\Http\Controllers\Admin\AuthNodeController::class, "delete"]);
// 其他常用接口
Route::post('upload-file', [\App\Http\Controllers\Admin\UploadController::class, "uploadFile"]);
Route::any('operate-log/index', [\App\Http\Controllers\Admin\OperateLogController::class, "index"]);
// 其他
Route::get('other/admin-user-list', [\App\Http\Controllers\Admin\OtherController::class, "adminUserList"]);
Route::get('other/admin-department-list', [\App\Http\Controllers\Admin\OtherController::class, "adminDepartmentList"]);
Route::group(['middleware' => ['sanctum.jwt:admin']], function () {
// 数据参数字典
Route::get("parameter/index", [\App\Http\Controllers\Admin\ParameterController::class, "index"]);
Route::post("parameter/store", [\App\Http\Controllers\Admin\ParameterController::class, "store"]);
Route::post("parameter/save", [\App\Http\Controllers\Admin\ParameterController::class, "save"]);
Route::get("parameter/delete", [\App\Http\Controllers\Admin\ParameterController::class, "delete"]);
Route::get("parameter/detail-delete", [\App\Http\Controllers\Admin\ParameterController::class, "deleteDetail"]);
// 权限
Route::get("auth-node/index", [\App\Http\Controllers\Admin\AuthNodeController::class, "index"]);
Route::post("auth-node/store", [\App\Http\Controllers\Admin\AuthNodeController::class, "store"]);
Route::get("auth-node/show", [\App\Http\Controllers\Admin\AuthNodeController::class, "show"]);
Route::post("auth-node/save", [\App\Http\Controllers\Admin\AuthNodeController::class, "save"]);
Route::get("auth-node/delete", [\App\Http\Controllers\Admin\AuthNodeController::class, "delete"]);
// 其他常用接口
Route::post('upload-file', [\App\Http\Controllers\Admin\UploadController::class, "uploadFile"]);
Route::any('operate-log/index', [\App\Http\Controllers\Admin\OperateLogController::class, "index"]);
// 其他
Route::get('other/admin-user-list', [\App\Http\Controllers\Admin\OtherController::class, "adminUserList"]);
Route::get('other/admin-department-list', [\App\Http\Controllers\Admin\OtherController::class, "adminDepartmentList"]);
// 自定义表单
Route::get("custom-form/index", [\App\Http\Controllers\Admin\CustomFormController::class, "index"]);
Route::get("custom-form/real-table-index", [\App\Http\Controllers\Admin\CustomFormController::class, "realTableindex"]);

@ -0,0 +1,128 @@
<?php
namespace Tests\Feature;
use App\Models\Admin;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;
class DisplayDashboardAccessTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
config([
'app.key' => 'base64:' . base64_encode(str_repeat('a', 32)),
'database.default' => 'sqlite',
'database.connections.sqlite.database' => ':memory:',
'cache.default' => 'array',
'session.driver' => 'array',
'audit.enabled' => false,
]);
DB::purge('sqlite');
app('migration.repository')->createRepository();
$migrations = [
'2019_12_14_000001_create_personal_access_tokens_table.php',
'2022_03_30_173108_create_permission_tables.php',
'2022_03_30_175453_create_admins_table.php',
'2022_04_06_102925_add_api_prefix_to_permissions.php',
'2022_08_18_105251_create_operate_logs_table.php',
'2024_07_17_135941_create_configs_table.php',
'2026_11_11_000001_add_display_account_to_admins.php',
];
app('migrator')->run(array_map(fn ($file) => database_path('migrations/' . $file), $migrations));
Cache::put('home_v2_data', [
'list' => ['schoolmate_total' => 42, 'internal_count' => 100],
'suzhou' => [],
'country' => [],
'monthCourses' => [[
'title' => '研修班',
'course' => ['teacher_detail' => [['name' => '带班老师', 'mobile' => '123456']]],
]],
'yearConfigs' => [],
'article' => ['internal_article'],
]);
}
public function test_display_login_issues_a_read_only_cookie_and_logout_revokes_it(): void
{
$admin = Admin::create([
'username' => 'tv',
'name' => '电视驾驶舱',
'password' => Hash::make('long-test-password'),
'is_display_account' => true,
]);
$login = $this->withServerVariables(['REMOTE_ADDR' => '127.0.0.1'])
->postJson('/api/admin/auth/login', ['username' => 'tv', 'password' => 'long-test-password']);
$login->assertExactJson(['display_only' => true]);
$cookie = $login->getCookie(Admin::DISPLAY_COOKIE, false);
$this->assertTrue($cookie->isHttpOnly());
$this->assertTrue($cookie->isSecure());
$this->assertSame('strict', $cookie->getSameSite());
$this->assertStringNotContainsString('long-test-password', DB::table('operate_logs')->value('req_parameter'));
$this->withUnencryptedCookie(Admin::DISPLAY_COOKIE, $cookie->getValue())->withCredentials();
$this->postJson('/api/admin/auth/me')->assertExactJson(['display_only' => true]);
$this->getJson('/api/admin/display/dashboard')
->assertJsonPath('list.schoolmate_total', 42)
->assertJsonPath('monthCourses.0.teacher', '带班老师')
->assertJsonMissingPath('list.internal_count')
->assertJsonMissingPath('monthCourses.0.course')
->assertJsonMissingPath('article')
->assertHeader('Cache-Control', 'no-store, private');
$this->getJson('/api/admin/configs/index')->assertJsonPath('errcode', 40001);
$this->getJson('/api/admin/other/courses-home-export')->assertJsonPath('errcode', 40001);
$this->postJson('/api/admin/auth-node/save', [])->assertJsonPath('errcode', 40001);
$this->travel(Admin::DISPLAY_SESSION_MINUTES + 1)->minutes();
$this->getJson('/api/admin/display/dashboard')->assertJsonPath('errcode', 40001);
$this->travelBack();
$this->postJson('/api/admin/auth/logout')->assertCookieExpired(Admin::DISPLAY_COOKIE);
$this->assertSame(0, $admin->tokens()->count());
$this->getJson('/api/admin/display/dashboard')->assertJsonPath('errcode', 40001);
}
public function test_legacy_statistics_require_admin_auth_and_display_rejects_bearer_tokens(): void
{
foreach (['home', 'home-v2', 'courses-home-export'] as $path) {
$this->getJson('/api/admin/other/' . $path)->assertJsonPath('errcode', 40001);
}
$admin = Admin::create(['name' => '后台用户']);
$token = $admin->createToken('token')->plainTextToken;
$this->withToken($token)->getJson('/api/admin/other/home-v2')->assertJsonPath('list.schoolmate_total', 42);
$this->withToken($token)->getJson('/api/admin/display/dashboard')->assertJsonPath('errcode', 40003);
$display = Admin::create(['name' => '大屏用户', 'is_display_account' => true]);
$scopedToken = $display->createToken('dashboard', [Admin::DISPLAY_ABILITY])->plainTextToken;
$this->withToken($scopedToken)->postJson('/api/admin/admin/save', [])->assertJsonPath('errcode', 40003);
$this->withToken($scopedToken)->getJson('/api/admin/display/dashboard')->assertJsonPath('errcode', 40001);
}
public function test_only_user_managers_can_create_display_accounts(): void
{
$manager = Admin::create(['name' => '用户管理员']);
$token = $manager->createToken('token')->plainTextToken;
$payload = [
'name' => '大厅电视',
'username' => 'lobby-tv',
'password' => 'long-test-password',
'is_display_account' => true,
];
$this->withToken($token)->postJson('/api/admin/admin/save', $payload)->assertJsonPath('errcode', 40003);
$permission = Permission::create(['name' => '用户管理', 'guard_name' => 'admin']);
DB::table('permissions')->where('id', $permission->id)->update(['api_prefix' => 'api/admin/admin']);
$manager->givePermissionTo($permission);
$this->withToken($token)->postJson('/api/admin/admin/save', $payload)->assertJsonPath('is_display_account', true);
$display = Admin::where('username', 'lobby-tv')->firstOrFail();
$this->assertTrue($display->isDisplayOnly());
$this->assertTrue(Hash::check($payload['password'], $display->password));
}
}
Loading…
Cancel
Save