You've already forked lubo_comment_query
支持通过命令行解析出节目
This commit is contained in:
139
CLAUDE.md
Normal file
139
CLAUDE.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## 项目概述
|
||||
|
||||
这是一个基于 Laravel 8 框架的视频评论和弹幕查询系统,主要功能包括:
|
||||
- 视频评论查询和搜索
|
||||
- 节目管理和视频关联
|
||||
- 弹幕数据管理(支持B站、西瓜视频、抖音)
|
||||
- WebAuthn 身份验证
|
||||
- 文件上传和管理
|
||||
|
||||
## 常用开发命令
|
||||
|
||||
### 后端 PHP/Laravel 命令
|
||||
```bash
|
||||
# 安装 PHP 依赖
|
||||
composer install
|
||||
|
||||
# 开发环境安装(包含开发依赖)
|
||||
composer install
|
||||
|
||||
# 生产环境安装(不含开发依赖)
|
||||
composer install --optimize-autoloader --no-dev
|
||||
|
||||
# 运行测试
|
||||
vendor/bin/phpunit
|
||||
# 或
|
||||
php artisan test
|
||||
|
||||
# 清除缓存
|
||||
php artisan cache:clear
|
||||
php artisan config:clear
|
||||
php artisan route:clear
|
||||
php artisan view:clear
|
||||
|
||||
# 生成应用密钥
|
||||
php artisan key:generate
|
||||
|
||||
# 数据库迁移
|
||||
php artisan migrate
|
||||
|
||||
# 启动开发服务器
|
||||
php artisan serve
|
||||
```
|
||||
|
||||
### 前端构建命令
|
||||
```bash
|
||||
# 安装前端依赖
|
||||
yarn install
|
||||
# 或
|
||||
npm install
|
||||
|
||||
# 开发模式构建
|
||||
yarn dev
|
||||
# 或
|
||||
npm run dev
|
||||
|
||||
# 监听文件变化并自动重新构建
|
||||
yarn watch
|
||||
# 或
|
||||
npm run watch
|
||||
|
||||
# 生产环境构建
|
||||
yarn production
|
||||
# 或
|
||||
npm run production
|
||||
```
|
||||
|
||||
### 生产环境部署
|
||||
使用 `build.sh` 脚本进行完整的生产环境构建:
|
||||
```bash
|
||||
./build.sh
|
||||
```
|
||||
该脚本会依次执行:
|
||||
1. 安装 PHP 依赖(生产环境)
|
||||
2. 安装前端依赖
|
||||
3. 构建前端资源
|
||||
4. 缓存配置、路由和视图
|
||||
|
||||
## 核心架构
|
||||
|
||||
### 数据模型关系
|
||||
- `Videos`: 视频主表,以 bvid 为主键
|
||||
- `VideoComments`: 视频评论,关联到 Videos
|
||||
- `VideoDanmakus`: 视频弹幕,支持多平台(B站、西瓜、抖音)
|
||||
- `Programs`: 节目表
|
||||
- `ProgramVideos`: 节目与视频的多对多关联表
|
||||
- `ProgramAppends`: 节目的点播内容
|
||||
- `User`: 用户表,支持传统登录和 WebAuthn
|
||||
|
||||
### 控制器架构
|
||||
#### 查询控制器(公开访问)
|
||||
- `CommentQueryController`: 评论搜索,支持关键词分词搜索
|
||||
- `VideoQueryController`: 视频信息查询
|
||||
- `ProgramQueryController`: 节目查询,包含关联视频
|
||||
- `DanmakuQueryController`: 弹幕查询
|
||||
|
||||
#### 管理控制器(需要登录)
|
||||
- `ProgramConstructController`: 节目创建和编辑
|
||||
- `ProgramVideoConstructController`: 节目视频关联管理
|
||||
- `ProgramAppendConstructController`: 节目点播内容管理
|
||||
- `DanmakuConstructController`: 弹幕批量导入
|
||||
- `FileController`: 文件上传
|
||||
|
||||
#### 用户认证
|
||||
- `UserController`: 传统用户名密码认证
|
||||
- `UserWebAuthnController`: WebAuthn 无密码认证
|
||||
|
||||
### 前端资源结构
|
||||
- 使用 Laravel Mix 构建工具
|
||||
- 支持 TailwindCSS 样式框架
|
||||
- JavaScript 组件化,主要包含:
|
||||
- `from_select.js`: 下拉选择组件
|
||||
- `webauthn.js`: WebAuthn 认证逻辑
|
||||
- 生产环境支持版本哈希和 S3 部署
|
||||
|
||||
### 视图组件
|
||||
- Blade 模板引擎
|
||||
- 组件化设计:
|
||||
- `components/links/`: 用户和视频链接组件
|
||||
- `components/appends/`: 点播内容列表组件
|
||||
- 支持中文本地化(zh_CN)
|
||||
|
||||
### 特殊功能
|
||||
- **多平台弹幕支持**: 通过 platform_id 区分不同平台(1=B站,2=西瓜,3=抖音)
|
||||
- **关键词分词搜索**: 评论搜索支持空格分隔的多关键词
|
||||
- **WebAuthn 集成**: 支持现代无密码认证
|
||||
- **文件上传**: 支持 S3 兼容存储
|
||||
- **时间戳处理**: 使用 Unix 时间戳格式存储
|
||||
|
||||
### 路由分组
|
||||
- `/`: 公开评论查询页面
|
||||
- `/construct/*`: 需要认证的管理功能
|
||||
- `/user/*`: 用户认证相关功能
|
||||
|
||||
### 数据库时间格式
|
||||
模型使用 Unix 时间戳格式 (`protected $dateFormat = 'U'`),注意在处理时间相关逻辑时需要考虑这个格式。
|
176
app/Console/Commands/CreateProgramsFromComments.php
Normal file
176
app/Console/Commands/CreateProgramsFromComments.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Programs;
|
||||
use App\Models\ProgramVideos;
|
||||
use App\Models\VideoComments;
|
||||
use App\Models\Videos;
|
||||
use App\Models\VideoParts;
|
||||
use App\Util\ProgramVideoUtil;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CreateProgramsFromComments extends Command
|
||||
{
|
||||
protected $signature = 'programs:create-from-comments
|
||||
{--dry-run : 仅显示结果,不实际创建}
|
||||
{--limit=10 : 处理视频数量限制}';
|
||||
|
||||
protected $description = '查询没有关联 programs 的视频,基于置顶的评论自动创建 programs';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$dryRun = $this->option('dry-run');
|
||||
$limit = (int) $this->option('limit');
|
||||
|
||||
$this->info('开始查询没有关联 programs 的视频...');
|
||||
|
||||
// 查询没有关联 programs 的视频,且有置顶的评论
|
||||
$videos = Videos::query()
|
||||
->whereDoesntHave('program_pivots')
|
||||
->whereHas('comments', function ($query) {
|
||||
$query->where('is_top', 1);
|
||||
})
|
||||
->with(['comments' => function ($query) {
|
||||
$query->where('is_top', 1)->orderBy('created_at');
|
||||
}])
|
||||
->limit($limit)
|
||||
->get();
|
||||
|
||||
if ($videos->isEmpty()) {
|
||||
$this->info('没有找到符合条件的视频。');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->info("找到 {$videos->count()} 个视频,开始处理...");
|
||||
|
||||
$successCount = 0;
|
||||
$errorCount = 0;
|
||||
|
||||
foreach ($videos as $video) {
|
||||
$this->line("处理视频: {$video->bvid} - {$video->title}");
|
||||
|
||||
$topComment = $video->comments->first();
|
||||
if (!$topComment) {
|
||||
$this->warn(" 跳过: 没有找到置顶的评论");
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->line(" 评论内容: " . mb_substr($topComment->content, 0, 100) . '...');
|
||||
|
||||
if ($dryRun) {
|
||||
$this->info(" [DryRun] 将会基于此评论创建 programs");
|
||||
$successCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->createProgramsFromComment($video->bvid, $topComment->content);
|
||||
if ($result['success']) {
|
||||
$this->info(" ✅ 成功创建 {$result['count']} 个 programs");
|
||||
$successCount++;
|
||||
} else {
|
||||
$this->error(" ❌ 创建失败: {$result['error']}");
|
||||
$errorCount++;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->error(" ❌ 处理异常: " . $e->getMessage());
|
||||
$errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->info("\n处理完成:");
|
||||
$this->info("成功: {$successCount} 个视频");
|
||||
if ($errorCount > 0) {
|
||||
$this->warn("失败: {$errorCount} 个视频");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于评论内容创建 programs
|
||||
* 复用 ProgramConstructController::batch_create 的逻辑
|
||||
*/
|
||||
private function createProgramsFromComment(string $bvid, string $content): array
|
||||
{
|
||||
// 检查是否已有关联的 programs
|
||||
$count = ProgramVideos::query()->where("video_bvid", "=", $bvid)->count();
|
||||
if ($count > 0) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => "该BVID下已有{$count}个节目关联"
|
||||
];
|
||||
}
|
||||
|
||||
// 使用和 batch_create 相同的正则表达式
|
||||
$regex = "/^(p(?P<part>\d{1,2})[-# _:,)]+)?(?P<time>(\d{1,2}[::])?\d{1,3}[::]\d{1,2})[\s-]*(?P<content>.+)$/ui";
|
||||
$video_parts = VideoParts::query()->where("bvid", "=", $bvid)->get();
|
||||
|
||||
$createdCount = 0;
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
foreach (explode("\n", $content) as $line) {
|
||||
$line = trim($line);
|
||||
if (empty($line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$match = [];
|
||||
$match_count = preg_match($regex, $line, $match);
|
||||
if ($match_count === 0) {
|
||||
$this->line(" 跳过行: {$line}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$time = $match["time"];
|
||||
$time = str_replace(":", ":", $time);
|
||||
while (substr_count($time, ":") < 2) {
|
||||
$time = "0:" . $time;
|
||||
}
|
||||
|
||||
$program = new Programs();
|
||||
$program->name = trim($match["content"]);
|
||||
$program->save();
|
||||
|
||||
$video_pivot = new ProgramVideos();
|
||||
$video_pivot->video_bvid = $bvid;
|
||||
$video_pivot->start_part = empty($match["part"]) ? 1 : $match["part"];
|
||||
$video_pivot->start_time = $time;
|
||||
$video_pivot->program_id = $program->id;
|
||||
$video_pivot->save();
|
||||
|
||||
$createdCount++;
|
||||
$this->line(" 创建: {$program->name} - P{$video_pivot->start_part} {$time}");
|
||||
}
|
||||
|
||||
if ($createdCount > 0) {
|
||||
DB::commit();
|
||||
|
||||
// 修复创建时间
|
||||
ProgramVideoUtil::fix_created_at_by_part_info($bvid, true);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'count' => $createdCount
|
||||
];
|
||||
} else {
|
||||
DB::rollBack();
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => '没有解析出有效的节目信息'
|
||||
];
|
||||
}
|
||||
|
||||
} catch (QueryException $e) {
|
||||
DB::rollBack();
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user