feat: 初始化知识库系统项目

- 实现基于 Laravel 11 和 Filament 3.X 的文档管理系统
- 添加用户认证和分组管理功能
- 实现文档上传、分类和权限控制
- 集成 Word 文档自动转换为 Markdown
- 集成 Meilisearch 全文搜索引擎
- 实现文档在线预览功能
- 添加安全日志和审计功能
- 完整的简体中文界面
- 包含完整的项目文档和部署指南

技术栈:
- Laravel 11.x
- Filament 3.X
- Meilisearch 1.5+
- Pandoc 文档转换
- Redis 队列系统
- Pest PHP 测试框架
This commit is contained in:
Knowledge Base System
2025-12-05 14:44:44 +08:00
commit acf549c43c
165 changed files with 32838 additions and 0 deletions

View File

@@ -0,0 +1,138 @@
<?php
use App\Models\Document;
use App\Models\Group;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
/**
* Feature: knowledge-base-system, Property 7: 用户文档列表权限过滤
*
* 对于任何用户,当查询其可访问的文档列表时,返回的结果应该只包含:
* (1) 所有全局知识库文档,以及 (2) 该用户所属分组的专用知识库文档
*
* Validates: Requirements 3.1, 3.2, 3.3
*/
test('property 7: 用户可访问的文档列表应该包含所有全局文档和用户分组的专用文档', function () {
// 运行 100 次迭代
for ($i = 0; $i < 100; $i++) {
// 创建随机数量的分组1-5个
$groupCount = rand(1, 5);
$groups = Group::factory()->count($groupCount)->create();
// 创建一个测试用户
$user = User::factory()->create();
// 随机将用户分配到一些分组0-3个
$userGroupCount = rand(0, min(3, $groupCount));
$userGroups = $groups->random(min($userGroupCount, $groupCount));
$user->groups()->attach($userGroups->pluck('id'));
// 创建随机数量的全局文档1-10个
$globalDocCount = rand(1, 10);
$globalDocs = Document::factory()->count($globalDocCount)->global()->create();
// 为每个分组创建随机数量的专用文档0-5个
$dedicatedDocs = collect();
foreach ($groups as $group) {
$docCount = rand(0, 5);
$docs = Document::factory()->count($docCount)->dedicated($group->id)->create();
$dedicatedDocs = $dedicatedDocs->merge($docs);
}
// 执行查询:获取用户可访问的文档
$accessibleDocs = Document::accessibleBy($user)->get();
// 验证:所有全局文档都应该在结果中
foreach ($globalDocs as $globalDoc) {
expect($accessibleDocs->contains('id', $globalDoc->id))
->toBeTrue("全局文档 {$globalDoc->id} 应该对用户可见");
}
// 验证:用户所属分组的专用文档都应该在结果中
$userGroupIds = $userGroups->pluck('id')->toArray();
$userDedicatedDocs = $dedicatedDocs->whereIn('group_id', $userGroupIds);
foreach ($userDedicatedDocs as $doc) {
expect($accessibleDocs->contains('id', $doc->id))
->toBeTrue("用户分组的专用文档 {$doc->id} 应该对用户可见");
}
// 验证:其他分组的专用文档不应该在结果中
$otherGroupDocs = $dedicatedDocs->whereNotIn('group_id', $userGroupIds);
foreach ($otherGroupDocs as $doc) {
expect($accessibleDocs->contains('id', $doc->id))
->toBeFalse("其他分组的专用文档 {$doc->id} 不应该对用户可见");
}
// 验证:结果数量应该等于全局文档数 + 用户分组的专用文档数
$expectedCount = $globalDocCount + $userDedicatedDocs->count();
expect($accessibleDocs->count())
->toBe($expectedCount, "可访问文档数量应该是 {$expectedCount}");
// 清理数据以便下一次迭代
Document::query()->delete();
User::query()->delete();
Group::query()->delete();
}
})->group('property-based-test');
/**
* Feature: knowledge-base-system, Property 8: 其他分组文档隔离
*
* 对于任何用户和任何不属于该用户分组的专用知识库文档,
* 该文档不应该出现在用户的可访问文档列表中
*
* Validates: Requirements 3.4
*/
test('property 8: 其他分组的专用文档应该被隔离,不出现在用户的可访问列表中', function () {
// 运行 100 次迭代
for ($i = 0; $i < 100; $i++) {
// 创建至少 2 个分组
$groupCount = rand(2, 5);
$groups = Group::factory()->count($groupCount)->create();
// 创建一个测试用户
$user = User::factory()->create();
// 将用户分配到部分分组(至少留一个分组不分配)
$userGroupCount = rand(1, $groupCount - 1);
$userGroups = $groups->random($userGroupCount);
$user->groups()->attach($userGroups->pluck('id'));
// 获取用户不属于的分组
$otherGroups = $groups->diff($userGroups);
// 为其他分组创建专用文档
$otherGroupDocs = collect();
foreach ($otherGroups as $group) {
$docCount = rand(1, 5);
$docs = Document::factory()->count($docCount)->dedicated($group->id)->create();
$otherGroupDocs = $otherGroupDocs->merge($docs);
}
// 执行查询:获取用户可访问的文档
$accessibleDocs = Document::accessibleBy($user)->get();
// 验证:其他分组的所有专用文档都不应该在结果中
foreach ($otherGroupDocs as $doc) {
expect($accessibleDocs->contains('id', $doc->id))
->toBeFalse("其他分组的专用文档 {$doc->id}(分组 {$doc->group_id})不应该对用户可见");
}
// 额外验证:确保结果中没有任何其他分组的文档
$otherGroupIds = $otherGroups->pluck('id')->toArray();
$leakedDocs = $accessibleDocs->filter(function ($doc) use ($otherGroupIds) {
return $doc->type === 'dedicated' && in_array($doc->group_id, $otherGroupIds);
});
expect($leakedDocs->count())
->toBe(0, "不应该有任何其他分组的专用文档泄露到用户的可访问列表中");
// 清理数据以便下一次迭代
Document::query()->delete();
User::query()->delete();
Group::query()->delete();
}
})->group('property-based-test');