refactor: kb & station & terminal

This commit is contained in:
2026-03-23 20:17:17 +08:00
parent 63ea2686e1
commit b74ba1a3f8
81 changed files with 1016 additions and 2492 deletions

View File

@@ -2,7 +2,6 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -24,25 +23,14 @@ class Document extends Model
'file_name',
'file_size',
'mime_type',
'type',
'group_id',
'uploaded_by',
'description',
'markdown_path',
'markdown_preview',
'conversion_status',
'conversion_error',
'knowledge_base_id',
];
/**
* 获取文档所属的分组
*/
public function group(): BelongsTo
{
return $this->belongsTo(Group::class);
}
/**
* 获取文档所属的知识库
*/
@@ -67,57 +55,9 @@ class Document extends Model
return $this->hasMany(DownloadLog::class);
}
/**
* 查询作用域:获取用户可访问的文档
* 包含全局文档和用户分组的专用文档,排除其他分组的专用文档
*
* @param Builder $query
* @param User $user
* @return Builder
*/
public function scopeAccessibleBy(Builder $query, User $user): Builder
{
// 获取用户所属的所有分组 ID
$userGroupIds = $user->groups()->pluck('groups.id')->toArray();
return $query->where(function (Builder $query) use ($userGroupIds) {
// 包含所有全局文档
$query->where('type', 'global')
// 或者包含用户所属分组的专用文档
->orWhere(function (Builder $query) use ($userGroupIds) {
$query->where('type', 'dedicated')
->whereIn('group_id', $userGroupIds);
});
});
}
/**
* 查询作用域:仅获取全局文档
*
* @param Builder $query
* @return Builder
*/
public function scopeGlobal(Builder $query): Builder
{
return $query->where('type', 'global');
}
/**
* 查询作用域:仅获取专用文档
*
* @param Builder $query
* @return Builder
*/
public function scopeDedicated(Builder $query): Builder
{
return $query->where('type', 'dedicated');
}
/**
* 获取可搜索的数组数据
* 用于 Meilisearch 索引
*
* @return array
*/
public function toSearchableArray(): array
{
@@ -127,8 +67,6 @@ class Document extends Model
'file_name' => $this->file_name,
'description' => $this->description,
'markdown_content' => $this->getMarkdownContent(),
'type' => $this->type,
'group_id' => $this->group_id,
'knowledge_base_id' => $this->knowledge_base_id,
'uploaded_by' => $this->uploaded_by,
'created_at' => $this->created_at?->timestamp,
@@ -138,8 +76,6 @@ class Document extends Model
/**
* 判断文档是否应该被索引
* 只有转换完成的文档才会被索引
*
* @return bool
*/
public function shouldBeSearchable(): bool
{
@@ -149,8 +85,6 @@ class Document extends Model
/**
* 获取完整的 Markdown 内容
* 从文件系统读取 Markdown 文件
*
* @return string|null
*/
public function getMarkdownContent(): ?string
{
@@ -163,7 +97,6 @@ class Document extends Model
return Storage::disk('markdown')->get($this->markdown_path);
}
} catch (\Exception $e) {
// 记录错误但不抛出异常
\Log::warning('Failed to read markdown content', [
'document_id' => $this->id,
'markdown_path' => $this->markdown_path,
@@ -176,8 +109,6 @@ class Document extends Model
/**
* 检查文档是否已转换为 Markdown
*
* @return bool
*/
public function hasMarkdown(): bool
{

View File

@@ -2,13 +2,11 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class DownloadLog extends Model
{
use HasFactory;
/**
* 表示模型不使用 created_at updated_at 时间戳
* 因为我们使用自定义的 downloaded_at 字段

View File

@@ -1,53 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Group extends Model
{
use HasFactory;
/**
* 可批量赋值的属性
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'description',
];
/**
* 模型的启动方法
* 注册模型事件监听器
*/
protected static function boot()
{
parent::boot();
// 监听分组删除事件
static::deleting(function (Group $group) {
// 将该分组的所有专用文档的 group_id 设置为 null孤立状态
$group->documents()->update(['group_id' => null]);
});
}
/**
* 获取分组的所有用户
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
/**
* 获取分组的所有文档
*/
public function documents(): HasMany
{
return $this->hasMany(Document::class);
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -47,12 +48,9 @@ class Guide extends Model
return $this->belongsTo(User::class, 'created_by');
}
public function terminals()
public function stations()
{
return $this->belongsToMany(Terminal::class, 'terminal_guides')
->withPivot('priority')
->withTimestamps()
->orderBy('priority');
return $this->belongsToMany(Station::class);
}
public function scopePublished($query)
@@ -65,6 +63,23 @@ class Guide extends Model
return $query->where('category', $category);
}
/**
* 按用户线站过滤:全局 Guide无线站关联+ 用户线站关联的 Guide
*/
public function scopeAccessibleBy(Builder $query, User $user): Builder
{
if (!$user->hasStationRestriction()) {
return $query;
}
$stationIds = $user->getAccessibleStationIds();
return $query->where(function (Builder $q) use ($stationIds) {
$q->whereDoesntHave('stations')
->orWhereHas('stations', fn ($sq) => $sq->whereIn('stations.id', $stationIds));
});
}
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()

View File

@@ -2,6 +2,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -30,4 +31,31 @@ class KnowledgeBase extends Model
{
return $this->hasMany(Document::class);
}
/**
* 获取知识库关联的线站
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function stations()
{
return $this->belongsToMany(Station::class);
}
/**
* 按用户线站过滤:全局 KB无线站关联+ 用户线站关联的 KB
*/
public function scopeAccessibleBy(Builder $query, User $user): Builder
{
if (!$user->hasStationRestriction()) {
return $query;
}
$stationIds = $user->getAccessibleStationIds();
return $query->where(function (Builder $q) use ($stationIds) {
$q->whereDoesntHave('stations')
->orWhereHas('stations', fn ($sq) => $sq->whereIn('stations.id', $stationIds));
});
}
}

48
app/Models/Station.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Station extends Model
{
use HasFactory;
protected $fillable = [
'name',
'description',
];
protected static function boot()
{
parent::boot();
static::deleting(function (Station $station) {
$station->terminals()->update(['station_id' => null]);
});
}
public function terminals(): HasMany
{
return $this->hasMany(Terminal::class);
}
public function knowledgeBases(): BelongsToMany
{
return $this->belongsToMany(KnowledgeBase::class);
}
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
public function guides(): BelongsToMany
{
return $this->belongsToMany(Guide::class);
}
}

View File

@@ -47,16 +47,13 @@ class Terminal extends Model
}
/**
* 获取终端关联的指引
* 获取终端所属的线站
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function guides()
public function station()
{
return $this->belongsToMany(Guide::class, 'terminal_guides')
->withPivot('priority')
->withTimestamps()
->orderBy('priority');
return $this->belongsTo(Station::class);
}
/**

View File

@@ -2,14 +2,13 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class TerminalPrompt extends Model
{
use HasFactory, LogsActivity;
use LogsActivity;
/**
* 可批量赋值的属性
*

View File

@@ -50,11 +50,28 @@ class User extends Authenticatable
}
/**
* 获取用户所属的所有分组
* 获取用户关联的线站
*/
public function groups(): BelongsToMany
public function stations(): BelongsToMany
{
return $this->belongsToMany(Group::class);
return $this->belongsToMany(Station::class);
}
/**
* 获取用户可访问的线站 IDs
* 空数组表示无限制(管理员)
*/
public function getAccessibleStationIds(): array
{
return $this->stations()->pluck('stations.id')->toArray();
}
/**
* 用户是否受线站限制
*/
public function hasStationRestriction(): bool
{
return $this->stations()->exists();
}
/**