完善生产查询与登录权限配置

This commit is contained in:
2026-09-03 19:56:19 +08:00
parent 793f140a45
commit 02c8fb7021
438 changed files with 1595 additions and 573 deletions
+13 -7
View File
@@ -59,6 +59,8 @@ class AuthService extends BaseService
// 更新登录信息
$this->updateLoginInfo($member);
$channel = strtolower((string)request()->header('channel', 'pc'));
$clientType = $channel === 'app' ? 'app' : 'web';
$userInfo = [
'id' => $member->id,
'username' => $member->username,
@@ -66,13 +68,14 @@ class AuthService extends BaseService
'avatar' => $member->avatar,
'phone' => $member->phone,
'email' => $member->email,
'client' => 'web',
'access_exp' => 7200,
'client' => $clientType,
'access_exp' => 3600,
'refresh_exp' => 604800,
];
// 使用新的 JwtToken 生成 token
$jwt = new JwtToken();
$tokenObj = $jwt->generate((string)$member->id, 'api', $userInfo);
$tokenObj = $jwt->generate((string)$member->id, $clientType, $userInfo);
$userInfo['access_exp'] = $tokenObj->expiresIn;
$token = [
'access_token' => $tokenObj->accessToken,
'refresh_token' => $tokenObj->refreshToken,
@@ -110,6 +113,8 @@ class AuthService extends BaseService
$this->updateLoginInfo($member);
// 生成token
$channel = strtolower((string)request()->header('channel', 'pc'));
$clientType = $channel === 'app' ? 'app' : 'web';
$userInfo = [
'id' => $member->id,
'username' => $member->username,
@@ -117,13 +122,14 @@ class AuthService extends BaseService
'avatar' => $member->avatar,
'phone' => $member->phone,
'email' => $member->email,
'client' => 'web',
'access_exp' => 7200,
'client' => $clientType,
'access_exp' => 3600,
'refresh_exp' => 604800,
];
// 使用新的 JwtToken 生成 token
$jwt = new JwtToken();
$tokenObj = $jwt->generate((string)$member->id, 'api', $userInfo);
$tokenObj = $jwt->generate((string)$member->id, $clientType, $userInfo);
$userInfo['access_exp'] = $tokenObj->expiresIn;
$token = [
'access_token' => $tokenObj->accessToken,
'refresh_token' => $tokenObj->refreshToken,
@@ -702,4 +708,4 @@ HTML;
throw new \Exception('登录已过期', 401);
}
}
}
}
+35 -7
View File
@@ -21,6 +21,7 @@ use Firebase\JWT\ExpiredException;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\SignatureInvalidException;
use app\model\system\Config;
use UnexpectedValueException;
class JwtToken
@@ -126,7 +127,7 @@ class JwtToken
$jti = $this->generateJti();
// 生成 Access Token
$accessTtl = $this->config['ttl']['access'] ?? 7200;
$accessTtl = $this->getConfiguredTtl('access', $clientType);
$accessPayload = $this->buildPayload(
id: $id,
client: $clientType,
@@ -137,7 +138,7 @@ class JwtToken
$accessToken = $this->makeToken($accessPayload);
// 生成 Refresh Token
$refreshTtl = $this->config['ttl']['refresh'] ?? 604800;
$refreshTtl = $this->getConfiguredTtl('refresh', $clientType);
$refreshJti = $this->generateJti();
$refreshPayload = $this->buildPayload(
id: $id,
@@ -156,7 +157,7 @@ class JwtToken
'extra' => $extraData,
'login_mode' => $loginMode,
'created_at' => time(),
], $accessTtl);
], max($accessTtl, $refreshTtl));
// 创建 Token 对象
$fullPayload = array_merge($accessPayload, ['id' => $id]);
@@ -217,7 +218,7 @@ class JwtToken
$loginMode = $this->config['login_mode'] ?? $oldTokenInfo['login_mode'] ?? LoginMode::MULTI->value;
// 将旧 access_token 和 refresh_token 加入黑名单
$blacklistTtl = $this->config['ttl']['refresh'] ?? 604800;
$blacklistTtl = $this->getConfiguredTtl('refresh', $clientType);
if ($oldAccessJti !== '') {
$this->blacklistStorage->add($oldAccessJti, $blacklistTtl);
$this->tokenStorage->delete($oldAccessJti);
@@ -288,7 +289,7 @@ class JwtToken
return null;
}
$accessTtl = $this->config['ttl']['access'] ?? 7200;
$accessTtl = $this->getConfiguredTtl('access', $payload['client'] ?? '');
$this->currentToken = Token::create($accessToken, '', $accessTtl, $payload);
return $this->currentToken;
@@ -739,6 +740,33 @@ class JwtToken
/**
* 构建 Token 载荷
*/
protected function getConfiguredTtl(string $type, string $clientType): int
{
$defaults = $this->config['ttl'] ?? ['access' => 3600, 'refresh' => 604800];
$default = (int)($defaults[$type] ?? ($type === 'access' ? 3600 : 604800));
$content = Config::query()->where('code', 'auth_token_setting')->where('enabled', 1)->value('content');
if (is_string($content)) {
$decoded = json_decode($content, true);
$content = json_last_error() === JSON_ERROR_NONE ? $decoded : null;
}
if (!is_array($content)) {
return $default;
}
$client = strtolower($clientType);
$key = match ($client) {
'admin' => $type === 'refresh' ? 'admin_refresh_ttl' : 'admin_access_ttl',
'app', 'mobile' => $type === 'refresh' ? 'app_refresh_ttl' : 'app_access_ttl',
default => $type === 'refresh' ? 'web_refresh_ttl' : 'web_access_ttl',
};
if ($type === 'refresh' && !array_key_exists($key, $content)) {
$key = 'refresh_ttl';
}
$value = (int)($content[$key] ?? $default);
$minimum = $type === 'refresh' ? 300 : 60;
return $value >= $minimum ? $value : $default;
}
protected function buildPayload(
string $id,
string $client,
@@ -749,8 +777,8 @@ class JwtToken
{
$now = time();
$ttl = match ($type) {
TokenType::ACCESS => $this->config['ttl']['access'] ?? 7200,
TokenType::REFRESH => $this->config['ttl']['refresh'] ?? 604800,
TokenType::ACCESS => $this->getConfiguredTtl('access', $client),
TokenType::REFRESH => $this->getConfiguredTtl('refresh', $client),
};
$payload = [
+1
View File
@@ -12,6 +12,7 @@ enum ClientType: string
case API = 'api';
case WEB = 'web';
case MOBILE = 'mobile';
case APP = 'app';
case MINI = 'mini';
/**
@@ -679,13 +679,14 @@ final class DataEntryController extends Base
#[OA\Get(path: '/produce/query/records', summary: '查询生产记录', tags: ['produce-web-query'])]
#[DataResponse(description: '查询成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
public function queryRecords(Request $request): Response
{
$recordType = trim((string) $request->input('record_type', 'scan'));
$orderNo = trim((string) $request->input('order_no', ''));
$keyword = trim((string) $request->input('keyword', ''));
$userCode = trim((string) $request->input('user_code', ''));
$processId = (int) $request->input('process_id', 0);
$stepId = (int) $request->input('step_id', 0);
$status = $request->input('status', '');
$scanResult = $request->input('scan_result', '');
$page = max(1, (int) $request->input('page', 1));
@@ -707,6 +708,8 @@ final class DataEntryController extends Base
if ($recordType === 'scan') {
$query->when($orderNo !== '', fn ($builder) => $builder->where('order_no', $orderNo));
$query->when($userCode !== '', fn ($builder) => $builder->where('user_code', $userCode));
$query->when($processId > 0, fn ($builder) => $builder->where('process_id', $processId));
$query->when($stepId > 0, fn ($builder) => $builder->where('step_id', $stepId));
$query->when($status !== '' && $status !== null, fn ($builder) => $builder->where('status_id', (int) $status));
$query->when($scanResult !== '' && $scanResult !== null, fn ($builder) => $builder->where('scan_result', (int) $scanResult));
$query->when($keyword !== '', fn ($builder) => $builder->where(function ($nested) use ($keyword) {
@@ -721,6 +724,8 @@ final class DataEntryController extends Base
} elseif ($recordType === 'product') {
$query->when($orderNo !== '', fn ($builder) => $builder->where('process_order_no', $orderNo));
$query->when($userCode !== '', fn ($builder) => $builder->where('user_code', $userCode));
$query->when($processId > 0, fn ($builder) => $builder->where('process_id', $processId));
$query->when($stepId > 0, fn ($builder) => $builder->where('step_id', $stepId));
$query->when($status !== '' && $status !== null, fn ($builder) => $builder->where('status_id', (int) $status));
$query->when($scanResult !== '' && $scanResult !== null, fn ($builder) => $builder->where('scan_result', (int) $scanResult));
$query->when($keyword !== '', fn ($builder) => $builder->where(function ($nested) use ($keyword) {
@@ -736,6 +741,8 @@ final class DataEntryController extends Base
} elseif ($recordType === 'exception') {
$query->when($orderNo !== '', fn ($builder) => $builder->where('order_no', $orderNo));
$query->when($userCode !== '', fn ($builder) => $builder->where('user_code', $userCode));
$query->when($processId > 0, fn ($builder) => $builder->where('process_id', $processId));
$query->when($stepId > 0, fn ($builder) => $builder->where('step_id', $stepId));
$query->when($status !== '' && $status !== null, fn ($builder) => $builder->where('status', (int) $status));
$query->when($keyword !== '', fn ($builder) => $builder->where(function ($nested) use ($keyword) {
$nested->where('order_no', 'like', "%{$keyword}%")
@@ -751,6 +758,8 @@ final class DataEntryController extends Base
$query->orderByDesc('reported_at')->orderByDesc('id');
} else {
$query->when($orderNo !== '', fn ($builder) => $builder->where('order_no', $orderNo));
$query->when($processId > 0, fn ($builder) => $builder->where('process_id', $processId));
$query->when($stepId > 0, fn ($builder) => $builder->where('step_id', $stepId));
$query->when($keyword !== '', fn ($builder) => $builder->where(function ($nested) use ($keyword) {
$nested->where('repair_no', 'like', "%{$keyword}%")
->orWhere('order_no', 'like', "%{$keyword}%")
@@ -772,7 +781,6 @@ final class DataEntryController extends Base
#[OA\Get(path: '/produce/query/timeline', summary: '查询订单生产时间线', tags: ['produce-web-query'])]
#[DataResponse(description: '查询成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: '公共接口')]
public function queryTimeline(Request $request): Response
{
$orderNo = trim((string) $request->input('order_no', ''));
@@ -936,9 +944,12 @@ final class DataEntryController extends Base
return Json::fail('用户编码不存在');
}
$repairFlow = $this->getRepairFlowNodeNames();
try {
$record = Db::transaction(function () use ($exceptionId, $repairResult, $categoryIds, $parts, $images, $operationFaultSteps, $responsibleOperators, $data, $member, $repairRecordService) {
$record = Db::transaction(function () use ($exceptionId, $repairResult, $categoryIds, $parts, $images, $operationFaultSteps, $responsibleOperators, $data, $member, $repairRecordService, $repairFlow) {
$exception = ExceptionRecord::query()
->with(['process', 'step'])
->lockForUpdate()
->whereKey($exceptionId)
->whereIn('status', [0, 1])
@@ -1062,6 +1073,10 @@ final class DataEntryController extends Base
'exception_reason' => (string) $exception->reason_name,
'exception_issue' => (string) ($exception->issue_name ?? ''),
'order_no' => (string) $exception->order_no,
'process_id' => $repairFlow['process_id'],
'step_id' => $repairFlow['step_id'],
'process_name' => $repairFlow['process_name'],
'step_name' => $repairFlow['step_name'],
'category_id' => $repairResult === 'assembly_mismatch' ? $categoryIds[0] : $primaryFault?->category_id,
'category_ids' => $repairResult === 'assembly_mismatch' ? $categoryIds : null,
'fault_description_id' => $primaryFault?->id,
@@ -1316,8 +1331,8 @@ final class DataEntryController extends Base
'record_type_name' => '维修记录',
'order_no' => (string) $item->order_no,
'product_code' => (string) $item->product_code,
'process_name' => $this->getRepairFlowNodeNames()['process_name'],
'step_name' => $this->getRepairFlowNodeNames()['step_name'],
'process_name' => (string) ($item->process_name ?: '历史数据未记录'),
'step_name' => (string) ($item->step_name ?: '历史数据未记录'),
'status_name' => '已维修',
'result_name' => (string) $item->repair_result_name,
'operator' => (string) $item->operator,
@@ -1349,8 +1364,10 @@ final class DataEntryController extends Base
$step = $stepId > 0 ? ProcessStep::query()->withTrashed()->find($stepId) : null;
return $this->repairFlowCache = [
'process_name' => (string) ($process?->process_name ?? ''),
'step_name' => (string) ($step?->step_name ?? ''),
'process_id' => $processId > 0 ? $processId : null,
'step_id' => $stepId > 0 ? $stepId : null,
'process_name' => (string) ($process?->process_name ?? '维修'),
'step_name' => (string) ($step?->step_name ?? '维修'),
];
}
@@ -86,7 +86,6 @@ final class DataEntryController
#[OA\Get(path: '/produce/query/records', summary: 'APP 查询生产记录', tags: ['produce-app'])]
#[DataResponse(description: '查询成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function queryRecords(Request $request): Response
{
return $this->delegate->queryRecords($request);
@@ -94,7 +93,6 @@ final class DataEntryController
#[OA\Get(path: '/produce/query/timeline', summary: 'APP 查询订单生产时间线', tags: ['produce-app'])]
#[DataResponse(description: '查询成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function queryTimeline(Request $request): Response
{
return $this->delegate->queryTimeline($request);
@@ -224,4 +222,3 @@ final class DataEntryController
return $this->delegate->productRecord($request);
}
}
@@ -21,7 +21,7 @@ class RepairRecord extends BaseModel
protected $with = ['exception'];
protected $fillable = [
'id', 'repair_no', 'exception_id', 'exception_reason', 'exception_issue', 'order_no', 'product_code', 'product_name', 'category_id', 'category_ids', 'category_name', 'category_names',
'id', 'repair_no', 'exception_id', 'exception_reason', 'exception_issue', 'order_no', 'product_code', 'product_name', 'process_id', 'step_id', 'process_name', 'step_name', 'category_id', 'category_ids', 'category_name', 'category_names',
'fault_description_id', 'fault_description', 'repair_result', 'parts', 'images', 'operation_fault_steps', 'responsible_operators', 'operator', 'repaired_at',
'remark', 'created_by', 'updated_by', 'created_at', 'updated_at', 'deleted_at',
];
@@ -33,6 +33,8 @@ class RepairRecord extends BaseModel
'images' => 'array',
'operation_fault_steps' => 'array',
'responsible_operators' => 'array',
'process_id' => 'integer',
'step_id' => 'integer',
'repaired_at' => 'integer',
'created_at' => 'integer',
'updated_at' => 'integer',
@@ -91,14 +91,20 @@ class WorkStatisticsService
$connection = $model->getConnection();
$table = $model->getTable();
$exceptionTable = $exceptionModel->getTable();
$flow = $this->getRepairFlow();
$query = $connection->table($table)
->leftJoin($exceptionTable, $exceptionTable . '.id', '=', $table . '.exception_id')
->whereBetween($table . '.repaired_at', [$start, $end])
->whereNull($table . '.deleted_at');
if (!$this->matchesRepairFlow($params, $flow)) {
$query->whereRaw('1 = 0');
foreach (['process_id', 'step_id'] as $field) {
if (($params[$field] ?? '') !== '') {
$query->where($table . '.' . $field, $params[$field]);
}
}
foreach (['process_name', 'step_name'] as $field) {
if (($params[$field] ?? '') !== '') {
$query->where($table . '.' . $field, 'like', '%' . $params[$field] . '%');
}
}
if (($params['user_code'] ?? '') !== '') {
$query->where($exceptionTable . '.user_code', 'like', '%' . $params['user_code'] . '%');
@@ -109,8 +115,8 @@ class WorkStatisticsService
return $query->select([$exceptionTable . '.user_code', $table . '.operator'])
->selectRaw($periodExpression($connection->getTablePrefix() . $table . '.repaired_at') . ' as period')
->selectRaw('? as process_name', [$flow['process_name']])
->selectRaw('? as step_name', [$flow['step_name']])
->selectRaw("COALESCE(NULLIF({$connection->getTablePrefix()}{$table}.process_name, ''), '历史数据未记录') as process_name")
->selectRaw("COALESCE(NULLIF({$connection->getTablePrefix()}{$table}.step_name, ''), '历史数据未记录') as step_name")
->selectRaw('COUNT(*) as amount')
->groupBy('user_code', 'operator', 'process_name', 'step_name', 'period');
}
@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return new class {
public function up(Builder $schema): void
{
$table = 'produce_repair_record';
if (!$schema->hasTable($table)) {
return;
}
$hasProcessId = $schema->hasColumn($table, 'process_id');
$hasStepId = $schema->hasColumn($table, 'step_id');
$hasProcessName = $schema->hasColumn($table, 'process_name');
$hasStepName = $schema->hasColumn($table, 'step_name');
$schema->table($table, function (Blueprint $blueprint) use ($hasProcessId, $hasStepId, $hasProcessName, $hasStepName): void {
if (!$hasProcessId) {
$blueprint->bigInteger('process_id')->nullable()->comment('维修工序ID快照');
$blueprint->index('process_id', 'idx_repair_record_process_id');
}
if (!$hasStepId) {
$blueprint->bigInteger('step_id')->nullable()->comment('维修步骤ID快照');
$blueprint->index('step_id', 'idx_repair_record_step_id');
}
if (!$hasProcessName) {
$blueprint->string('process_name', 100)->nullable()->comment('维修工序名称快照');
}
if (!$hasStepName) {
$blueprint->string('step_name', 100)->nullable()->comment('维修步骤名称快照');
}
});
$this->backfillSnapshots($schema);
}
private function backfillSnapshots(Builder $schema): void
{
$connection = $schema->getConnection();
if (!$schema->hasTable('produce_exception_record')) {
$connection->table('produce_repair_record')->update([
'process_name' => '历史数据未记录',
'step_name' => '历史数据未记录',
]);
return;
}
$records = $connection->table('produce_repair_record')->get(['id', 'exception_id']);
foreach ($records as $record) {
$exception = $connection->table('produce_exception_record')
->where('id', $record->exception_id)
->first(['process_id', 'step_id']);
$processId = $exception?->process_id;
$stepId = $exception?->step_id;
$processName = $processId
? (string) ($connection->table('produce_process')->where('id', $processId)->value('process_name') ?? '')
: '';
$stepName = $stepId
? (string) ($connection->table('produce_process_step')->where('id', $stepId)->value('step_name') ?? '')
: '';
$connection->table('produce_repair_record')->where('id', $record->id)->update([
'process_id' => $processId,
'step_id' => $stepId,
'process_name' => $processName ?: '历史数据未记录',
'step_name' => $stepName ?: '历史数据未记录',
]);
}
}
public function down(Builder $schema): void
{
$table = 'produce_repair_record';
if (!$schema->hasTable($table)) {
return;
}
$schema->table($table, function (Blueprint $blueprint) use ($schema): void {
if ($schema->hasColumn('produce_repair_record', 'process_id')) {
$blueprint->dropIndex('idx_repair_record_process_id');
$blueprint->dropColumn('process_id');
}
if ($schema->hasColumn('produce_repair_record', 'step_id')) {
$blueprint->dropIndex('idx_repair_record_step_id');
$blueprint->dropColumn('step_id');
}
if ($schema->hasColumn('produce_repair_record', 'process_name')) {
$blueprint->dropColumn('process_name');
}
if ($schema->hasColumn('produce_repair_record', 'step_name')) {
$blueprint->dropColumn('step_name');
}
});
}
};
@@ -152,6 +152,8 @@ export const crudSchema = (): CrudSchema => ({
{ field: "order_product", title: "订单号/商品编码" },
{ field: "exception_type_name", title: "异常分类" },
{ field: "exception_reason", title: "异常原因" },
{ field: "process_name", title: "维修工序" },
{ field: "step_name", title: "维修步骤" },
{ field: "part_category", title: "物料分类" },
{ field: "part_code", title: "物料编码" },
{ field: "part_name", title: "更换备件" },
@@ -221,6 +223,8 @@ export const crudSchema = (): CrudSchema => ({
},
{ prop: "exception_type_name", label: "异常分类", minWidth: 120, cellStyle: { whiteSpace: "nowrap" } },
{ prop: "exception_reason", label: "异常原因", minWidth: 180, cellStyle: { whiteSpace: "nowrap" } },
{ prop: "process_name", label: "维修工序", minWidth: 140, cellStyle: { whiteSpace: "nowrap" } },
{ prop: "step_name", label: "维修步骤", minWidth: 140, cellStyle: { whiteSpace: "nowrap" } },
{
prop: "part_category",
label: "物料分类",
@@ -380,6 +384,22 @@ export const crudSchema = (): CrudSchema => ({
ifDetail: true,
colSpan: 24,
},
{
component: "Input",
prop: "process_name",
label: "维修工序",
componentProps: { readonly: true },
ifDetail: true,
colSpan: 12,
},
{
component: "Input",
prop: "step_name",
label: "维修步骤",
componentProps: { readonly: true },
ifDetail: true,
colSpan: 12,
},
{
component: "Input",
prop: "fault_descriptions",
+1
View File
@@ -229,6 +229,7 @@ return [
'content' => [
"site_name" => "",
"site_url" => "",
"require_login" => 1,
"seo_title" => "",
"seo_keywords" => "",
"seo_description" => "",